Spaces:
Running
Running
| """Deterministic, scenario-scoped game knowledge for the agent. | |
| Frontier models have patchy Red Alert knowledge; a model losing | |
| because it doesn't know `proc` is the refinery is a *knowledge* | |
| confound, not a reasoning signal. So we inject — identically for every | |
| model, no tool, no turn cost — exactly the knowledge needed for the | |
| scenario at hand: | |
| * a glossary of only the actor codes actually present, | |
| * the fixed game model (stances / coordinates / fog), | |
| * a plain-language translation of THIS scenario's machine win/fail | |
| condition (the model is judged on reasoning toward a *known* goal, | |
| not on divining the success criterion). | |
| Pure functions, fully unit-testable. | |
| """ | |
| from __future__ import annotations | |
| from typing import Any | |
| # Short, accurate glossary. Keep terse — this is reference, not prose. | |
| ACTOR_GLOSSARY: dict[str, str] = { | |
| # infantry | |
| "e1": "rifle infantry (cheap, anti-infantry)", | |
| "e2": "grenadier infantry", | |
| "e3": "rocket soldier (anti-vehicle/anti-air)", | |
| "e6": "engineer (captures/repairs buildings; unarmed)", | |
| "dog": "attack dog (fast, anti-infantry only)", | |
| "medi": "field medic (heals nearby infantry; unarmed)", | |
| "spy": "spy (infiltrates enemy buildings)", | |
| "thf": "thief (steals enemy credits)", | |
| "tanya": "Tanya (Allied hero commando; pistol vs infantry, " | |
| "C4 vs buildings)", | |
| # vehicles | |
| "jeep": "ranger/jeep (fast, light, ideal scout)", | |
| "1tnk": "light tank (medium armour, anti-vehicle)", | |
| "2tnk": "heavy/medium tank (main battle tank)", | |
| "3tnk": "heavy tank (Soviet, strong armour)", | |
| "4tnk": "mammoth tank (very heavy, dual cannon+missile)", | |
| "apc": "armoured personnel carrier (transports infantry)", | |
| "arty": "artillery (long-range, fragile)", | |
| "harv": "ore harvester (gathers ore for credits; unarmed)", | |
| "mcv": "mobile construction vehicle (deploys into a fact)", | |
| "lst": "landing craft / transport", | |
| # naval | |
| "dd": "destroyer (Allied naval; ranged shore bombardment)", | |
| # aircraft | |
| "heli": "Hind attack helicopter (flies over terrain/buildings; " | |
| "anti-ground)", | |
| # buildings | |
| "fact": "construction yard (builds structures; LOSS-CRITICAL base)", | |
| "powr": "power plant (supplies power to structures)", | |
| "apwr": "advanced power plant", | |
| "proc": "ore refinery (turns ore into credits; economy core)", | |
| "barr": "Soviet barracks (trains infantry)", | |
| "tent": "Allied barracks (trains infantry)", | |
| "weap": "war factory (builds vehicles)", | |
| "fix": "service depot (repairs vehicles)", | |
| "dome": "radar dome (reveals map / tech prerequisite)", | |
| "silo": "ore silo (stores surplus ore)", | |
| "mine": "ore mine (resource node harvesters gather from)", | |
| "gun": "gun turret (anti-vehicle base defence)", | |
| "pbox": "pillbox (anti-infantry base defence)", | |
| "brik": "concrete wall (inert obstacle; no weapon — channels but does not kill)", | |
| "tsla": "Tesla coil (powerful anti-everything defence)", | |
| "sam": "SAM site (anti-air defence)", | |
| "mslo": "missile silo (Soviet superweapon; launches a single " | |
| "high-damage nuke at a target cell after a charge cycle)", | |
| } | |
| GAME_MODEL = ( | |
| "GAME MODEL:\n" | |
| "- Map cells are (x,y); x grows east, y grows south. Tools take " | |
| "integer cell coords.\n" | |
| "- Fog of war: you only see near your own units; 'explored' stays " | |
| "dim once revealed but enemies there may have moved/changed. Scout " | |
| "to gain information.\n" | |
| "- Stances: 0=HoldFire (never fire), 1=ReturnFire (only if " | |
| "attacked), 2=Defend (engage nearby threats, hold position), " | |
| "3=AttackAnything (auto-engage on sight).\n" | |
| "- move_units auto-fires opportunistically en route; attack_move " | |
| "advances while engaging; attack_unit focus-fires one target; " | |
| "stop cancels current orders; observe passes the turn.\n" | |
| ) | |
| TECH_NOTE = ( | |
| "TECH TREE: production needs prerequisites — a barracks " | |
| "(tent=Allied / barr=Soviet) before infantry; a war factory (weap) " | |
| "before vehicles; power (powr) sufficient for what's built; " | |
| "advanced defences (tsla) need power and usually a tech building. " | |
| "Building unnecessary structures wastes limited credits.\n" | |
| ) | |
| def actor_codes(scenario: Any) -> set[str]: | |
| """Lowercase actor-type codes present in a compiled scenario.""" | |
| out: set[str] = set() | |
| for a in getattr(scenario, "actors", None) or []: | |
| t = getattr(a, "type", None) if not isinstance(a, dict) else a.get("type") | |
| if t: | |
| out.add(str(t).lower()) | |
| return out | |
| def _condition_codes(node: Any) -> set[str]: | |
| """Actor codes named inside a win/fail predicate tree (production | |
| targets like e3/tsla that are NOT pre-placed actors but the model | |
| is asked to build/destroy — they must still be glossary-explained).""" | |
| out: set[str] = set() | |
| if node is None: | |
| return out | |
| if not isinstance(node, dict): | |
| node = dict(getattr(node, "__pydantic_extra__", {}) or {}) | |
| for k, v in node.items(): | |
| if k in ("all_of", "any_of"): | |
| for c in v: | |
| out |= _condition_codes(c) | |
| elif k == "not": | |
| out |= _condition_codes(v) | |
| elif isinstance(v, dict): | |
| if v.get("type"): | |
| out.add(str(v["type"]).lower()) | |
| for t in v.get("types", []) or []: | |
| out.add(str(t).lower()) | |
| elif k == "has_building" and isinstance(v, str): | |
| out.add(v.lower()) | |
| return out | |
| def scenario_primer(compiled: Any) -> str: | |
| """The knowledge block for THIS scenario: glossary of present | |
| codes + the fixed game model (+ tech note only if the scenario | |
| actually allows production).""" | |
| sc = compiled.scenario | |
| codes = set(actor_codes(sc)) | |
| codes |= _condition_codes(getattr(compiled, "win_condition", None)) | |
| codes |= _condition_codes(getattr(compiled, "fail_condition", None)) | |
| codes = sorted(codes) | |
| lines = ["GAME KNOWLEDGE (Command & Conquer: Red Alert)"] | |
| if codes: | |
| lines.append("Units/buildings in this scenario:") | |
| for c in codes: | |
| lines.append(f" {c} = {ACTOR_GLOSSARY.get(c, 'unknown actor')}") | |
| lines.append("") | |
| lines.append(GAME_MODEL) | |
| tools = set(getattr(sc, "tools", None) or []) | |
| if {"build", "place_building"} & tools: | |
| lines.append(TECH_NOTE) | |
| return "\n".join(lines).strip() | |
| # ── win/fail predicate → plain language ──────────────────────────────────── | |
| _REGION_KEYS = ( | |
| "reach_region", | |
| "units_in_region_gte", | |
| "units_of_type_in_region_gte", | |
| "all_units_in_region", | |
| ) | |
| def _region(x: Any, coords: str = "exact") -> str: | |
| if isinstance(x, dict): | |
| if coords == "relative": | |
| # Disclose only the authored compass label — the model | |
| # must localize the target on the minimap itself. Fall | |
| # back to coords if a region lacks a label (authoring bug | |
| # made visible rather than silently leaking numbers). | |
| lbl = x.get("label") | |
| if lbl: | |
| return str(lbl) | |
| return f"region ({x.get('x')},{x.get('y')}) r={x.get('radius', 3)}" | |
| return str(x) | |
| # key -> lazy formatter (only the matched one runs, so a scalar v never | |
| # hits a .get meant for a dict-valued predicate). | |
| _PHRASES: dict[str, Any] = { | |
| "within_ticks": lambda v: f"before game tick {v}", | |
| "after_ticks": lambda v: f"not before game tick {v}", | |
| "units_killed_gte": lambda v: f"destroy ≥{v} enemy units", | |
| "units_lost_lte": lambda v: f"lose ≤{v} of your own units", | |
| "explored_pct_gte": lambda v: f"reveal ≥{v}% of the map", | |
| "enemies_discovered_gte": lambda v: f"spot ≥{v} enemy units", | |
| "buildings_discovered_gte": lambda v: f"spot ≥{v} enemy buildings", | |
| # Region phrases are handled by _REGION_PHRASES (coords-aware); | |
| # these fallbacks keep the exact default for any direct use. | |
| "reach_region": lambda v: f"get a unit into {_region(v)}", | |
| "units_in_region_gte": lambda v: ( | |
| f"get ≥{(v if isinstance(v, dict) else {}).get('n', 1)} " | |
| f"units into {_region(v)}" | |
| ), | |
| "units_of_type_in_region_gte": lambda v: ( | |
| f"get ≥{(v if isinstance(v, dict) else {}).get('n', 1)} " | |
| f"'{(v if isinstance(v, dict) else {}).get('type')}' " | |
| f"units into {_region(v)}" | |
| ), | |
| "all_units_in_region": lambda v: f"get EVERY unit into {_region(v)}", | |
| "own_units_gte": lambda v: f"keep ≥{v} units alive", | |
| "cash_gte": lambda v: f"hold ≥{v} credits", | |
| "resources_gte": lambda v: f"hold ≥{v} stored ore", | |
| "economy_value_gte": lambda v: f"reach economy value ≥{v} (cash+ore)", | |
| "power_surplus_gte": lambda v: f"keep power surplus ≥{v}", | |
| "power_provided_gte": lambda v: f"keep gross power provided ≥{v}", | |
| "has_building": lambda v: f"own a '{v}'", | |
| "buildings_owned_gte": lambda v: f"own ≥{v} distinct building types", | |
| "building_total_gte": lambda v: f"own ≥{v} buildings total", | |
| "building_count_gte": lambda v: f"own ≥{(v or {}).get('n', 1)} " | |
| f"'{(v or {}).get('type')}' building(s)", | |
| "building_in_region": lambda v: f"have {(v or {}).get('count', 1)} " | |
| f"building(s) near ({(v or {}).get('x')},{(v or {}).get('y')})", | |
| "unit_type_count_eq": lambda v: f"have EXACTLY {(v or {}).get('n')} " | |
| f"'{(v or {}).get('type')}' (no more, no fewer)", | |
| "unit_type_count_gte": lambda v: f"have ≥{(v or {}).get('n')} " | |
| f"'{(v or {}).get('type')}'", | |
| "enemy_buildings_destroyed_gte": lambda v: f"destroy ≥{v} enemy buildings", | |
| "enemy_key_buildings_destroyed": lambda v: "destroy the enemy " | |
| + "+".join(v.get("types", []) if isinstance(v, dict) else v), | |
| "enemy_key_buildings_destroyed_in_region": lambda v: ( | |
| "destroy the enemy " | |
| + "+".join((v or {}).get("types", [])) | |
| + f" at the base near ({(v or {}).get('x')},{(v or {}).get('y')})" | |
| ), | |
| "tool_violations_gte": lambda v: ( | |
| "you used a forbidden tool (instant fail)" | |
| if int(v) <= 1 | |
| else f"you used a forbidden tool ≥{v} times" | |
| ), | |
| } | |
| # Coords-aware region phrasing (honours objective_coords). | |
| _REGION_PHRASES: dict[str, Any] = { | |
| "reach_region": lambda v, c: f"get a unit into {_region(v, c)}", | |
| "units_in_region_gte": lambda v, c: ( | |
| f"get ≥{(v if isinstance(v, dict) else {}).get('n', 1)} " | |
| f"units into {_region(v, c)}" | |
| ), | |
| "units_of_type_in_region_gte": lambda v, c: ( | |
| f"get ≥{(v if isinstance(v, dict) else {}).get('n', 1)} " | |
| f"'{(v if isinstance(v, dict) else {}).get('type')}' " | |
| f"units into {_region(v, c)}" | |
| ), | |
| "all_units_in_region": lambda v, c: f"get EVERY unit into {_region(v, c)}", | |
| "waypoint_sequence": lambda v, c: ( | |
| "reach these waypoints IN ORDER (no skipping, no idling): " | |
| + " → then → ".join( | |
| _region(p, c) | |
| for p in ((v.get("points") or []) if isinstance(v, dict) else []) | |
| ) | |
| ), | |
| } | |
| def _leaf_phrase(key: str, v: Any, coords: str = "exact") -> str: | |
| if key in _REGION_PHRASES: | |
| return _REGION_PHRASES[key](v, coords) | |
| fn = _PHRASES.get(key) | |
| return fn(v) if fn else f"{key}={v}" | |
| def _describe(node: Any, join: str = " AND ", coords: str = "exact") -> str: | |
| if node is None: | |
| return "" | |
| if not isinstance(node, dict): | |
| node = dict(getattr(node, "__pydantic_extra__", {}) or {}) | |
| if "all_of" in node: | |
| return join.join(_describe(c, coords=coords) for c in node["all_of"]) | |
| if "any_of" in node: | |
| return "(" + " OR ".join( | |
| _describe(c, coords=coords) for c in node["any_of"] | |
| ) + ")" | |
| if "not" in node: | |
| inner = node["not"] | |
| inner_d = inner if isinstance(inner, dict) else dict( | |
| getattr(inner, "__pydantic_extra__", {}) or {} | |
| ) | |
| # Common fail form {not: {own_units_gte: N}} reads far clearer | |
| # as a plain loss statement than a double negative. | |
| if set(inner_d) == {"own_units_gte"}: | |
| n = inner_d["own_units_gte"] | |
| return ( | |
| "your whole force is destroyed" if int(n) <= 1 | |
| else f"fewer than {n} of your units remain" | |
| ) | |
| return "NOT (" + _describe(inner, coords=coords) + ")" | |
| if "then" in node: | |
| # The happened-before composite. Read each clause as a stage | |
| # in an enforced ordered chain. | |
| v = node["then"] or {} | |
| clauses = v.get("clauses") or [] | |
| if not clauses: | |
| return "(empty then:)" | |
| return ( | |
| "in this exact order: " | |
| + " → THEN → ".join( | |
| _describe(c, coords=coords) for c in clauses | |
| ) | |
| ) | |
| return join.join(_leaf_phrase(k, v, coords) for k, v in node.items()) | |
| def objective_brief(description: str, win_condition: Any, | |
| fail_condition: Any, max_turns: int, | |
| objective_coords: str = "exact") -> str: | |
| """Plain-language objective the model sees every turn: the scenario | |
| prose PLUS the exact machine win/fail criteria (so success is a | |
| known target, not a guess).""" | |
| parts = [] | |
| if description: | |
| parts.append(description.strip()) | |
| win = _describe(win_condition, coords=objective_coords) | |
| parts.append(f"WIN WHEN: {win}." if win else "WIN: (none defined)") | |
| fail = _describe(fail_condition, join=" AND ", coords=objective_coords) | |
| if fail: | |
| parts.append(f"YOU LOSE IF: {fail}.") | |
| parts.append( | |
| f"You have at most {max_turns} decision turns; acting " | |
| "decisively and early matters." | |
| ) | |
| return "\n".join(parts) | |
| # ── Chinese (ZH) objective generation ───────────────────────────────────── | |
| # Generates Chinese WIN/LOSE conditions directly from the structured | |
| # predicates, avoiding the broken find-and-replace approach. | |
| _DIRECTION_ZH = { | |
| "NORTH": "北方", "SOUTH": "南方", "EAST": "东方", "WEST": "西方", | |
| "NORTH-EAST": "东北方", "NORTH-WEST": "西北方", | |
| "SOUTH-EAST": "东南方", "SOUTH-WEST": "西南方", | |
| "NE": "东北", "NW": "西北", "SE": "东南", "SW": "西南", | |
| "north": "北方", "south": "南方", "east": "东方", "west": "西方", | |
| "north-east": "东北方", "north-west": "西北方", | |
| "south-east": "东南方", "south-west": "西南方", | |
| "northern": "北部", "southern": "南部", "eastern": "东部", "western": "西部", | |
| "far": "最远", "mid": "中部", "centre": "中央", "center": "中央", | |
| "corner": "角落", | |
| } | |
| _ACTOR_ZH = { | |
| "e1": "步枪兵", "e2": "掷弹兵", "e3": "火箭兵", | |
| "e6": "工程师", "dog": "军犬", "medi": "医疗兵", | |
| "spy": "间谍", "thf": "小偷", "tanya": "谭雅", | |
| "jeep": "吉普车", "1tnk": "轻型坦克", "2tnk": "中型坦克", | |
| "3tnk": "重型坦克", "4tnk": "猛犸坦克", | |
| "apc": "装甲运兵车", "arty": "火炮", "harv": "采矿车", | |
| "mcv": "基地车", "lst": "登陆艇", | |
| "dd": "驱逐舰", "heli": "雌鹿武装直升机", | |
| "fact": "建造厂", "powr": "发电厂", "apwr": "高级发电厂", | |
| "proc": "矿石精炼厂", "barr": "苏军兵营", "tent": "盟军兵营", | |
| "weap": "战车工厂", "fix": "维修站", "dome": "雷达", | |
| "silo": "矿石仓库", "mine": "矿场", | |
| "gun": "防御炮塔", "pbox": "碉堡", "tsla": "磁暴线圈", | |
| "sam": "防空导弹", "mslo": "核弹发射井", | |
| } | |
| def _actor_zh(code: str) -> str: | |
| return _ACTOR_ZH.get(code, code) | |
| def _region_zh(x: Any, coords: str = "exact") -> str: | |
| if isinstance(x, dict): | |
| if coords == "relative": | |
| lbl = x.get("label") | |
| if lbl: | |
| return _translate_label_zh(str(lbl)) | |
| return f"区域 ({x.get('x')},{x.get('y')}) 半径={x.get('radius', 3)}" | |
| return str(x) | |
| def _translate_label_zh(lbl: str) -> str: | |
| """Translate a compass-direction label to Chinese.""" | |
| import re | |
| result = lbl | |
| for en, zh in sorted(_DIRECTION_ZH.items(), key=lambda kv: -len(kv[0])): | |
| result = re.sub(re.escape(en), zh, result, flags=re.IGNORECASE) | |
| return result | |
| _PHRASES_ZH: dict[str, Any] = { | |
| "within_ticks": lambda v: f"在游戏第 {v} 刻之前完成", | |
| "after_ticks": lambda v: f"不早于游戏第 {v} 刻", | |
| "units_killed_gte": lambda v: f"消灭 ≥{v} 个敌方单位", | |
| "units_lost_lte": lambda v: f"己方损失不超过 {v} 个单位", | |
| "explored_pct_gte": lambda v: f"探索至少 {v}% 的地图", | |
| "enemies_discovered_gte": lambda v: f"发现至少 {v} 个敌方单位", | |
| "buildings_discovered_gte": lambda v: f"发现至少 {v} 个敌方建筑", | |
| "reach_region": lambda v: f"派一个单位到达{_region_zh(v)}", | |
| "units_in_region_gte": lambda v: ( | |
| f"将至少 {(v if isinstance(v, dict) else {}).get('n', 1)} " | |
| f"个单位移动到{_region_zh(v)}" | |
| ), | |
| "units_of_type_in_region_gte": lambda v: ( | |
| f"将至少 {(v if isinstance(v, dict) else {}).get('n', 1)} 个" | |
| f"「{_actor_zh((v if isinstance(v, dict) else {}).get('type', '?'))}」" | |
| f"移动到{_region_zh(v)}" | |
| ), | |
| "all_units_in_region": lambda v: f"将所有单位移动到{_region_zh(v)}", | |
| "own_units_gte": lambda v: f"保持至少 {v} 个单位存活", | |
| "cash_gte": lambda v: f"持有至少 {v} 资金", | |
| "resources_gte": lambda v: f"持有至少 {v} 矿石储备", | |
| "economy_value_gte": lambda v: f"经济总值达到 ≥{v}(资金+矿石)", | |
| "power_surplus_gte": lambda v: f"电力盈余保持 ≥{v}", | |
| "power_provided_gte": lambda v: f"总发电量保持 ≥{v}", | |
| "has_building": lambda v: f"拥有一座「{_actor_zh(v)}」", | |
| "buildings_owned_gte": lambda v: f"拥有至少 {v} 种不同建筑", | |
| "building_total_gte": lambda v: f"拥有至少 {v} 座建筑", | |
| "building_count_gte": lambda v: ( | |
| f"拥有至少 {(v or {}).get('n', 1)} 座" | |
| f"「{_actor_zh((v or {}).get('type', '?'))}」" | |
| ), | |
| "building_in_region": lambda v: ( | |
| f"在 ({(v or {}).get('x')},{(v or {}).get('y')}) 附近" | |
| f"放置 {(v or {}).get('count', 1)} 座建筑" | |
| ), | |
| "unit_type_count_eq": lambda v: ( | |
| f"恰好拥有 {(v or {}).get('n')} 个" | |
| f"「{_actor_zh((v or {}).get('type', '?'))}」(不多不少)" | |
| ), | |
| "unit_type_count_gte": lambda v: ( | |
| f"拥有至少 {(v or {}).get('n')} 个" | |
| f"「{_actor_zh((v or {}).get('type', '?'))}」" | |
| ), | |
| "enemy_buildings_destroyed_gte": lambda v: f"摧毁至少 {v} 座敌方建筑", | |
| "enemy_key_buildings_destroyed": lambda v: ( | |
| "摧毁敌方的" | |
| + "+".join( | |
| _actor_zh(t) | |
| for t in (v.get("types", []) if isinstance(v, dict) else v) | |
| ) | |
| ), | |
| "enemy_key_buildings_destroyed_in_region": lambda v: ( | |
| "摧毁" | |
| f" ({(v or {}).get('x')},{(v or {}).get('y')}) 附近的敌方" | |
| + "+".join(_actor_zh(t) for t in (v or {}).get("types", [])) | |
| ), | |
| "tool_violations_gte": lambda v: ( | |
| "使用了禁止的工具(立即失败)" | |
| if int(v) <= 1 | |
| else f"使用禁止的工具 ≥{v} 次" | |
| ), | |
| } | |
| _REGION_PHRASES_ZH: dict[str, Any] = { | |
| "reach_region": lambda v, c: f"派一个单位到达{_region_zh(v, c)}", | |
| "units_in_region_gte": lambda v, c: ( | |
| f"将至少 {(v if isinstance(v, dict) else {}).get('n', 1)} " | |
| f"个单位移动到{_region_zh(v, c)}" | |
| ), | |
| "units_of_type_in_region_gte": lambda v, c: ( | |
| f"将至少 {(v if isinstance(v, dict) else {}).get('n', 1)} 个" | |
| f"「{_actor_zh((v if isinstance(v, dict) else {}).get('type', '?'))}」" | |
| f"移动到{_region_zh(v, c)}" | |
| ), | |
| "all_units_in_region": lambda v, c: f"将所有单位移动到{_region_zh(v, c)}", | |
| "waypoint_sequence": lambda v, c: ( | |
| "按顺序依次到达以下路径点(不可跳过,不可停滞):" | |
| + " → 然后 → ".join( | |
| _region_zh(p, c) | |
| for p in ((v.get("points") or []) if isinstance(v, dict) else []) | |
| ) | |
| ), | |
| } | |
| def _leaf_phrase_zh(key: str, v: Any, coords: str = "exact") -> str: | |
| if key in _REGION_PHRASES_ZH: | |
| return _REGION_PHRASES_ZH[key](v, coords) | |
| fn = _PHRASES_ZH.get(key) | |
| return fn(v) if fn else f"{key}={v}" | |
| def _describe_zh(node: Any, join: str = ",并且", coords: str = "exact") -> str: | |
| if node is None: | |
| return "" | |
| if not isinstance(node, dict): | |
| node = dict(getattr(node, "__pydantic_extra__", {}) or {}) | |
| if "all_of" in node: | |
| return join.join(_describe_zh(c, coords=coords) for c in node["all_of"]) | |
| if "any_of" in node: | |
| return "(" + " 或 ".join( | |
| _describe_zh(c, coords=coords) for c in node["any_of"] | |
| ) + ")" | |
| if "not" in node: | |
| inner = node["not"] | |
| inner_d = inner if isinstance(inner, dict) else dict( | |
| getattr(inner, "__pydantic_extra__", {}) or {} | |
| ) | |
| if set(inner_d) == {"own_units_gte"}: | |
| n = inner_d["own_units_gte"] | |
| return "全军覆没" if int(n) <= 1 else f"存活单位不足 {n} 个" | |
| return "未能(" + _describe_zh(inner, coords=coords) + ")" | |
| if "then" in node: | |
| v = node["then"] or {} | |
| clauses = v.get("clauses") or [] | |
| if not clauses: | |
| return "(空的顺序条件)" | |
| return ( | |
| "按以下顺序完成:" | |
| + " → 然后 → ".join( | |
| _describe_zh(c, coords=coords) for c in clauses | |
| ) | |
| ) | |
| return join.join(_leaf_phrase_zh(k, v, coords) for k, v in node.items()) | |
| def objective_brief_zh( | |
| description_zh: str, | |
| win_condition: Any, | |
| fail_condition: Any, | |
| max_turns: int, | |
| objective_coords: str = "exact", | |
| ) -> str: | |
| """Chinese objective text, structured identically to objective_brief.""" | |
| parts = [] | |
| if description_zh: | |
| parts.append(description_zh.strip()) | |
| win = _describe_zh(win_condition, coords=objective_coords) | |
| parts.append(f"胜利条件:{win}。" if win else "胜利条件:(未定义)") | |
| fail = _describe_zh(fail_condition, join=",并且", coords=objective_coords) | |
| if fail: | |
| parts.append(f"失败条件:{fail}。") | |
| parts.append(f"你最多有 {max_turns} 个决策回合,果断且尽早行动至关重要。") | |
| return "\n".join(parts) | |