mirror of
https://github.com/GoodStartLabs/AI_Diplomacy.git
synced 2026-04-19 12:58:09 +00:00
add simplified prompts
This commit is contained in:
parent
0bd909b30b
commit
ebf26cf8a6
33 changed files with 1762 additions and 143 deletions
|
|
@ -90,26 +90,24 @@ class DiplomacyAgent:
|
|||
|
||||
# --- Load and set the appropriate system prompt ---
|
||||
# Get the directory containing the current file (agent.py)
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
# Construct path relative to the current file's directory
|
||||
default_prompts_path = os.path.join(current_dir, "prompts")
|
||||
power_prompt_filename = f"{power_name.lower()}_system_prompt.txt"
|
||||
default_prompt_filename = "system_prompt.txt"
|
||||
current_dir = os.path.dirname(os.path.abspath(__file__))
|
||||
default_prompts_path = os.path.join(current_dir, "prompts")
|
||||
prompts_root = self.prompts_dir or default_prompts_path
|
||||
|
||||
# Use the provided prompts_dir if available, otherwise use the default
|
||||
prompts_path_to_use = self.prompts_dir if self.prompts_dir else default_prompts_path
|
||||
|
||||
power_prompt_filepath = os.path.join(prompts_path_to_use, power_prompt_filename)
|
||||
default_prompt_filepath = os.path.join(prompts_path_to_use, default_prompt_filename)
|
||||
power_prompt_name = f"{power_name.lower()}_system_prompt.txt"
|
||||
default_prompt_name = "system_prompt.txt"
|
||||
|
||||
system_prompt_content = load_prompt(power_prompt_filepath, prompts_dir=self.prompts_dir)
|
||||
power_prompt_path = os.path.join(prompts_root, power_prompt_name)
|
||||
default_prompt_path = os.path.join(prompts_root, default_prompt_name)
|
||||
|
||||
system_prompt_content = load_prompt(power_prompt_path)
|
||||
|
||||
if not system_prompt_content:
|
||||
logger.warning(f"Power-specific prompt '{power_prompt_filepath}' not found or empty. Loading default system prompt.")
|
||||
system_prompt_content = load_prompt(default_prompt_filepath, prompts_dir=self.prompts_dir)
|
||||
else:
|
||||
logger.info(f"Loaded power-specific system prompt for {power_name}.")
|
||||
# ----------------------------------------------------
|
||||
logger.warning(
|
||||
f"Power-specific prompt not found at {power_prompt_path}. Falling back to default."
|
||||
)
|
||||
system_prompt_content = load_prompt(default_prompt_path)
|
||||
|
||||
|
||||
if system_prompt_content: # Ensure we actually have content before setting
|
||||
self.client.set_system_prompt(system_prompt_content)
|
||||
|
|
@ -547,6 +545,12 @@ class DiplomacyAgent:
|
|||
diary_text_candidate = parsed_data[key].strip()
|
||||
logger.info(f"[{self.power_name}] Successfully extracted '{key}' for diary.")
|
||||
break
|
||||
|
||||
if 'intent' in parsed_data:
|
||||
if diary_text_candidate == None:
|
||||
diary_text_candidate = parsed_data['intent']
|
||||
else:
|
||||
diary_text_candidate += '\nIntent: ' + parsed_data['intent']
|
||||
|
||||
if diary_text_candidate:
|
||||
diary_entry_text = diary_text_candidate
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ async def run_diary_consolidation(
|
|||
agent: 'DiplomacyAgent',
|
||||
game: "Game",
|
||||
log_file_path: str,
|
||||
entries_to_keep_unsummarized: int = 15,
|
||||
entries_to_keep_unsummarized: int = 6,
|
||||
prompts_dir: Optional[str] = None,
|
||||
):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -179,7 +179,8 @@ def save_game_state(
|
|||
def load_game_state(
|
||||
run_dir: str,
|
||||
game_file_name: str,
|
||||
resume_from_phase: Optional[str] = None
|
||||
run_config: Namespace,
|
||||
resume_from_phase: Optional[str] = None,
|
||||
) -> Tuple[Game, Dict[str, DiplomacyAgent], GameHistory, Optional[Namespace]]:
|
||||
"""Loads and reconstructs the game state from a saved game file."""
|
||||
game_file_path = os.path.join(run_dir, game_file_name)
|
||||
|
|
@ -190,15 +191,6 @@ def load_game_state(
|
|||
with open(game_file_path, 'r') as f:
|
||||
saved_game_data = json.load(f)
|
||||
|
||||
# Find the latest config saved in the file
|
||||
run_config = None
|
||||
if saved_game_data.get("phases"):
|
||||
for phase in reversed(saved_game_data["phases"]):
|
||||
if "config" in phase:
|
||||
run_config = Namespace(**phase["config"])
|
||||
logger.info(f"Loaded run configuration from phase {phase['name']}.")
|
||||
break
|
||||
|
||||
# If resuming, find the specified phase and truncate the data after it
|
||||
if resume_from_phase:
|
||||
logger.info(f"Resuming from phase '{resume_from_phase}'. Truncating subsequent data.")
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ from typing import Dict, List, Callable, Optional, Any, Set, Tuple
|
|||
from diplomacy.engine.map import Map as GameMap
|
||||
from diplomacy.engine.game import Game as BoardState
|
||||
import logging
|
||||
import re
|
||||
|
||||
# Placeholder for actual map type from diplomacy.engine.map.Map
|
||||
# GameMap = Any
|
||||
|
|
@ -15,78 +16,61 @@ logger = logging.getLogger(__name__)
|
|||
|
||||
def build_diplomacy_graph(game_map: GameMap) -> Dict[str, Dict[str, List[str]]]:
|
||||
"""
|
||||
Builds a graph where keys are SHORT province names (e.g., 'PAR', 'STP').
|
||||
Adjacency lists also contain SHORT province names.
|
||||
This graph is used for BFS pathfinding.
|
||||
Return graph[PROV]['ARMY'|'FLEET'] = list of 3-letter neighbour provinces.
|
||||
Works for dual-coast provinces by interrogating `abuts()` directly instead
|
||||
of relying on loc_abut.
|
||||
"""
|
||||
graph: Dict[str, Dict[str, List[str]]] = {}
|
||||
|
||||
# Deriving a clean list of unique, 3-letter, uppercase short province names
|
||||
# game_map.locs contains all locations, including coasts e.g. "STP/SC"
|
||||
unique_short_names = set()
|
||||
for loc in game_map.locs:
|
||||
short_name = loc.split('/')[0][:3].upper() # Take first 3 chars and uppercase
|
||||
if len(short_name) == 3: # Ensure it's a 3-letter name
|
||||
unique_short_names.add(short_name)
|
||||
|
||||
all_short_province_names = sorted(list(unique_short_names))
|
||||
# ── collect all 3-letter province codes ───────────────────────────────
|
||||
provs: Set[str] = {
|
||||
loc.split("/")[0][:3].upper() # 'BUL/EC' -> 'BUL'
|
||||
for loc in game_map.locs
|
||||
if len(loc.split("/")[0]) == 3
|
||||
}
|
||||
|
||||
# Initialize graph with all valid short province names as keys
|
||||
for province_name in all_short_province_names:
|
||||
graph[province_name] = {'ARMY': [], 'FLEET': []}
|
||||
graph: Dict[str, Dict[str, List[str]]] = {
|
||||
p: {"ARMY": [], "FLEET": []} for p in provs
|
||||
}
|
||||
|
||||
for province_short_source in all_short_province_names: # e.g. 'PAR', 'STP'
|
||||
# Get all full names for this source province (e.g. 'STP' -> ['STP/NC', 'STP/SC', 'STP'])
|
||||
full_names_for_source = game_map.loc_coasts.get(province_short_source, [province_short_source])
|
||||
# ── helper: list every concrete variant of a province ─────────────────
|
||||
def variants(code: str) -> List[str]:
|
||||
lst = list(game_map.loc_coasts.get(code, []))
|
||||
if code not in lst:
|
||||
lst.append(code) # ensure base node included
|
||||
return lst
|
||||
|
||||
for loc_full_source_variant in full_names_for_source: # e.g. 'STP/NC', then 'STP/SC', then 'STP'
|
||||
# province_short_source is already the short name like 'STP'
|
||||
# game_map.loc_abut provides general adjacencies, which might include specific coasts or lowercase names
|
||||
for raw_adj_loc_from_loc_abut in game_map.loc_abut.get(province_short_source, []):
|
||||
# Normalize this raw adjacent location to its short, uppercase form
|
||||
adj_short_name_normalized = raw_adj_loc_from_loc_abut[:3].upper()
|
||||
# ── populate adjacency by brute-force queries to `abuts()` ────────────
|
||||
for src in provs:
|
||||
src_vers = variants(src)
|
||||
|
||||
# Get all full names for this *normalized* adjacent short name (e.g. 'BUL' -> ['BUL/EC', 'BUL/SC', 'BUL'])
|
||||
full_names_for_adj_dest = game_map.loc_coasts.get(adj_short_name_normalized, [adj_short_name_normalized])
|
||||
for dest in provs:
|
||||
if dest == src:
|
||||
continue
|
||||
dest_vers = variants(dest)
|
||||
|
||||
# Check for ARMY movement
|
||||
unit_char_army = 'A'
|
||||
if any(
|
||||
game_map.abuts(
|
||||
unit_char_army,
|
||||
loc_full_source_variant, # Specific full source, e.g. 'STP/NC'
|
||||
'-', # Order type for move
|
||||
full_dest_variant # Specific full destination, e.g. 'MOS' or 'FIN'
|
||||
)
|
||||
for full_dest_variant in full_names_for_adj_dest
|
||||
):
|
||||
if adj_short_name_normalized not in graph[province_short_source]['ARMY']:
|
||||
graph[province_short_source]['ARMY'].append(adj_short_name_normalized)
|
||||
# ARMY — only bases count as the origin (armies can’t sit on /EC)
|
||||
if any(
|
||||
game_map.abuts("A", src, "-", dv) # src is the base node
|
||||
for dv in dest_vers
|
||||
):
|
||||
graph[src]["ARMY"].append(dest)
|
||||
|
||||
# FLEET — any src variant that can host a fleet is valid
|
||||
if any(
|
||||
game_map.abuts("F", sv, "-", dv)
|
||||
for sv in src_vers
|
||||
for dv in dest_vers
|
||||
):
|
||||
graph[src]["FLEET"].append(dest)
|
||||
|
||||
# ── tidy up duplicates / order ---------------------------------------
|
||||
for p in graph:
|
||||
graph[p]["ARMY"] = sorted(set(graph[p]["ARMY"]))
|
||||
graph[p]["FLEET"] = sorted(set(graph[p]["FLEET"]))
|
||||
|
||||
# Check for FLEET movement
|
||||
unit_char_fleet = 'F'
|
||||
if any(
|
||||
game_map.abuts(
|
||||
unit_char_fleet,
|
||||
loc_full_source_variant, # Specific full source, e.g. 'STP/NC'
|
||||
'-', # Order type for move
|
||||
full_dest_variant # Specific full destination, e.g. 'BAR' or 'NWY'
|
||||
)
|
||||
for full_dest_variant in full_names_for_adj_dest
|
||||
):
|
||||
if adj_short_name_normalized not in graph[province_short_source]['FLEET']:
|
||||
graph[province_short_source]['FLEET'].append(adj_short_name_normalized)
|
||||
|
||||
# Remove duplicates from adjacency lists (just in case)
|
||||
for province_short in graph:
|
||||
if 'ARMY' in graph[province_short]:
|
||||
graph[province_short]['ARMY'] = sorted(list(set(graph[province_short]['ARMY'])))
|
||||
if 'FLEET' in graph[province_short]:
|
||||
graph[province_short]['FLEET'] = sorted(list(set(graph[province_short]['FLEET'])))
|
||||
|
||||
return graph
|
||||
|
||||
|
||||
|
||||
def bfs_shortest_path(
|
||||
graph: Dict[str, Dict[str, List[str]]],
|
||||
board_state: BoardState,
|
||||
|
|
@ -241,33 +225,56 @@ def get_nearest_enemy_units(
|
|||
|
||||
|
||||
def get_nearest_uncontrolled_scs(
|
||||
game_map: GameMap,
|
||||
board_state: BoardState,
|
||||
graph: Dict[str, Dict[str, List[str]]],
|
||||
power_name: str,
|
||||
start_unit_loc_full: str,
|
||||
start_unit_type: str,
|
||||
n: int = 3
|
||||
) -> List[Tuple[str, int, List[str]]]: # (sc_name_short, distance, path_short_names)
|
||||
"""Finds up to N nearest SCs not controlled by power_name, sorted by path length."""
|
||||
uncontrolled_sc_paths: List[Tuple[str, int, List[str]]] = []
|
||||
game_map: GameMap,
|
||||
board_state: BoardState,
|
||||
graph: Dict[str, Dict[str, List[str]]],
|
||||
power_name: str,
|
||||
start_unit_loc_full: str,
|
||||
start_unit_type: str,
|
||||
n: int = 3,
|
||||
) -> List[Tuple[str, int, List[str]]]:
|
||||
"""
|
||||
Return up to N nearest supply centres not controlled by `power_name`,
|
||||
excluding centres that are the unit’s own province (distance 0) or
|
||||
adjacent in one move (distance 1).
|
||||
|
||||
all_scs_short = game_map.scs # This is a list of short province names that are SCs
|
||||
Each tuple is (sc_code + ctrl_tag, distance, path_of_short_codes).
|
||||
"""
|
||||
results: List[Tuple[str, int, List[str]]] = []
|
||||
|
||||
for sc_short in game_map.scs: # all SC province codes
|
||||
controller = get_sc_controller(game_map, board_state, sc_short)
|
||||
if controller == power_name:
|
||||
continue # already ours
|
||||
|
||||
# helper for BFS target test
|
||||
def is_target(loc_short: str, _state: BoardState) -> bool:
|
||||
return loc_short == sc_short
|
||||
|
||||
path = bfs_shortest_path(
|
||||
graph,
|
||||
board_state,
|
||||
game_map,
|
||||
start_unit_loc_full,
|
||||
start_unit_type,
|
||||
is_target,
|
||||
)
|
||||
if not path:
|
||||
continue # unreachable
|
||||
|
||||
distance = len(path) - 1 # moves needed
|
||||
|
||||
# skip distance 0 (same province) and 1 (adjacent)
|
||||
if distance <= 1:
|
||||
continue
|
||||
|
||||
tag = f"{sc_short} (Ctrl: {controller or 'None'})"
|
||||
results.append((tag, distance, path))
|
||||
|
||||
# sort by distance, then SC code for tie-breaks
|
||||
results.sort(key=lambda x: (x[1], x[0]))
|
||||
return results[:n]
|
||||
|
||||
for sc_loc_short in all_scs_short:
|
||||
controller = get_sc_controller(game_map, board_state, sc_loc_short)
|
||||
if controller != power_name:
|
||||
def is_target_sc(loc_short: str, current_board_state: BoardState) -> bool:
|
||||
return loc_short == sc_loc_short
|
||||
|
||||
path_short_names = bfs_shortest_path(graph, board_state, game_map, start_unit_loc_full, start_unit_type, is_target_sc)
|
||||
if path_short_names:
|
||||
# Path includes start, so distance is len - 1
|
||||
uncontrolled_sc_paths.append((f"{sc_loc_short} (Ctrl: {controller or 'None'})", len(path_short_names) -1, path_short_names))
|
||||
|
||||
# Sort by distance (path length - 1), then by SC name for tie-breaking
|
||||
uncontrolled_sc_paths.sort(key=lambda x: (x[1], x[0]))
|
||||
return uncontrolled_sc_paths[:n]
|
||||
|
||||
def get_adjacent_territory_details(
|
||||
game_map: GameMap,
|
||||
|
|
@ -362,7 +369,7 @@ def get_adjacent_territory_details(
|
|||
|
||||
|
||||
# --- Main context generation function ---
|
||||
def generate_rich_order_context(game: Any, power_name: str, possible_orders_for_power: Dict[str, List[str]]) -> str:
|
||||
def generate_rich_order_context_xml(game: Any, power_name: str, possible_orders_for_power: Dict[str, List[str]]) -> str:
|
||||
"""
|
||||
Generates a strategic overview context string.
|
||||
Details units and SCs for power_name, including possible orders and simplified adjacencies for its units.
|
||||
|
|
@ -456,3 +463,405 @@ def generate_rich_order_context(game: Any, power_name: str, possible_orders_for_
|
|||
|
||||
final_context_lines.append("</PossibleOrdersContext>")
|
||||
return "\n".join(final_context_lines)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Regex and tiny helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
import re
|
||||
from typing import Tuple, List, Dict, Optional, Any
|
||||
|
||||
# ── order-syntax matchers ─────────────────────────────────────────────────
|
||||
_SIMPLE_MOVE_RE = re.compile(r"^[AF] [A-Z]{3}(?:/[A-Z]{2})? - [A-Z]{3}(?:/[A-Z]{2})?$")
|
||||
_HOLD_RE = re.compile(r"^[AF] [A-Z]{3}(?:/[A-Z]{2})? H$") # NEW
|
||||
_RETREAT_RE = re.compile(r"^[AF] [A-Z]{3}(?:/[A-Z]{2})? R [A-Z]{3}(?:/[A-Z]{2})?$")
|
||||
_ADJUST_RE = re.compile(r"^[AF] [A-Z]{3}(?:/[A-Z]{2})? [BD]$") # build / disband
|
||||
|
||||
def _is_hold_order(order: str) -> bool: # NEW
|
||||
return bool(_HOLD_RE.match(order.strip()))
|
||||
|
||||
|
||||
def _norm_power(name: str) -> str:
|
||||
"""Trim & uppercase for reliable comparisons."""
|
||||
return name.strip().upper()
|
||||
|
||||
def _is_simple_move(order: str) -> bool:
|
||||
return bool(_SIMPLE_MOVE_RE.match(order.strip()))
|
||||
|
||||
def _is_retreat_order(order: str) -> bool:
|
||||
return bool(_RETREAT_RE.match(order.strip()))
|
||||
|
||||
def _is_adjust_order(order: str) -> bool:
|
||||
return bool(_ADJUST_RE.match(order.strip()))
|
||||
|
||||
def _split_move(order: str) -> Tuple[str, str]:
|
||||
"""Return ('A BUD', 'TRI') from 'A BUD - TRI' (validated move only)."""
|
||||
unit_part, dest = order.split(" - ")
|
||||
return unit_part.strip(), dest.strip()
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Gather *all* friendly support orders for a given move
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _all_support_examples(
|
||||
mover: str,
|
||||
dest: str,
|
||||
all_orders: Dict[str, List[str]],
|
||||
) -> List[str]:
|
||||
"""
|
||||
Return *every* order of the form 'A/F XYZ S <mover> - <dest>'
|
||||
issued by our other units. Order of return is input order.
|
||||
"""
|
||||
target = f"{mover} - {dest}"
|
||||
supports: List[str] = []
|
||||
|
||||
for loc, orders in all_orders.items():
|
||||
if mover.endswith(loc):
|
||||
continue # skip the moving unit itself
|
||||
for o in orders:
|
||||
if " S " in o and target in o:
|
||||
supports.append(o.strip())
|
||||
|
||||
return supports
|
||||
|
||||
def _all_support_hold_examples(
|
||||
holder: str,
|
||||
all_orders: Dict[str, List[str]],
|
||||
) -> List[str]:
|
||||
"""
|
||||
Return every order of the form 'A/F XYZ S <holder>' that supports
|
||||
<holder> to HOLD, excluding the holding unit itself.
|
||||
"""
|
||||
target = f" S {holder}"
|
||||
supports: List[str] = []
|
||||
|
||||
for loc, orders in all_orders.items():
|
||||
if holder.endswith(loc): # skip the holding unit
|
||||
continue
|
||||
for o in orders:
|
||||
if o.strip().endswith(target):
|
||||
supports.append(o.strip())
|
||||
return supports
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Province-type resolver (handles short codes, coasts, seas)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _province_type_display(game_map, prov_short: str) -> str:
|
||||
"""
|
||||
Return 'LAND', 'COAST', or 'WATER' for the 3-letter province code.
|
||||
Falls back to 'UNKNOWN' only if nothing matches.
|
||||
"""
|
||||
for full in game_map.loc_coasts.get(prov_short, [prov_short]):
|
||||
t = game_map.loc_type.get(full)
|
||||
if not t:
|
||||
continue
|
||||
t = t.upper()
|
||||
if t in ("LAND", "L"):
|
||||
return "LAND"
|
||||
if t in ("COAST", "C"):
|
||||
return "COAST"
|
||||
if t in ("WATER", "SEA", "W"):
|
||||
return "WATER"
|
||||
return "UNKNOWN"
|
||||
|
||||
|
||||
def _dest_occupancy_desc(
|
||||
dest_short: str,
|
||||
game_map,
|
||||
board_state,
|
||||
our_power: str,
|
||||
) -> str:
|
||||
""" '(occupied by X)', '(occupied by X — you!)', or '(unoccupied)' """
|
||||
occupant: Optional[str] = None
|
||||
for full in game_map.loc_coasts.get(dest_short, [dest_short]):
|
||||
u = get_unit_at_location(board_state, full)
|
||||
if u:
|
||||
occupant = u.split(" ")[-1].strip("()")
|
||||
break
|
||||
if occupant is None:
|
||||
return "(unoccupied)"
|
||||
if occupant == our_power:
|
||||
return f"(occupied by {occupant} — you!)"
|
||||
return f"(occupied by {occupant})"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Adjacent-territory lines (used by movement-phase builder)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _adjacent_territory_lines(
|
||||
graph,
|
||||
game_map,
|
||||
board_state,
|
||||
unit_loc_full: str,
|
||||
mover_descr: str,
|
||||
our_power: str,
|
||||
) -> List[str]:
|
||||
lines: List[str] = []
|
||||
indent1 = " "
|
||||
indent2 = " "
|
||||
|
||||
unit_loc_short = game_map.loc_name.get(unit_loc_full, unit_loc_full)[:3]
|
||||
mover_type_key = "ARMY" if mover_descr.startswith("A") else "FLEET"
|
||||
adjacents = graph.get(unit_loc_short, {}).get(mover_type_key, [])
|
||||
|
||||
for adj in adjacents:
|
||||
typ_display = _province_type_display(game_map, adj)
|
||||
|
||||
base_parts = [f"{indent1}{adj} ({typ_display})"]
|
||||
|
||||
sc_ctrl = get_sc_controller(game_map, board_state, adj)
|
||||
if sc_ctrl:
|
||||
base_parts.append(f"SC Control: {sc_ctrl}")
|
||||
|
||||
unit_here = None
|
||||
for full in game_map.loc_coasts.get(adj, [adj]):
|
||||
unit_here = get_unit_at_location(board_state, full)
|
||||
if unit_here:
|
||||
break
|
||||
if unit_here:
|
||||
base_parts.append(f"Units: {unit_here}")
|
||||
|
||||
lines.append(" ".join(base_parts))
|
||||
|
||||
# second analytical line if occupied
|
||||
if unit_here:
|
||||
pwr = unit_here.split(" ")[-1].strip("()")
|
||||
if pwr == our_power:
|
||||
friend_descr = unit_here.split(" (")[0]
|
||||
lines.append(
|
||||
f"{indent2}Support hold: {mover_descr} S {friend_descr}"
|
||||
)
|
||||
else:
|
||||
lines.append(
|
||||
f"{indent2}-> {unit_here} can support or contest {mover_descr}’s moves and vice-versa"
|
||||
)
|
||||
|
||||
return lines
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Movement-phase generator (UNCHANGED LOGIC)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _generate_rich_order_context_movement(
|
||||
game: Any,
|
||||
power_name: str,
|
||||
possible_orders_for_power: Dict[str, List[str]],
|
||||
) -> str:
|
||||
"""
|
||||
Produce the <Territory …> blocks *exactly* as before for movement phases.
|
||||
"""
|
||||
board_state = game.get_state()
|
||||
game_map = game.map
|
||||
graph = build_diplomacy_graph(game_map)
|
||||
|
||||
blocks: List[str] = []
|
||||
me = _norm_power(power_name)
|
||||
|
||||
for unit_loc_full, orders in possible_orders_for_power.items():
|
||||
unit_full_str = get_unit_at_location(board_state, unit_loc_full)
|
||||
if not unit_full_str:
|
||||
continue
|
||||
|
||||
unit_power = unit_full_str.split(" ")[-1].strip("()")
|
||||
if _norm_power(unit_power) != me:
|
||||
continue # Skip units that aren’t ours
|
||||
|
||||
mover_descr, _ = _split_move(
|
||||
f"{unit_full_str.split(' ')[0]} {unit_loc_full} - {unit_loc_full}"
|
||||
)
|
||||
|
||||
prov_short = game_map.loc_name.get(unit_loc_full, unit_loc_full)[:3]
|
||||
prov_type_disp = _province_type_display(game_map, prov_short)
|
||||
sc_tag = " (SC)" if prov_short in game_map.scs else ""
|
||||
|
||||
owner = get_sc_controller(game_map, board_state, unit_loc_full) or "None"
|
||||
owner_line = (
|
||||
f"Held by {owner} (You)" if owner == power_name else f"Held by {owner}"
|
||||
)
|
||||
|
||||
ind = " "
|
||||
block: List[str] = [f"<Territory {prov_short}>"]
|
||||
block.append(f"{ind}({prov_type_disp}){sc_tag}")
|
||||
block.append(f"{ind}{owner_line}")
|
||||
block.append(f"{ind}Units present: {unit_full_str}")
|
||||
|
||||
# ----- Adjacent territories -----
|
||||
block.append("# Adjacent territories:")
|
||||
block.extend(
|
||||
_adjacent_territory_lines(
|
||||
graph, game_map, board_state,
|
||||
unit_loc_full, mover_descr, power_name
|
||||
)
|
||||
)
|
||||
|
||||
# ----- Nearest enemy units -----
|
||||
block.append("# Nearest units (not ours):")
|
||||
enemies = get_nearest_enemy_units(
|
||||
board_state, graph, game_map,
|
||||
power_name, unit_loc_full,
|
||||
"ARMY" if mover_descr.startswith("A") else "FLEET",
|
||||
n=3,
|
||||
)
|
||||
for u, path in enemies:
|
||||
path_disp = "→".join([unit_loc_full] + path[1:])
|
||||
block.append(f"{ind}{u}, path [{path_disp}]")
|
||||
|
||||
# ----- Nearest uncontrolled SCs -----
|
||||
block.append("# Nearest supply centers (not controlled by us):")
|
||||
scs = get_nearest_uncontrolled_scs(
|
||||
game_map, board_state, graph,
|
||||
power_name, unit_loc_full,
|
||||
"ARMY" if mover_descr.startswith("A") else "FLEET",
|
||||
n=3,
|
||||
)
|
||||
for sc_str, dist, sc_path in scs:
|
||||
path_disp = "→".join([unit_loc_full] + sc_path[1:])
|
||||
sc_fmt = sc_str.replace("Ctrl:", "Controlled by")
|
||||
block.append(f"{ind}{sc_fmt}, path [{path_disp}]")
|
||||
|
||||
# ----- Possible moves -----
|
||||
block.append(f"# Possible {mover_descr} unit movements & supports:")
|
||||
|
||||
simple_moves = [o for o in orders if _is_simple_move(o)]
|
||||
hold_orders = [o for o in orders if _is_hold_order(o)] # NEW
|
||||
|
||||
if not simple_moves and not hold_orders:
|
||||
block.append(f"{ind}None")
|
||||
else:
|
||||
# ---- Moves (same behaviour as before) ----
|
||||
for mv in simple_moves:
|
||||
mover, dest = _split_move(mv)
|
||||
occ = _dest_occupancy_desc(
|
||||
dest.split("/")[0][:3], game_map, board_state, power_name
|
||||
)
|
||||
block.append(f"{ind}{mv} {occ}")
|
||||
|
||||
for s in _all_support_examples(mover, dest, possible_orders_for_power):
|
||||
block.append(f"{ind*2}Available Support: {s}")
|
||||
|
||||
# ---- Holds (new) ----
|
||||
for hd in hold_orders:
|
||||
holder = hd.split(" H")[0] # e.g., 'F DEN'
|
||||
block.append(f"{ind}{hd}")
|
||||
|
||||
for s in _all_support_hold_examples(holder, possible_orders_for_power):
|
||||
block.append(f"{ind*2}Available Support: {s}")
|
||||
|
||||
|
||||
block.append(f"</Territory {prov_short}>")
|
||||
blocks.append("\n".join(block))
|
||||
|
||||
return "\n\n".join(blocks)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Retreat-phase builder – echo orders verbatim, no tags
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _generate_rich_order_context_retreat(
|
||||
game: Any,
|
||||
power_name: str,
|
||||
possible_orders_for_power: Dict[str, List[str]],
|
||||
) -> str:
|
||||
"""
|
||||
Flatten all retreat / disband orders into one list:
|
||||
A PAR R PIC
|
||||
A PAR D
|
||||
F NTH R HEL
|
||||
If the engine supplies nothing, return the standard placeholder.
|
||||
"""
|
||||
lines: List[str] = []
|
||||
for orders in possible_orders_for_power.values():
|
||||
for o in orders:
|
||||
lines.append(o.strip())
|
||||
|
||||
return "\n".join(lines) if lines else "(No dislodged units)"
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Adjustment-phase builder – summary line + orders, no WAIVEs, no tags
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _generate_rich_order_context_adjustment(
|
||||
game: Any,
|
||||
power_name: str,
|
||||
possible_orders_for_power: Dict[str, List[str]],
|
||||
) -> str:
|
||||
"""
|
||||
* First line states how many builds are allowed or disbands required.
|
||||
* Echo every B/D order exactly as supplied, skipping WAIVE.
|
||||
* No wrapper tags.
|
||||
"""
|
||||
board_state = game.get_state()
|
||||
sc_owned = len(board_state.get("centers", {}).get(power_name, []))
|
||||
units_num = len(board_state.get("units", {}).get(power_name, []))
|
||||
delta = sc_owned - units_num # +ve ⇒ builds, -ve ⇒ disbands
|
||||
|
||||
# ----- summary line ----------------------------------------------------
|
||||
if delta > 0:
|
||||
summary = f"Builds available: {delta}"
|
||||
elif delta < 0:
|
||||
summary = f"Disbands required: {-delta}"
|
||||
else:
|
||||
summary = "No builds or disbands required"
|
||||
|
||||
# ----- collect orders (skip WAIVE) -------------------------------------
|
||||
lines: List[str] = [summary]
|
||||
for orders in possible_orders_for_power.values():
|
||||
for o in orders:
|
||||
if "WAIVE" in o.upper():
|
||||
continue
|
||||
lines.append(o.strip())
|
||||
|
||||
# If nothing but the summary, just return the summary.
|
||||
return "\n".join(lines) if len(lines) > 1 else summary
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Phase-dispatch wrapper (public entry point)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def generate_rich_order_context(
|
||||
game: Any,
|
||||
power_name: str,
|
||||
possible_orders_for_power: Dict[str, List[str]],
|
||||
) -> str:
|
||||
"""
|
||||
Call the correct phase-specific builder.
|
||||
|
||||
* Movement phase output is IDENTICAL to the previous implementation.
|
||||
* Retreat and Adjustment phases use the streamlined builders introduced
|
||||
earlier.
|
||||
"""
|
||||
|
||||
phase_type = game.current_short_phase[-1]
|
||||
|
||||
if phase_type == "M": # Movement
|
||||
return _generate_rich_order_context_movement(
|
||||
game, power_name, possible_orders_for_power
|
||||
)
|
||||
|
||||
if phase_type == "R": # Retreat
|
||||
return _generate_rich_order_context_retreat(
|
||||
game, power_name, possible_orders_for_power
|
||||
)
|
||||
|
||||
if phase_type == "A": # Adjustment (build / disband)
|
||||
return _generate_rich_order_context_adjustment(
|
||||
game, power_name, possible_orders_for_power
|
||||
)
|
||||
|
||||
# Fallback – treat unknown formats as movement
|
||||
return _generate_rich_order_context_movement(
|
||||
game, power_name, possible_orders_for_power
|
||||
)
|
||||
|
|
|
|||
|
|
@ -5,7 +5,11 @@ import logging
|
|||
from typing import Dict, List, Optional, Any # Added Any for game type placeholder
|
||||
|
||||
from .utils import load_prompt
|
||||
from .possible_order_context import generate_rich_order_context
|
||||
from .possible_order_context import (
|
||||
generate_rich_order_context,
|
||||
generate_rich_order_context_xml,
|
||||
)
|
||||
import os
|
||||
from .game_history import GameHistory # Assuming GameHistory is correctly importable
|
||||
|
||||
# placeholder for diplomacy.Game to avoid circular or direct dependency if not needed for typehinting only
|
||||
|
|
@ -14,6 +18,17 @@ from .game_history import GameHistory # Assuming GameHistory is correctly import
|
|||
logger = logging.getLogger(__name__)
|
||||
logger.setLevel(logging.DEBUG) # Or inherit from parent logger
|
||||
|
||||
# --- Home-center lookup -------------------------------------------
|
||||
HOME_CENTERS: dict[str, list[str]] = {
|
||||
"AUSTRIA": ["Budapest", "Trieste", "Vienna"],
|
||||
"ENGLAND": ["Edinburgh", "Liverpool", "London"],
|
||||
"FRANCE": ["Brest", "Marseilles", "Paris"],
|
||||
"GERMANY": ["Berlin", "Kiel", "Munich"],
|
||||
"ITALY": ["Naples", "Rome", "Venice"],
|
||||
"RUSSIA": ["Moscow", "Saint Petersburg", "Sevastopol", "Warsaw"],
|
||||
"TURKEY": ["Ankara", "Constantinople", "Smyrna"],
|
||||
}
|
||||
|
||||
def build_context_prompt(
|
||||
game: Any, # diplomacy.Game object
|
||||
board_state: dict,
|
||||
|
|
@ -24,6 +39,7 @@ def build_context_prompt(
|
|||
agent_relationships: Optional[Dict[str, str]] = None,
|
||||
agent_private_diary: Optional[str] = None,
|
||||
prompts_dir: Optional[str] = None,
|
||||
include_messages: Optional[bool] = True,
|
||||
) -> str:
|
||||
"""Builds the detailed context part of the prompt.
|
||||
|
||||
|
|
@ -59,14 +75,27 @@ def build_context_prompt(
|
|||
# Get the current phase
|
||||
year_phase = board_state["phase"] # e.g. 'S1901M'
|
||||
|
||||
possible_orders_context_str = generate_rich_order_context(game, power_name, possible_orders)
|
||||
# Decide which context builder to use.
|
||||
_use_simple = os.getenv("SIMPLE_PROMPTS", "0").lower() in {"1", "true", "yes"}
|
||||
if _use_simple:
|
||||
possible_orders_context_str = generate_rich_order_context(
|
||||
game, power_name, possible_orders
|
||||
)
|
||||
else:
|
||||
possible_orders_context_str = generate_rich_order_context_xml(
|
||||
game, power_name, possible_orders
|
||||
)
|
||||
|
||||
messages_this_round_text = game_history.get_messages_this_round(
|
||||
power_name=power_name,
|
||||
current_phase_name=year_phase
|
||||
)
|
||||
if not messages_this_round_text.strip():
|
||||
messages_this_round_text = "\n(No messages this round)\n"
|
||||
|
||||
if include_messages:
|
||||
messages_this_round_text = game_history.get_messages_this_round(
|
||||
power_name=power_name,
|
||||
current_phase_name=year_phase
|
||||
)
|
||||
if not messages_this_round_text.strip():
|
||||
messages_this_round_text = "\n(No messages this round)\n"
|
||||
else:
|
||||
messages_this_round_text = "\n"
|
||||
|
||||
# Separate active and eliminated powers for clarity
|
||||
active_powers = [p for p in game.powers.keys() if not game.powers[p].is_eliminated()]
|
||||
|
|
@ -75,21 +104,30 @@ def build_context_prompt(
|
|||
# Build units representation with power status
|
||||
units_lines = []
|
||||
for p, u in board_state["units"].items():
|
||||
u_str = ", ".join(u)
|
||||
if game.powers[p].is_eliminated():
|
||||
units_lines.append(f" {p}: {u} [ELIMINATED]")
|
||||
units_lines.append(f" {p}: {u_str} [ELIMINATED]")
|
||||
else:
|
||||
units_lines.append(f" {p}: {u}")
|
||||
units_lines.append(f" {p}: {u_str}")
|
||||
units_repr = "\n".join(units_lines)
|
||||
|
||||
|
||||
# Build centers representation with power status
|
||||
centers_lines = []
|
||||
for p, c in board_state["centers"].items():
|
||||
c_str = ", ".join(c)
|
||||
if game.powers[p].is_eliminated():
|
||||
centers_lines.append(f" {p}: {c} [ELIMINATED]")
|
||||
centers_lines.append(f" {p}: {c_str} [ELIMINATED]")
|
||||
else:
|
||||
centers_lines.append(f" {p}: {c}")
|
||||
centers_lines.append(f" {p}: {c_str}")
|
||||
centers_repr = "\n".join(centers_lines)
|
||||
|
||||
# Build {home_centers}
|
||||
home_centers_str = ", ".join(HOME_CENTERS.get(power_name.upper(), []))
|
||||
|
||||
# Replace token only if it exists (template may not include it)
|
||||
if "{home_centers}" in context_template:
|
||||
context_template = context_template.replace("{home_centers}", home_centers_str)
|
||||
|
||||
context = context_template.format(
|
||||
power_name=power_name,
|
||||
current_phase=year_phase,
|
||||
|
|
@ -135,7 +173,19 @@ def construct_order_generation_prompt(
|
|||
"""
|
||||
# Load prompts
|
||||
_ = load_prompt("few_shot_example.txt", prompts_dir=prompts_dir) # Loaded but not used, as per original logic
|
||||
instructions = load_prompt("order_instructions.txt", prompts_dir=prompts_dir)
|
||||
# Pick the phase-specific instruction file
|
||||
phase_code = board_state["phase"][-1] # 'M' (movement), 'R', or 'A' / 'B'
|
||||
if phase_code == "M":
|
||||
instructions_file = "order_instructions_movement_phase.txt"
|
||||
elif phase_code in ("A", "B"): # builds / adjustments
|
||||
instructions_file = "order_instructions_adjustment_phase.txt"
|
||||
elif phase_code == "R": # retreats
|
||||
instructions_file = "order_instructions_retreat_phase.txt"
|
||||
else: # unexpected – default to movement rules
|
||||
instructions_file = "order_instructions_movement_phase.txt"
|
||||
|
||||
instructions = load_prompt(instructions_file, prompts_dir=prompts_dir)
|
||||
_use_simple = os.getenv("SIMPLE_PROMPTS", "0").lower() in {"1", "true", "yes"}
|
||||
|
||||
# Build the context prompt
|
||||
context = build_context_prompt(
|
||||
|
|
@ -148,7 +198,9 @@ def construct_order_generation_prompt(
|
|||
agent_relationships=agent_relationships,
|
||||
agent_private_diary=agent_private_diary_str,
|
||||
prompts_dir=prompts_dir,
|
||||
include_messages=not _use_simple, # include only when *not* simple
|
||||
)
|
||||
|
||||
final_prompt = system_prompt + "\n\n" + context + "\n\n" + instructions
|
||||
return final_prompt
|
||||
|
||||
return final_prompt
|
||||
|
|
|
|||
122
ai_diplomacy/prompts/order_instructions_movement_phase.txt
Normal file
122
ai_diplomacy/prompts/order_instructions_movement_phase.txt
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
PRIMARY OBJECTIVE
|
||||
Control 18 supply centers. Nothing else will do.
|
||||
|
||||
CRITICAL RULES
|
||||
1. Only use orders from the provided possible_orders list
|
||||
2. Support orders must match actual moves (e.g., 'A PAR S F PIC - ENG' needs 'F PIC - ENG')
|
||||
3. Build orders (build phase only):
|
||||
- Format: '[UnitType] [Location3LetterCode] B'
|
||||
- UnitType: 'A' (Army) or 'F' (Fleet)
|
||||
- Example: 'A PAR B', 'F LON B'
|
||||
- NOTE YOU CAN ONLY BUILD UNITS IN YOUR HOME CENTER THAT ARE EMPTY, THE ONES YOU STARTED WITH, IF YOU LOSE THESE YOU CANNOT BUILD UNITS SO THEY ARE CRITICAL
|
||||
Austria
|
||||
- Budapest
|
||||
- Trieste
|
||||
- Vienna
|
||||
England
|
||||
- Edinburgh
|
||||
- Liverpool
|
||||
- London
|
||||
France
|
||||
- Brest
|
||||
- Marseilles
|
||||
- Paris
|
||||
Germany
|
||||
- Berlin
|
||||
- Kiel
|
||||
- Munich
|
||||
Italy
|
||||
- Naples
|
||||
- Rome
|
||||
- Venice
|
||||
Russia
|
||||
- Moscow
|
||||
- Saint Petersburg
|
||||
- Sevastopol
|
||||
- Warsaw
|
||||
Turkey
|
||||
- Ankara
|
||||
- Constantinople
|
||||
- Smyrna
|
||||
|
||||
ORDER SUBMISSION PROCESS
|
||||
1. ANALYZE
|
||||
- Review game state, orders, messages, and other powers' motivations
|
||||
- Focus on expansion and capturing supply centers
|
||||
- Be aggressive, not passive
|
||||
- Take calculated risks for significant gains
|
||||
- Find alternative paths if blocked
|
||||
|
||||
2. REASON
|
||||
- Write out your strategic thinking
|
||||
- Explain goals and move choices
|
||||
- Consider supports and holds
|
||||
|
||||
3. FORMAT
|
||||
Return orders in this exact format:
|
||||
PARSABLE OUTPUT:
|
||||
{{
|
||||
"orders": ["order1", "order2", ...]
|
||||
}}
|
||||
|
||||
|
||||
4. Dual-coast provinces (STP, SPA, BUL):
|
||||
- Specify coast when needed: 'F [PROVINCE]/[COAST_CODE]'
|
||||
- Example: 'F STP/NC B', 'A MAR S F SPA/SC - WES'
|
||||
- Coast codes: NC (North), SC (South), EC (East), WC (West)
|
||||
5. All orders resolve simultaneously
|
||||
6. Submit orders only, no messages
|
||||
|
||||
EXAMPLES
|
||||
Reasoning:
|
||||
- Secure Burgundy against German threat
|
||||
- Mid-Atlantic move enables future convoys
|
||||
|
||||
PARSABLE OUTPUT:
|
||||
{{
|
||||
"orders": [
|
||||
"A PAR H",
|
||||
"A MAR - BUR",
|
||||
"F BRE - MAO"
|
||||
]
|
||||
}}
|
||||
|
||||
Example 2: As Germany, Spring 1901, aiming for a swift expansion into DEN and HOL, while also securing home centers.
|
||||
|
||||
Reasoning:
|
||||
- Denmark (DEN) and Holland (HOL) are key neutral centers for early German expansion.
|
||||
- Need to secure Berlin (BER) and Munich (MUN) against potential French or Russian incursions.
|
||||
- Kiel (KIE) fleet is best positioned for DEN, while an army from Ruhr (RUH) can take HOL.
|
||||
|
||||
PARSABLE OUTPUT:
|
||||
{{
|
||||
"orders": [
|
||||
"A BER H",
|
||||
"A MUN H",
|
||||
"F KIE - DEN",
|
||||
"A RUH - HOL",
|
||||
"A SIL - WAR", // Opportunistic move towards Warsaw if Russia is weak or focused elsewhere
|
||||
"F HEL H" // Hold Heligoland Bight for naval defense
|
||||
]
|
||||
}}
|
||||
|
||||
Example 3: As Italy, Autumn 1902, after securing Tunis and trying to break into Austria, while also defending against a potential French naval attack. My units are A VEN, A ROM, F NAP, F ION, A APU. Austria has F TRI, A VIE, A BUD. France has F WES, F MAR.
|
||||
|
||||
Reasoning:
|
||||
- My primary goal is to take Trieste (TRI) from Austria. Army in Venice (VEN) will attack, supported by Army in Apulia (APU).
|
||||
- Fleet in Ionian Sea (ION) will support the attack on Trieste from the sea.
|
||||
- Army in Rome (ROM) will hold to protect the capital.
|
||||
- Fleet in Naples (NAP) will move to Tyrrhenian Sea (TYS) to defend against a potential French move from Western Mediterranean (WES) towards Naples or Rome.
|
||||
|
||||
PARSABLE OUTPUT:
|
||||
{{
|
||||
"orders": [
|
||||
"A VEN - TRI",
|
||||
"A APU S A VEN - TRI",
|
||||
"F ION S A VEN - TRI",
|
||||
"A ROM H",
|
||||
"F NAP - TYS"
|
||||
]
|
||||
}}
|
||||
|
||||
RESPOND WITH YOUR REASONING AND ORDERS (within PARSABLE OUTPUT) BELOW
|
||||
122
ai_diplomacy/prompts/order_instructions_retreat_phase.txt
Normal file
122
ai_diplomacy/prompts/order_instructions_retreat_phase.txt
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
PRIMARY OBJECTIVE
|
||||
Control 18 supply centers. Nothing else will do.
|
||||
|
||||
CRITICAL RULES
|
||||
1. Only use orders from the provided possible_orders list
|
||||
2. Support orders must match actual moves (e.g., 'A PAR S F PIC - ENG' needs 'F PIC - ENG')
|
||||
3. Build orders (build phase only):
|
||||
- Format: '[UnitType] [Location3LetterCode] B'
|
||||
- UnitType: 'A' (Army) or 'F' (Fleet)
|
||||
- Example: 'A PAR B', 'F LON B'
|
||||
- NOTE YOU CAN ONLY BUILD UNITS IN YOUR HOME CENTER THAT ARE EMPTY, THE ONES YOU STARTED WITH, IF YOU LOSE THESE YOU CANNOT BUILD UNITS SO THEY ARE CRITICAL
|
||||
Austria
|
||||
- Budapest
|
||||
- Trieste
|
||||
- Vienna
|
||||
England
|
||||
- Edinburgh
|
||||
- Liverpool
|
||||
- London
|
||||
France
|
||||
- Brest
|
||||
- Marseilles
|
||||
- Paris
|
||||
Germany
|
||||
- Berlin
|
||||
- Kiel
|
||||
- Munich
|
||||
Italy
|
||||
- Naples
|
||||
- Rome
|
||||
- Venice
|
||||
Russia
|
||||
- Moscow
|
||||
- Saint Petersburg
|
||||
- Sevastopol
|
||||
- Warsaw
|
||||
Turkey
|
||||
- Ankara
|
||||
- Constantinople
|
||||
- Smyrna
|
||||
|
||||
ORDER SUBMISSION PROCESS
|
||||
1. ANALYZE
|
||||
- Review game state, orders, messages, and other powers' motivations
|
||||
- Focus on expansion and capturing supply centers
|
||||
- Be aggressive, not passive
|
||||
- Take calculated risks for significant gains
|
||||
- Find alternative paths if blocked
|
||||
|
||||
2. REASON
|
||||
- Write out your strategic thinking
|
||||
- Explain goals and move choices
|
||||
- Consider supports and holds
|
||||
|
||||
3. FORMAT
|
||||
Return orders in this exact format:
|
||||
PARSABLE OUTPUT:
|
||||
{{
|
||||
"orders": ["order1", "order2", ...]
|
||||
}}
|
||||
|
||||
|
||||
4. Dual-coast provinces (STP, SPA, BUL):
|
||||
- Specify coast when needed: 'F [PROVINCE]/[COAST_CODE]'
|
||||
- Example: 'F STP/NC B', 'A MAR S F SPA/SC - WES'
|
||||
- Coast codes: NC (North), SC (South), EC (East), WC (West)
|
||||
5. All orders resolve simultaneously
|
||||
6. Submit orders only, no messages
|
||||
|
||||
EXAMPLES
|
||||
Reasoning:
|
||||
- Secure Burgundy against German threat
|
||||
- Mid-Atlantic move enables future convoys
|
||||
|
||||
PARSABLE OUTPUT:
|
||||
{{
|
||||
"orders": [
|
||||
"A PAR H",
|
||||
"A MAR - BUR",
|
||||
"F BRE - MAO"
|
||||
]
|
||||
}}
|
||||
|
||||
Example 2: As Germany, Spring 1901, aiming for a swift expansion into DEN and HOL, while also securing home centers.
|
||||
|
||||
Reasoning:
|
||||
- Denmark (DEN) and Holland (HOL) are key neutral centers for early German expansion.
|
||||
- Need to secure Berlin (BER) and Munich (MUN) against potential French or Russian incursions.
|
||||
- Kiel (KIE) fleet is best positioned for DEN, while an army from Ruhr (RUH) can take HOL.
|
||||
|
||||
PARSABLE OUTPUT:
|
||||
{{
|
||||
"orders": [
|
||||
"A BER H",
|
||||
"A MUN H",
|
||||
"F KIE - DEN",
|
||||
"A RUH - HOL",
|
||||
"A SIL - WAR", // Opportunistic move towards Warsaw if Russia is weak or focused elsewhere
|
||||
"F HEL H" // Hold Heligoland Bight for naval defense
|
||||
]
|
||||
}}
|
||||
|
||||
Example 3: As Italy, Autumn 1902, after securing Tunis and trying to break into Austria, while also defending against a potential French naval attack. My units are A VEN, A ROM, F NAP, F ION, A APU. Austria has F TRI, A VIE, A BUD. France has F WES, F MAR.
|
||||
|
||||
Reasoning:
|
||||
- My primary goal is to take Trieste (TRI) from Austria. Army in Venice (VEN) will attack, supported by Army in Apulia (APU).
|
||||
- Fleet in Ionian Sea (ION) will support the attack on Trieste from the sea.
|
||||
- Army in Rome (ROM) will hold to protect the capital.
|
||||
- Fleet in Naples (NAP) will move to Tyrrhenian Sea (TYS) to defend against a potential French move from Western Mediterranean (WES) towards Naples or Rome.
|
||||
|
||||
PARSABLE OUTPUT:
|
||||
{{
|
||||
"orders": [
|
||||
"A VEN - TRI",
|
||||
"A APU S A VEN - TRI",
|
||||
"F ION S A VEN - TRI",
|
||||
"A ROM H",
|
||||
"F NAP - TYS"
|
||||
]
|
||||
}}
|
||||
|
||||
RESPOND WITH YOUR REASONING AND ORDERS (within PARSABLE OUTPUT) BELOW
|
||||
3
ai_diplomacy/prompts_simple/austria_system_prompt.txt
Normal file
3
ai_diplomacy/prompts_simple/austria_system_prompt.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
You are playing as AUSTRIA in the game of Diplomacy.
|
||||
|
||||
Your Goal: Achieve world domination by controlling 18 supply centers.
|
||||
30
ai_diplomacy/prompts_simple/context_prompt.txt
Normal file
30
ai_diplomacy/prompts_simple/context_prompt.txt
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
Your power is {power_name}. The {current_phase} phase.
|
||||
|
||||
Power: {power_name}
|
||||
Phase: {current_phase}
|
||||
|
||||
# Your Power's Home Centers
|
||||
{home_centers}
|
||||
Note: You can only build units in your home centers if they are empty. If you lose control of a home center, you cannot build units there, so holding them is critical.
|
||||
|
||||
# Player Status
|
||||
Current Goals: {agent_goals}
|
||||
Relationships: {agent_relationships}
|
||||
|
||||
# Recent Private Diary Entries (Your inner thoughts and plans):
|
||||
{agent_private_diary}
|
||||
|
||||
# Game Map
|
||||
Unit Locations:
|
||||
{all_unit_locations}
|
||||
|
||||
Supply Centers Held:
|
||||
{all_supply_centers}
|
||||
|
||||
Possible Orders For {current_phase}
|
||||
{possible_orders}
|
||||
End Possible Orders
|
||||
|
||||
Messages This Round
|
||||
{messages_this_round}
|
||||
End Messages
|
||||
28
ai_diplomacy/prompts_simple/conversation_instructions.txt
Normal file
28
ai_diplomacy/prompts_simple/conversation_instructions.txt
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
NEGOTIATION MESSAGES
|
||||
|
||||
TASK
|
||||
Generate one or more (preferably several) strategic messages to advance your interests.
|
||||
Always prioritize responding to the messages in the "RECENT MESSAGES REQUIRING YOUR ATTENTION" section.
|
||||
Maintain consistent conversation threads (unless you are choosing to ignore).
|
||||
|
||||
RESPONSE FORMAT
|
||||
Return ONLY a single JSON array containing one or more message objects, remembering to properly escape strings:
|
||||
|
||||
Required JSON structure:
|
||||
[
|
||||
{
|
||||
"message_type": "global" or "private",
|
||||
"content": "Your message text"
|
||||
},
|
||||
...
|
||||
]
|
||||
|
||||
For private messages, also include the recipient:
|
||||
[
|
||||
{
|
||||
"message_type": "private",
|
||||
"recipient": "POWER_NAME",
|
||||
"content": "Your message text"
|
||||
},
|
||||
...
|
||||
]
|
||||
27
ai_diplomacy/prompts_simple/diary_consolidation_prompt.txt
Normal file
27
ai_diplomacy/prompts_simple/diary_consolidation_prompt.txt
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
DIARY CONSOLIDATION REQUEST
|
||||
Your Power: {power_name}
|
||||
|
||||
GAME CONTEXT
|
||||
You are playing Diplomacy, a strategic board game set in pre-WWI Europe. Seven powers compete for control by conquering supply centers. Victory requires 18 supply centers.
|
||||
|
||||
Key game mechanics:
|
||||
- Spring (S) and Fall (F) movement phases where armies/fleets move
|
||||
- Fall phases include builds/disbands based on supply center control
|
||||
- Units can support, convoy, or attack
|
||||
- All orders resolve simultaneously
|
||||
- Success often requires negotiated coordination with other powers
|
||||
|
||||
FULL DIARY HISTORY
|
||||
{full_diary_text}
|
||||
|
||||
TASK
|
||||
Create a comprehensive consolidated summary of the most important parts of this diary history. It will serve as your long-term memory.
|
||||
|
||||
Prioritize the following:
|
||||
1. **Recent Events, Goals & Intentions**
|
||||
2. **Long-Term Strategy:** Enduring goals, rivalries, and alliances that are still relevant.
|
||||
3. **Key Historical Events:** Major betrayals, decisive battles, and significant turning points that shape the current diplomatic landscape.
|
||||
4. **Important Notes:** Any notes you deem important from the history not already included.
|
||||
|
||||
RESPONSE FORMAT
|
||||
Return ONLY the consolidated summary text. Do not include JSON, formatting markers, or meta-commentary.
|
||||
3
ai_diplomacy/prompts_simple/england_system_prompt.txt
Normal file
3
ai_diplomacy/prompts_simple/england_system_prompt.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
You are playing as ENGLAND in the game of Diplomacy.
|
||||
|
||||
Your Goal: Achieve world domination by controlling 18 supply centers.
|
||||
30
ai_diplomacy/prompts_simple/few_shot_example.txt
Normal file
30
ai_diplomacy/prompts_simple/few_shot_example.txt
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
EXAMPLE GAME STATE
|
||||
Power: FRANCE
|
||||
Phase: S1901M
|
||||
Your Units: ['A PAR','F BRE']
|
||||
Possible Orders:
|
||||
PAR: ['A PAR H','A PAR - BUR','A PAR - GAS']
|
||||
BRE: ['F BRE H','F BRE - MAO']
|
||||
|
||||
PAST PHASE SUMMARIES
|
||||
- Your move A BUD -> SER bounced last time because Turkey also moved A SMY -> SER with support.
|
||||
- Your support F TRI S A BUD -> SER was wasted because F TRI was needed to block Ionian invasion.
|
||||
|
||||
THINKING PROCESS
|
||||
1. Consider enemy units, centers, and likely moves
|
||||
2. Review your units, centers, and strategic position
|
||||
3. Analyze recent conversations and phase summaries
|
||||
4. Evaluate public/private goals and reality of positions
|
||||
5. Choose best strategic moves from possible orders
|
||||
|
||||
Example thought process:
|
||||
- Germany might move to BUR with support - consider bounce or defend
|
||||
- Moving A PAR -> BUR is aggressive but strategic
|
||||
- F BRE -> MAO secures Atlantic expansion
|
||||
- Avoid contradictory or random supports
|
||||
|
||||
RESPONSE FORMAT
|
||||
PARSABLE OUTPUT:
|
||||
{{
|
||||
"orders": ["A PAR - BUR","F BRE - MAO"]
|
||||
}}
|
||||
3
ai_diplomacy/prompts_simple/france_system_prompt.txt
Normal file
3
ai_diplomacy/prompts_simple/france_system_prompt.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
You are playing as France in a game of Diplomacy.
|
||||
|
||||
Your Goal: Achieve world domination by controlling 18 supply centers.
|
||||
3
ai_diplomacy/prompts_simple/germany_system_prompt.txt
Normal file
3
ai_diplomacy/prompts_simple/germany_system_prompt.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
You are playing as GERMANY in the game of Diplomacy.
|
||||
|
||||
Your Goal: Achieve world domination by controlling 18 supply centers.
|
||||
3
ai_diplomacy/prompts_simple/italy_system_prompt.txt
Normal file
3
ai_diplomacy/prompts_simple/italy_system_prompt.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
You are playing as ITALY in the game of Diplomacy.
|
||||
|
||||
Your Goal: Achieve world domination by controlling 18 supply centers.
|
||||
93
ai_diplomacy/prompts_simple/negotiation_diary_prompt.txt
Normal file
93
ai_diplomacy/prompts_simple/negotiation_diary_prompt.txt
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
NEGOTIATION SUMMARY REQUEST
|
||||
Power: {power_name}
|
||||
Phase: {current_phase}
|
||||
|
||||
MESSAGES THIS ROUND
|
||||
{messages_this_round}
|
||||
{ignored_messages_context}
|
||||
|
||||
CURRENT STATUS
|
||||
Goals:
|
||||
{agent_goals}
|
||||
|
||||
Relationships:
|
||||
{agent_relationships}
|
||||
|
||||
Game State:
|
||||
{board_state_str}
|
||||
|
||||
TASK
|
||||
Analyze the negotiations, goals, relationships, and game state to:
|
||||
1. Summarize key outcomes and agreements
|
||||
2. State your strategic intent for {current_phase}
|
||||
3. Update relationships as needed (Enemy, Unfriendly, Neutral, Friendly, Ally)
|
||||
4. Note which powers are not responding to your messages and consider adjusting your approach
|
||||
|
||||
When powers ignore your messages, consider:
|
||||
- They may be intentionally avoiding commitment
|
||||
- They could be prioritizing other relationships
|
||||
- Your approach may need adjustment (more direct questions, different incentives)
|
||||
- Their silence might indicate hostility or indifference
|
||||
|
||||
RESPONSE FORMAT
|
||||
Return ONLY a JSON object with this structure:
|
||||
|
||||
{
|
||||
"negotiation_summary": "Key outcomes from negotiations",
|
||||
"intent": "Strategic intent for upcoming orders",
|
||||
"updated_relationships": {
|
||||
"POWER_NAME": "Enemy|Unfriendly|Neutral|Friendly|Ally"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Do not include any text outside the JSON. Reminder: If you need to quote something, only use single quotes in the actual messages so as not to interfere with the JSON structure.
|
||||
|
||||
EXAMPLES:
|
||||
|
||||
Scenario 1: As France, after discussing a joint move against Germany with England, while Italy seems to be posturing aggressively in Piedmont.
|
||||
|
||||
{
|
||||
"negotiation_summary": "Reached a tentative agreement with England to support their fleet into Belgium (BEL) if they support my army into Ruhr (RUH). Italy's messages are vague but their army in Piedmont (PIE) is concerning; they claim it's defensive against Austria but it also threatens Marseilles (MAR). Russia remains silent. Austria and Turkey are focused on each other.",
|
||||
"intent": "Secure Ruhr with English support. Hold Marseilles defensively. Probe Italy's intentions further. If England upholds their end, improve relations. If Italy moves on MAR, downgrade relations severely.",
|
||||
"updated_relationships": {
|
||||
"ENGLAND": "Friendly",
|
||||
"GERMANY": "Enemy",
|
||||
"ITALY": "Unfriendly",
|
||||
"AUSTRIA": "Neutral",
|
||||
"RUSSIA": "Neutral",
|
||||
"TURKEY": "Neutral"
|
||||
}
|
||||
}
|
||||
|
||||
Scenario 2: As Turkey, after Germany proposed an alliance against Russia, but France also offered a non-aggression pact and hinted at concerns about Austria.
|
||||
|
||||
{
|
||||
"negotiation_summary": "Germany is keen on an anti-Russian alliance, offering support into Sevastopol (SEV) if I attack. France proposed a mutual non-aggression pact and expressed worry about Austrian expansion in the Balkans, which aligns with my concerns. England is distant. Italy seems focused on France.",
|
||||
"intent": "Prioritize securing Black Sea (BLA) and consider options against Russia, but German support needs to be concrete. Maintain neutrality with France for now, as their non-aggression pact could be useful if Austria becomes a larger threat. Try to confirm German commitment before moving on Russia. Delay any aggressive moves against Austria until my position is stronger.",
|
||||
"updated_relationships": {
|
||||
"GERMANY": "Friendly",
|
||||
"RUSSIA": "Unfriendly",
|
||||
"FRANCE": "Neutral",
|
||||
"ENGLAND": "Neutral",
|
||||
"ITALY": "Neutral",
|
||||
"AUSTRIA": "Unfriendly"
|
||||
}
|
||||
}
|
||||
|
||||
Scenario 3: As England, when France hasn't responded to two alliance proposals and Russia is ignoring naval cooperation messages.
|
||||
|
||||
{
|
||||
"negotiation_summary": "France continues to ignore my alliance proposals regarding Belgium and the Channel, having not responded to messages in the last two phases. Russia similarly hasn't acknowledged my Baltic cooperation suggestions. Meanwhile, Germany actively engaged about Denmark. This silence from France and Russia is telling - they likely have other commitments or see me as a threat.",
|
||||
"intent": "Shift focus to Germany as primary partner given their responsiveness. Prepare defensive positions against potentially hostile France. Consider more aggressive Baltic moves since Russia seems uninterested in cooperation. May need to force France's hand with direct questions or public statements.",
|
||||
"updated_relationships": {
|
||||
"FRANCE": "Unfriendly",
|
||||
"GERMANY": "Friendly",
|
||||
"RUSSIA": "Unfriendly",
|
||||
"ITALY": "Neutral",
|
||||
"AUSTRIA": "Neutral",
|
||||
"TURKEY": "Neutral"
|
||||
}
|
||||
}
|
||||
|
||||
Reminder: If you need to quote something, only use single quotes in the actual messages so as not to interfere with the JSON structure.
|
||||
27
ai_diplomacy/prompts_simple/order_diary_prompt.txt
Normal file
27
ai_diplomacy/prompts_simple/order_diary_prompt.txt
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
ORDER DIARY ENTRY
|
||||
Power: {power_name}
|
||||
Phase: {current_phase}
|
||||
|
||||
ORDERS ISSUED
|
||||
{orders_list_str}
|
||||
|
||||
CURRENT STATUS
|
||||
Game State:
|
||||
{board_state_str}
|
||||
|
||||
Goals:
|
||||
{agent_goals}
|
||||
|
||||
Relationships:
|
||||
{agent_relationships}
|
||||
|
||||
TASK
|
||||
Write a concise diary note summarizing your orders.
|
||||
|
||||
RESPONSE FORMAT
|
||||
Return ONLY a JSON object with this structure:
|
||||
{
|
||||
"order_summary": "Brief summary of orders and strategic intent"
|
||||
}
|
||||
|
||||
Do not include any text outside the JSON.
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
# Primary Objective
|
||||
Control 18 supply centers. Nothing else will do.
|
||||
|
||||
# Critical Rules
|
||||
1. The possible orders section shows your units' allowed adjustment orders
|
||||
2. Dual-coast provinces (STP, SPA, BUL) require coast specification:
|
||||
- Format: 'F [PROVINCE]/[COAST]' where [COAST] = NC (North), SC (South), EC (East), or WC (West)
|
||||
- Example: 'F STP/NC B'
|
||||
- Only fleet builds need coast specification.
|
||||
|
||||
# Adjustment Phase Orders:
|
||||
You have two main order types in the adjustment phase:
|
||||
Build: '[UnitType] [Location] B'
|
||||
e.g. 'A PAR B', 'F LON B'
|
||||
Disband: '[UnitType] [Location] D'
|
||||
e.g. 'A PAR D', 'F LON D'
|
||||
|
||||
Your Task:
|
||||
1. Reason
|
||||
- comprehensive reasoning about your adjustment decisions
|
||||
2. Output Moves in JSON
|
||||
- return all build/disband orders needed
|
||||
|
||||
Respond with this exact format:
|
||||
|
||||
Reasoning:
|
||||
(Your reasoning goes here)
|
||||
|
||||
PARSABLE OUTPUT:
|
||||
{{
|
||||
"orders": ["order1", "order2", ...]
|
||||
}}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
# Primary Objective
|
||||
Control 18 supply centers. Nothing else will do.
|
||||
|
||||
# Critical Rules
|
||||
1. The possible orders section shows your units' allowed moves & supports of your own units.
|
||||
2. The possible orders section does *not* list possible supports for other powers' units; you can work these out yourself by looking at the units that are adjacent to your own.
|
||||
3. If your goal is to *take* a province, give exactly one move order on that province and any additional support from other units must be properly formatted support orders.
|
||||
4. Dual-coast provinces (STP, SPA, BUL) require coast specification:
|
||||
- Format: 'F [PROVINCE]/[COAST]' where [COAST] = NC (North), SC (South), EC (East), or WC (West)
|
||||
- Example: 'F SPA/SC - MAO'
|
||||
- Only fleets need coast specification.
|
||||
|
||||
Your Task:
|
||||
1. Reason
|
||||
- comprehensive reasoning about your move decisions
|
||||
2. Output Moves in JSON
|
||||
- aim to return an order for each of your units.
|
||||
|
||||
Respond with this exact format:
|
||||
|
||||
Reasoning:
|
||||
(Your reasoning goes here)
|
||||
|
||||
PARSABLE OUTPUT:
|
||||
{{
|
||||
"orders": ["order1", "order2", ...]
|
||||
}}
|
||||
30
ai_diplomacy/prompts_simple/order_instructions_retreat.txt
Normal file
30
ai_diplomacy/prompts_simple/order_instructions_retreat.txt
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Primary Objective
|
||||
Control 18 supply centers. Nothing else will do.
|
||||
|
||||
# Critical Rules
|
||||
1. The possible orders section shows where your dislodged units can retreat.
|
||||
2. Units cannot retreat to:
|
||||
- The province they were dislodged from
|
||||
- A province occupied after this turn's moves
|
||||
- A province where a standoff occurred
|
||||
3. If no valid retreat exists, the unit must disband.
|
||||
4. Dual-coast provinces (STP, SPA, BUL) require coast specification:
|
||||
- Format: 'F [PROVINCE]/[COAST]' where [COAST] = NC (North), SC (South), EC (East), or WC (West)
|
||||
- Example: 'F SPA/SC - MAO'
|
||||
- Only fleet retreat orders need coast specification.
|
||||
|
||||
Your Task:
|
||||
1. Reason
|
||||
- comprehensive reasoning about your retreat decisions
|
||||
2. Output Moves in JSON
|
||||
- provide a retreat or disband order for each dislodged unit
|
||||
|
||||
Respond with this exact format:
|
||||
|
||||
Reasoning:
|
||||
(Your reasoning goes here)
|
||||
|
||||
PARSABLE OUTPUT:
|
||||
{{
|
||||
"orders": ["order1", "order2", ...]
|
||||
}}
|
||||
42
ai_diplomacy/prompts_simple/phase_result_diary_prompt.txt
Normal file
42
ai_diplomacy/prompts_simple/phase_result_diary_prompt.txt
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
PHASE RESULT ANALYSIS
|
||||
Power: {power_name}
|
||||
Phase: {current_phase}
|
||||
|
||||
PHASE SUMMARY
|
||||
{phase_summary}
|
||||
|
||||
ALL POWERS' ORDERS THIS PHASE
|
||||
{all_orders_formatted}
|
||||
|
||||
YOUR NEGOTIATIONS THIS PHASE
|
||||
{your_negotiations}
|
||||
|
||||
YOUR RELATIONSHIPS BEFORE THIS PHASE
|
||||
{pre_phase_relationships}
|
||||
|
||||
YOUR GOALS
|
||||
{agent_goals}
|
||||
|
||||
YOUR ACTUAL ORDERS
|
||||
{your_actual_orders}
|
||||
|
||||
TASK
|
||||
Analyze what actually happened this phase compared to negotiations and expectations.
|
||||
|
||||
Consider:
|
||||
1. BETRAYALS: Who broke their promises? Did you break any promises?
|
||||
2. COLLABORATIONS: Which agreements were successfully executed?
|
||||
3. SURPRISES: What unexpected moves occurred?
|
||||
4. IMPACT: How did these events affect your strategic position?
|
||||
|
||||
Write a reflective diary entry (150-250 words) that:
|
||||
- Identifies key betrayals or successful collaborations
|
||||
- Assesses impact on your position
|
||||
- Updates your understanding of other powers' trustworthiness
|
||||
- Notes strategic lessons learned
|
||||
- Adjusts your perception of threats and opportunities
|
||||
|
||||
Focus on concrete events and their implications for your future strategy.
|
||||
|
||||
RESPONSE FORMAT
|
||||
Return ONLY a diary entry text. Do not include JSON or formatting markers.
|
||||
44
ai_diplomacy/prompts_simple/planning_instructions.txt
Normal file
44
ai_diplomacy/prompts_simple/planning_instructions.txt
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
STRATEGIC PLANNING
|
||||
|
||||
PRIMARY OBJECTIVE
|
||||
Capture 18 supply centers to win. Be aggressive and expansionist.
|
||||
- Prioritize capturing supply centers
|
||||
- Seize opportunities aggressively
|
||||
- Take calculated risks for significant gains
|
||||
- Find alternative paths if blocked
|
||||
- Avoid purely defensive postures
|
||||
|
||||
KEY CONSIDERATIONS
|
||||
1. Target Supply Centers
|
||||
- Which centers can you capture this phase?
|
||||
- Which centers should you target in future phases?
|
||||
|
||||
2. Success Requirements
|
||||
- What must happen for your moves to succeed?
|
||||
- How to prevent bounces?
|
||||
|
||||
3. Diplomatic Strategy
|
||||
- Which negotiations could help your moves succeed?
|
||||
- What deals or threats might be effective?
|
||||
- Consider alliances, deception, and concessions
|
||||
|
||||
4. Defense Assessment
|
||||
- Which of your centers might others target?
|
||||
- How can you protect vulnerable positions?
|
||||
|
||||
5. Diplomatic Protection
|
||||
- What negotiations could deter attacks?
|
||||
- How to mislead potential attackers?
|
||||
|
||||
TASK
|
||||
Write a detailed one-paragraph directive covering:
|
||||
- Supply centers to capture
|
||||
- How to capture them (orders, allies, deals)
|
||||
- Defensive considerations
|
||||
- Diplomatic approach (including potential deception)
|
||||
|
||||
|
||||
This directive will guide your future negotiations and orders.
|
||||
Be specific, strategic, and wary of deception from others.
|
||||
|
||||
RESPOND WITH YOUR DIRECTIVE BELOW
|
||||
3
ai_diplomacy/prompts_simple/russia_system_prompt.txt
Normal file
3
ai_diplomacy/prompts_simple/russia_system_prompt.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
You are playing as RUSSIA in the game of Diplomacy.
|
||||
|
||||
Your Goal: Achieve world domination by controlling 18 supply centers.
|
||||
139
ai_diplomacy/prompts_simple/state_update_prompt.txt
Normal file
139
ai_diplomacy/prompts_simple/state_update_prompt.txt
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
You are analyzing the results of a phase in Diplomacy for {power_name}.
|
||||
|
||||
GAME STATE
|
||||
Year: {current_year}
|
||||
Phase: {current_phase}
|
||||
Board State:
|
||||
{board_state_str}
|
||||
|
||||
PHASE SUMMARY ({current_phase}):
|
||||
{phase_summary}
|
||||
|
||||
CURRENT STATUS
|
||||
|
||||
Relationships with other powers ({other_powers}):
|
||||
{current_relationships}
|
||||
|
||||
TASK
|
||||
Analyze the phase summary and game state to update your relationships and goals.
|
||||
|
||||
IMPORTANT RULES
|
||||
1. Update relationships for ALL powers in {other_powers}
|
||||
2. Use ONLY these relationship values: Enemy, Unfriendly, Neutral, Friendly, Ally
|
||||
3. Make goals specific and actionable
|
||||
4. Base analysis on actual events, not assumptions
|
||||
5. Return ONLY valid JSON - no text before or after
|
||||
|
||||
Example Response Structure:
|
||||
{{
|
||||
"reasoning": "Brief explanation of your analysis",
|
||||
"relationships": {{
|
||||
"FRANCE": "Neutral",
|
||||
"GERMANY": "Unfriendly",
|
||||
"RUSSIA": "Enemy"
|
||||
}},
|
||||
"goals": [
|
||||
"Specific goal 1",
|
||||
"Specific goal 2"
|
||||
]
|
||||
}}
|
||||
|
||||
EXAMPLE SCENARIOS
|
||||
|
||||
1. After Cooperation:
|
||||
{{
|
||||
"reasoning": "Austria helped take Warsaw. Russia attacked Prussia.",
|
||||
"relationships": {{
|
||||
"AUSTRIA": "Ally",
|
||||
"RUSSIA": "Enemy",
|
||||
"TURKEY": "Neutral",
|
||||
"ITALY": "Unfriendly",
|
||||
"FRANCE": "Neutral"
|
||||
}},
|
||||
"goals": [
|
||||
"Hold Warsaw against Russia",
|
||||
"Keep Austrian alliance",
|
||||
"Block Italian expansion"
|
||||
]
|
||||
}}
|
||||
|
||||
2. After Betrayal:
|
||||
{{
|
||||
"reasoning": "France betrayed Channel agreement. Russia cooperating north.",
|
||||
"relationships": {{
|
||||
"FRANCE": "Enemy",
|
||||
"RUSSIA": "Friendly",
|
||||
"GERMANY": "Unfriendly",
|
||||
"ITALY": "Neutral",
|
||||
"AUSTRIA": "Neutral"
|
||||
}},
|
||||
"goals": [
|
||||
"Counter French fleet",
|
||||
"Secure Norway with Russia",
|
||||
"Build London fleet"
|
||||
]
|
||||
}}
|
||||
|
||||
3. After Builds:
|
||||
{{
|
||||
"reasoning": "Naval buildup in north. Russia threatening.",
|
||||
"relationships": {{
|
||||
"RUSSIA": "Enemy",
|
||||
"GERMANY": "Unfriendly",
|
||||
"FRANCE": "Neutral",
|
||||
"AUSTRIA": "Neutral",
|
||||
"TURKEY": "Neutral"
|
||||
}},
|
||||
"goals": [
|
||||
"Control northern waters",
|
||||
"Take Denmark first",
|
||||
"Find anti-Russia ally"
|
||||
]
|
||||
}}
|
||||
|
||||
4. As England, after a failed attack on Belgium (BEL) which was occupied by France, supported by Germany. Russia moved into Sweden (SWE) uncontested. Austria and Italy skirmished over Trieste (TRI). Turkey was quiet.
|
||||
|
||||
{{
|
||||
"reasoning": "My attack on Belgium was decisively repulsed due to Franco-German cooperation, marking them as a significant threat bloc. Russia's acquisition of Sweden is concerning for my northern position. The Austro-Italian conflict seems localized for now, and Turkey's inactivity makes them an unknown variable, potentially open to diplomacy.",
|
||||
"relationships": {{
|
||||
"FRANCE": "Enemy",
|
||||
"GERMANY": "Enemy",
|
||||
"RUSSIA": "Unfriendly",
|
||||
"AUSTRIA": "Neutral",
|
||||
"ITALY": "Neutral",
|
||||
"TURKEY": "Neutral"
|
||||
}},
|
||||
"goals": [
|
||||
"Break the Franco-German alliance or find a way to counter their combined strength.",
|
||||
"Secure North Sea (NTH) and prevent further Russian expansion towards Norway (NWY).",
|
||||
"Seek dialogue with Turkey or Austria/Italy to create a counterweight to the dominant bloc."
|
||||
]
|
||||
}}
|
||||
|
||||
5. As Russia, after successfully negotiating passage through Black Sea (BLA) with Turkey to take Rumania (RUM). Germany moved into Silesia (SIL), threatening Warsaw (WAR). Austria and France exchanged hostile messages but made no direct moves against each other. England built a new fleet in London (LON). Italy seems focused west.
|
||||
|
||||
{{
|
||||
"reasoning": "Securing Rumania via Turkish agreement is a major success. This improves my southern position and Turkey is now a provisional ally. Germany's move into Silesia is a direct and immediate threat to Warsaw; they are now my primary adversary. Austria and France are posturing, but their conflict doesn't directly affect me yet, keeping them neutral. England's new fleet is a long-term concern but not immediate. Italy's westward focus means they are not a current threat or priority.",
|
||||
"relationships": {{
|
||||
"GERMANY": "Enemy",
|
||||
"AUSTRIA": "Neutral",
|
||||
"TURKEY": "Ally",
|
||||
"ITALY": "Neutral",
|
||||
"FRANCE": "Neutral",
|
||||
"ENGLAND": "Unfriendly"
|
||||
}},
|
||||
"goals": [
|
||||
"Defend Warsaw against Germany, possibly by moving Lvn-War or Mos-War.",
|
||||
"Solidify alliance with Turkey, potentially coordinating further moves in the south or against Austria if Germany allies with them.",
|
||||
"Monitor English fleet movements and prepare for a potential northern threat in future turns.",
|
||||
"Explore diplomatic options with France or Austria to counter German aggression."
|
||||
]
|
||||
}}
|
||||
|
||||
JSON FORMAT
|
||||
Return a single JSON object with these exact keys:
|
||||
- reasoning: String explaining your updates
|
||||
- relationships: Object mapping power names to relationship values
|
||||
- goals: Array of specific goal strings
|
||||
|
||||
RETURN JSON BELOW ONLY (DO NOT PREPEND WITH ```json or ``` or any other text)
|
||||
0
ai_diplomacy/prompts_simple/system_prompt.txt
Normal file
0
ai_diplomacy/prompts_simple/system_prompt.txt
Normal file
3
ai_diplomacy/prompts_simple/turkey_system_prompt.txt
Normal file
3
ai_diplomacy/prompts_simple/turkey_system_prompt.txt
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
You are playing as TURKEY in the game of Diplomacy.
|
||||
|
||||
Your Goal: Achieve world domination by controlling 18 supply centers.
|
||||
|
|
@ -294,22 +294,37 @@ def normalize_and_compare_orders(
|
|||
|
||||
# Helper to load prompt text from file relative to the expected 'prompts' dir
|
||||
def load_prompt(filename: str, prompts_dir: Optional[str] = None) -> str:
|
||||
"""Helper to load prompt text from file"""
|
||||
if prompts_dir:
|
||||
"""
|
||||
Return the contents of *filename* while never joining paths twice.
|
||||
|
||||
Logic
|
||||
-----
|
||||
1. If *filename* is absolute → use it directly.
|
||||
2. Elif *filename* already contains a path component (e.g. 'x/y.txt')
|
||||
→ treat it as a relative path and use it directly.
|
||||
3. Elif *prompts_dir* is provided → join prompts_dir + filename.
|
||||
4. Otherwise → join the package’s default prompts dir.
|
||||
"""
|
||||
if os.path.isabs(filename): # rule 1
|
||||
prompt_path = filename
|
||||
elif os.path.dirname(filename): # rule 2 (has slash)
|
||||
prompt_path = filename # relative but already complete
|
||||
elif prompts_dir: # rule 3
|
||||
prompt_path = os.path.join(prompts_dir, filename)
|
||||
else:
|
||||
# Default behavior: relative to this file's location in the 'prompts' subdir
|
||||
prompt_path = os.path.join(os.path.dirname(__file__), 'prompts', filename)
|
||||
|
||||
else: # rule 4
|
||||
default_dir = os.path.join(os.path.dirname(__file__), "prompts")
|
||||
prompt_path = os.path.join(default_dir, filename)
|
||||
|
||||
try:
|
||||
with open(prompt_path, "r", encoding='utf-8') as f: # Added encoding
|
||||
return f.read().strip()
|
||||
with open(prompt_path, "r", encoding="utf-8") as fh:
|
||||
return fh.read().strip()
|
||||
except FileNotFoundError:
|
||||
logger.error(f"Prompt file not found: {prompt_path}")
|
||||
# Return an empty string or raise an error, depending on desired handling
|
||||
return ""
|
||||
|
||||
|
||||
|
||||
|
||||
# == New LLM Response Logging Function ==
|
||||
def log_llm_response(
|
||||
log_file_path: str,
|
||||
|
|
|
|||
|
|
@ -521,6 +521,7 @@ class StatisticalGameAnalyzer:
|
|||
"""Extract game-level features (placeholder for future implementation)."""
|
||||
|
||||
game_features = []
|
||||
game_scores = self._compute_game_scores(game_data)
|
||||
|
||||
for power in self.powers:
|
||||
features = {
|
||||
|
|
@ -568,6 +569,8 @@ class StatisticalGameAnalyzer:
|
|||
'overall_success_rate_percentage': 0.0,
|
||||
|
||||
}
|
||||
|
||||
features['game_score'] = game_scores.get(power)
|
||||
|
||||
# === CALCULATE FINAL STATE METRICS ===
|
||||
if game_data['phases']:
|
||||
|
|
@ -902,6 +905,88 @@ class StatisticalGameAnalyzer:
|
|||
return territory
|
||||
return unit_str
|
||||
|
||||
# ───────────────── Diplobench style game score ──────────────────
|
||||
@staticmethod
|
||||
def _year_from_phase(name: str) -> int | None:
|
||||
"""Return the 4-digit year embedded in a phase name such as 'F1903M'."""
|
||||
m = re.search(r'(\d{4})', name)
|
||||
return int(m.group(1)) if m else None
|
||||
|
||||
|
||||
def _phase_year(self, phases, idx: int) -> int | None:
|
||||
"""
|
||||
Like _year_from_phase but walks backward if a phase itself has no year
|
||||
(e.g. 'COMPLETED'). Returns None if nothing is found.
|
||||
"""
|
||||
for j in range(idx, -1, -1):
|
||||
y = self._year_from_phase(phases[j]["name"])
|
||||
if y is not None:
|
||||
return y
|
||||
return None
|
||||
|
||||
|
||||
def _compute_game_scores(self, game_data: dict) -> dict[str, int]:
|
||||
"""
|
||||
Return {power → game_score} using the Diplobench scheme.
|
||||
|
||||
max_turns = number of *years* actually played
|
||||
solo winner → max_turns + 17 + (max_turns − win_turn)
|
||||
full-length survivor (no solo) → max_turns + final_SCs
|
||||
everyone else → elimination_turn (or win_turn if someone else solos)
|
||||
"""
|
||||
phases = game_data.get("phases", [])
|
||||
if not phases:
|
||||
return {}
|
||||
|
||||
# years played
|
||||
years = [self._year_from_phase(p["name"]) for p in phases if self._year_from_phase(p["name"]) is not None]
|
||||
if not years:
|
||||
return {}
|
||||
start_year, last_year = years[0], years[-1]
|
||||
max_turns = last_year - start_year + 1
|
||||
|
||||
# solo winner?
|
||||
solo_winner = None
|
||||
win_turn = None
|
||||
last_state = phases[-1]["state"]
|
||||
for pwr, scs in last_state.get("centers", {}).items():
|
||||
if len(scs) >= 18:
|
||||
solo_winner = pwr
|
||||
# first phase in which 18+ SCs were reached
|
||||
for idx in range(len(phases) - 1, -1, -1):
|
||||
if len(phases[idx]["state"]["centers"].get(pwr, [])) >= 18:
|
||||
yr = self._phase_year(phases, idx)
|
||||
if yr is not None:
|
||||
win_turn = yr - start_year + 1
|
||||
break
|
||||
break
|
||||
|
||||
# elimination turn for every power
|
||||
elim_turn: dict[str, int | None] = {p: None for p in self.DIPLOMACY_POWERS}
|
||||
for idx, ph in enumerate(phases):
|
||||
yr = self._phase_year(phases, idx)
|
||||
if yr is None:
|
||||
continue
|
||||
turn = yr - start_year + 1
|
||||
for pwr in elim_turn:
|
||||
if elim_turn[pwr] is None and not ph["state"]["centers"].get(pwr):
|
||||
elim_turn[pwr] = turn
|
||||
|
||||
scores: dict[str, int] = {}
|
||||
for pwr in elim_turn:
|
||||
if pwr == solo_winner:
|
||||
scores[pwr] = max_turns + 17 + (max_turns - (win_turn or max_turns))
|
||||
elif solo_winner is not None: # somebody else soloed
|
||||
scores[pwr] = win_turn or max_turns
|
||||
else: # no solo
|
||||
if elim_turn[pwr] is None: # survived the distance
|
||||
final_scs = len(last_state.get("centers", {}).get(pwr, []))
|
||||
scores[pwr] = max_turns + final_scs
|
||||
else: # eliminated earlier
|
||||
scores[pwr] = elim_turn[pwr]
|
||||
return scores
|
||||
|
||||
|
||||
def _load_csv_as_dicts(self, csv_path: str) -> List[dict]:
|
||||
"""Load CSV file as list of dictionaries."""
|
||||
data = []
|
||||
|
|
@ -1034,7 +1119,10 @@ class StatisticalGameAnalyzer:
|
|||
'avg_military_units_per_phase',
|
||||
'percent_messages_to_allies_overall',
|
||||
'percent_messages_to_enemies_overall',
|
||||
'percent_global_vs_private_overall'
|
||||
'percent_global_vs_private_overall',
|
||||
|
||||
# === Diplobench style single scalar game score ===
|
||||
'game_score',
|
||||
]
|
||||
|
||||
# Ensure all actual fields are included
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ def _add_experiment_flags(p: argparse.ArgumentParser) -> None:
|
|||
p.add_argument(
|
||||
"--analysis_modules",
|
||||
type=str,
|
||||
default="summary",
|
||||
default="summary,statistical_game_analysis",
|
||||
help=(
|
||||
"Comma-separated list of analysis module names to execute after all "
|
||||
"runs finish. Modules are imported from "
|
||||
|
|
|
|||
214
experiment_runner/analysis/statistical_game_analysis.py
Normal file
214
experiment_runner/analysis/statistical_game_analysis.py
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
"""
|
||||
experiment_runner.analysis.statistical_game_analysis
|
||||
----------------------------------------------------
|
||||
|
||||
Runs the Statistical Game Analyzer to create per-run / combined CSVs,
|
||||
then produces a suite of PNG plots:
|
||||
|
||||
analysis/
|
||||
└── statistical_game_analysis/
|
||||
├── individual/
|
||||
│ ├── run_00000_game_analysis.csv
|
||||
│ └── …
|
||||
└── plots/
|
||||
├── game/
|
||||
│ ├── final_supply_centers_owned.png
|
||||
│ └── …
|
||||
├── game_summary_heatmap.png
|
||||
└── phase/
|
||||
├── supply_centers_owned_count.png
|
||||
└── …
|
||||
|
||||
Complies with experiment-runner’s plug-in contract:
|
||||
run(experiment_dir: pathlib.Path, ctx: dict) -> None
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import re
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
import matplotlib.pyplot as plt
|
||||
import pandas as pd
|
||||
import seaborn as sns
|
||||
|
||||
# third-party analyser that creates the CSVs
|
||||
from analysis.statistical_game_analysis import StatisticalGameAnalyzer # type: ignore
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# ───────────────────────── helpers ──────────────────────────
|
||||
_SEASON_ORDER = {"S": 0, "F": 1, "W": 2, "A": 3}
|
||||
|
||||
|
||||
def _sanitize(name: str) -> str:
|
||||
return re.sub(r"[^\w\-\.]", "_", name)
|
||||
|
||||
|
||||
def _discover_csvs(individual_dir: Path, pattern: str) -> List[Path]:
|
||||
return sorted(individual_dir.glob(pattern))
|
||||
|
||||
|
||||
def _numeric_columns(df: pd.DataFrame, extra_exclude: set[str] | None = None) -> List[str]:
|
||||
exclude = {
|
||||
"game_id",
|
||||
"llm_model",
|
||||
"power_name",
|
||||
"game_phase",
|
||||
"analyzed_response_type",
|
||||
}
|
||||
if extra_exclude:
|
||||
exclude |= extra_exclude
|
||||
return [c for c in df.select_dtypes("number").columns if c not in exclude]
|
||||
|
||||
|
||||
def _phase_sort_key(ph: str) -> tuple[int, int]:
|
||||
"""Convert 'S1901M' → (1901, 0)."""
|
||||
year = int(ph[1:5]) if len(ph) >= 5 and ph[1:5].isdigit() else 0
|
||||
season = _SEASON_ORDER.get(ph[0], 9)
|
||||
return year, season
|
||||
|
||||
|
||||
def _phase_index(series: pd.Series) -> pd.Series:
|
||||
uniq = sorted(series.unique(), key=_phase_sort_key)
|
||||
mapping = {ph: i for i, ph in enumerate(uniq)}
|
||||
return series.map(mapping)
|
||||
|
||||
|
||||
# ───────────────────────── plots ────────────────────────────
|
||||
def _plot_game_level(all_games: pd.DataFrame, plot_dir: Path) -> None:
|
||||
"""
|
||||
• Box-plots per metric (hue = power, legend removed).
|
||||
• Z-score heat-map: powers × metrics, colour-coded by relative standing.
|
||||
"""
|
||||
plot_dir.mkdir(parents=True, exist_ok=True)
|
||||
num_cols = _numeric_columns(all_games)
|
||||
|
||||
# ── per-metric box-plots ──────────────────────────────────────────
|
||||
for col in num_cols:
|
||||
fig, ax = plt.subplots(figsize=(8, 6))
|
||||
sns.boxplot(
|
||||
data=all_games,
|
||||
x="power_name",
|
||||
y=col,
|
||||
hue="power_name",
|
||||
palette="pastel",
|
||||
dodge=False,
|
||||
ax=ax,
|
||||
)
|
||||
leg = ax.get_legend()
|
||||
if leg is not None:
|
||||
leg.remove()
|
||||
ax.set_title(col.replace("_", " ").title())
|
||||
fig.tight_layout()
|
||||
fig.savefig(plot_dir / f"{_sanitize(col)}.png", dpi=140)
|
||||
plt.close(fig)
|
||||
|
||||
# ── summary heat-map (column-wise z-scores) ───────────────────────
|
||||
# 1) mean across runs 2) z-score each column
|
||||
summary = all_games.groupby("power_name")[num_cols].mean().sort_index()
|
||||
zscores = summary.apply(lambda col: (col - col.mean()) / col.std(ddof=0), axis=0)
|
||||
|
||||
fig_w = max(6, len(num_cols) * 0.45 + 2)
|
||||
fig, ax = plt.subplots(figsize=(fig_w, 6))
|
||||
sns.heatmap(
|
||||
zscores,
|
||||
cmap="coolwarm",
|
||||
center=0,
|
||||
linewidths=0.4,
|
||||
annot=True,
|
||||
fmt=".2f",
|
||||
ax=ax,
|
||||
)
|
||||
ax.set_title("Relative Standing (column-wise z-score)")
|
||||
ax.set_ylabel("Power")
|
||||
fig.tight_layout()
|
||||
fig.savefig(plot_dir.parent / "game_summary_zscore_heatmap.png", dpi=160)
|
||||
plt.close(fig)
|
||||
|
||||
|
||||
|
||||
|
||||
def _plot_phase_level(all_phase: pd.DataFrame, plot_dir: Path) -> None:
|
||||
if all_phase.empty:
|
||||
return
|
||||
plot_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
if "phase_index" not in all_phase.columns:
|
||||
all_phase["phase_index"] = _phase_index(all_phase["game_phase"])
|
||||
|
||||
num_cols = _numeric_columns(all_phase)
|
||||
|
||||
agg = (
|
||||
all_phase
|
||||
.groupby(["phase_index", "game_phase", "power_name"], as_index=False)[num_cols]
|
||||
.mean()
|
||||
)
|
||||
|
||||
n_phases = agg["phase_index"].nunique()
|
||||
fig_base_width = max(8, n_phases * 0.1 + 4) # 0.45 in per label + padding
|
||||
|
||||
for col in num_cols:
|
||||
plt.figure(figsize=(fig_base_width, 6))
|
||||
sns.lineplot(
|
||||
data=agg,
|
||||
x="phase_index",
|
||||
y=col,
|
||||
hue="power_name",
|
||||
marker="o",
|
||||
)
|
||||
|
||||
phases_sorted = (
|
||||
agg.drop_duplicates("phase_index")
|
||||
.sort_values("phase_index")[["phase_index", "game_phase"]]
|
||||
)
|
||||
plt.xticks(
|
||||
phases_sorted["phase_index"],
|
||||
phases_sorted["game_phase"],
|
||||
rotation=90,
|
||||
fontsize=8,
|
||||
)
|
||||
plt.xlabel("Game Phase")
|
||||
plt.title(col.replace("_", " ").title())
|
||||
plt.tight_layout()
|
||||
plt.savefig(plot_dir / f"{_sanitize(col)}.png", dpi=140)
|
||||
plt.close()
|
||||
|
||||
|
||||
# ───────────────────────── entry-point ─────────────────────────
|
||||
def run(experiment_dir: Path, ctx: dict) -> None: # pylint: disable=unused-argument
|
||||
root = experiment_dir / "analysis" / "statistical_game_analysis"
|
||||
indiv_dir = root / "individual"
|
||||
plots_root = root / "plots"
|
||||
|
||||
# 1. (re)generate CSVs
|
||||
try:
|
||||
StatisticalGameAnalyzer().analyze_multiple_folders(
|
||||
str(experiment_dir / "runs"), str(root)
|
||||
)
|
||||
log.info("statistical_game_analysis: CSV generation complete")
|
||||
except Exception as exc: # noqa: broad-except
|
||||
log.exception("statistical_game_analysis: CSV generation failed – %s", exc)
|
||||
return
|
||||
|
||||
# 2. load CSVs
|
||||
game_csvs = _discover_csvs(indiv_dir, "*_game_analysis.csv")
|
||||
phase_csvs = _discover_csvs(indiv_dir, "*_phase_analysis.csv")
|
||||
|
||||
if not game_csvs:
|
||||
log.warning("statistical_game_analysis: no *_game_analysis.csv found")
|
||||
return
|
||||
|
||||
all_game_df = pd.concat((pd.read_csv(p) for p in game_csvs), ignore_index=True)
|
||||
all_phase_df = (
|
||||
pd.concat((pd.read_csv(p) for p in phase_csvs), ignore_index=True)
|
||||
if phase_csvs else pd.DataFrame()
|
||||
)
|
||||
|
||||
# 3. plots
|
||||
sns.set_theme(style="whitegrid")
|
||||
_plot_game_level(all_game_df, plots_root / "game")
|
||||
_plot_phase_level(all_phase_df, plots_root / "phase")
|
||||
|
||||
log.info("statistical_game_analysis: plots written → %s", plots_root)
|
||||
35
lm_game.py
35
lm_game.py
|
|
@ -9,6 +9,7 @@ from collections import defaultdict
|
|||
from argparse import Namespace
|
||||
from typing import Dict
|
||||
import shutil
|
||||
import sys
|
||||
|
||||
# Suppress Gemini/PaLM gRPC warnings
|
||||
os.environ["GRPC_PYTHON_LOG_LEVEL"] = "40" # ERROR level only
|
||||
|
|
@ -24,7 +25,7 @@ from ai_diplomacy.negotiations import conduct_negotiations
|
|||
from ai_diplomacy.planning import planning_phase
|
||||
from ai_diplomacy.game_history import GameHistory
|
||||
from ai_diplomacy.agent import DiplomacyAgent
|
||||
import ai_diplomacy.narrative
|
||||
# import ai_diplomacy.narrative
|
||||
from ai_diplomacy.game_logic import (
|
||||
save_game_state,
|
||||
load_game_state,
|
||||
|
|
@ -45,6 +46,14 @@ logging.getLogger("httpx").setLevel(logging.WARNING)
|
|||
# logging.getLogger("root").setLevel(logging.WARNING) # Assuming root handles AFC
|
||||
|
||||
|
||||
def _str2bool(v: str) -> bool:
|
||||
v = str(v).lower()
|
||||
if v in {"1", "true", "t", "yes", "y"}:
|
||||
return True
|
||||
if v in {"0", "false", "f", "no", "n"}:
|
||||
return False
|
||||
raise argparse.ArgumentTypeError(f"Boolean value expected, got '{v}'")
|
||||
|
||||
def parse_arguments():
|
||||
parser = argparse.ArgumentParser(
|
||||
description="Run a Diplomacy game simulation with configurable parameters."
|
||||
|
|
@ -129,6 +138,16 @@ def parse_arguments():
|
|||
default=None,
|
||||
help="Path to the directory containing prompt files. Defaults to the packaged prompts directory."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--simple_prompts",
|
||||
type=_str2bool,
|
||||
nargs="?",
|
||||
const=True,
|
||||
default=False,
|
||||
help=(
|
||||
"When true (1 / true / yes) the engine switches to simpler prompts which low-midrange models handle better."
|
||||
),
|
||||
)
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
|
|
@ -137,6 +156,17 @@ async def main():
|
|||
args = parse_arguments()
|
||||
start_whole = time.time()
|
||||
|
||||
# honour --simple_prompts before anything else needs it
|
||||
if args.simple_prompts:
|
||||
os.environ["SIMPLE_PROMPTS"] = "1" # read by prompt_constructor
|
||||
if args.prompts_dir is None:
|
||||
pkg_root = os.path.join(os.path.dirname(__file__), "ai_diplomacy")
|
||||
args.prompts_dir = os.path.join(pkg_root, "prompts_simple")
|
||||
|
||||
if args.prompts_dir and not os.path.isdir(args.prompts_dir):
|
||||
print(f"ERROR: Prompts directory not found: {args.prompts_dir}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# --- 1. Determine Run Directory and Mode (New vs. Resume) ---
|
||||
run_dir = args.run_dir
|
||||
is_resuming = False
|
||||
|
|
@ -197,7 +227,8 @@ async def main():
|
|||
if is_resuming:
|
||||
try:
|
||||
# When resuming, we load the state and also the config from the last saved phase.
|
||||
game, agents, game_history, loaded_run_config = load_game_state(run_dir, game_file_name, args.resume_from_phase)
|
||||
game, agents, game_history, loaded_run_config = load_game_state(run_dir, game_file_name, run_config, args.resume_from_phase)
|
||||
|
||||
if loaded_run_config:
|
||||
# Use the saved config, but allow current CLI args to override control-flow parameters
|
||||
run_config = loaded_run_config
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue