# VERTICAL SLICE - NOT FOR PRODUCTION # 战斗期望蒙特卡洛:当前参数下"胜轮数 / 每轮承伤 / 清图次数 / 开格数"的分布实测。 # 政策 = 理性玩家近似:挖最近可挖格(AutoPlayer 单一实现)。 # 运行:Godot_console.exe --headless --path . --script res://sim/monte_carlo.gd extends SceneTree const BattleDriver := preload("res://sim/battle_driver.gd") # T6 收口:挖掘驱动循环单点(策略仍归 AutoPlayer 单一实现) const N := 30 const SEED0 := 42 func _initialize() -> void: var db = load("res://core/config_db.gd").new() db.load_tables("res://data/tables", false) for L in [1, 2]: _run_encounter(db, L) quit(0) func _run_encounter(db, L: int) -> void: var rounds_v: Array = [] # 胜利战斗的轮数 var dmg_per_round: Array = [] # 每战斗 每轮承伤 var dmg_total: Array = [] var safe_opened: Array = [] var clears: Array = [] # 每战斗清图(换新板)次数 var buff_grants: Array = [] # attack_up+ac_up 授予数 var losses := 0 var timeouts := 0 for i in range(N): var seed_val: int = SEED0 + i var r := _run_battle(db, seed_val, L) if r.result == "victory": rounds_v.append(r.rounds) dmg_total.append(r.hero_dmg) dmg_per_round.append(float(r.hero_dmg) / float(r.rounds)) safe_opened.append(r.safe_opened) clears.append(r.board_swaps) buff_grants.append(r.buffs) elif r.result == "defeat": losses += 1 else: timeouts += 1 print("=== L%d · %d 种子(败 %d / 超时 %d)===" % [L, N, losses, timeouts]) print(" 胜利轮数 均值 %.2f 中位 %d p10 %.1f p90 %.1f 分布 %s" % [_mean(rounds_v), _median(rounds_v), _pct_f(rounds_v, 0.1), _pct_f(rounds_v, 0.9), _hist(rounds_v)]) print(" 每轮承伤 均值 %.2f 中位 %.1f p90 %.1f" % [_mean(dmg_per_round), _median_f(dmg_per_round), _pct_f(dmg_per_round, 0.9)]) print(" 整场承伤 均值 %.1f 中位 %.0f(主角 max_hp=%d)" % [_mean(dmg_total), _median_f(dmg_total), int(db.hero.max_hp)]) print(" 开安全格 均值 %.1f 中位 %d" % [_mean(safe_opened), _median(safe_opened)]) print(" 清图(换板) 均值 %.2f 中位 %d" % [_mean(clears), _median(clears)]) print(" 攻防buff授予 均值 %.2f" % [_mean(buff_grants)]) func _run_battle(db, seed_val: int, L: int) -> Dictionary: var rng = load("res://core/rng_service.gd").new() rng.set_master_seed(seed_val) var settlement = load("res://core/battle_orchestrator.gd").new(db, rng) var out := {"rounds": 0, "result": "", "hero_dmg": 0, "safe_opened": 0, "board_swaps": 0, "buffs": 0} settlement.battle_finished.connect(func(payload): out.result = payload.get("result", "")) settlement.board_changed.connect(func(): out.board_swaps += 1) settlement.step_row.connect(func(row): if row.type == "damage" and row.source_class == "enemy_action": out.hero_dmg += int(row.value) elif row.type == "effect" and row.source_class == "safe_cell" and (row.effect_id == "attack_up" or row.effect_id == "ac_up"): out.buffs += 1) settlement.minesweeper.cell_revealed.connect(func(evt): if evt.kind == "safe" and evt.channel != "initial": out.safe_opened += 1) if not settlement.begin_battle({"level_index": L, "node_depth": 0, "hero_hp": int(db.hero.max_hp)}): return out var guard_rounds := 40 while out.result == "" and guard_rounds > 0: guard_rounds -= 1 out.rounds += 1 BattleDriver.drive_round(settlement, func() -> bool: return out.result == "") if out.result == "": out.result = "timeout" return out func _mean(a: Array) -> float: if a.is_empty(): return 0.0 var t := 0.0 for v in a: t += float(v) return t / a.size() func _median(a: Array) -> int: return int(_median_f(a)) func _median_f(a: Array) -> float: if a.is_empty(): return 0.0 var b := a.duplicate() b.sort() return float(b[b.size() / 2]) func _pct_f(a: Array, p: float) -> float: if a.is_empty(): return 0.0 var b := a.duplicate() b.sort() return float(b[mini(b.size() - 1, int(p * b.size()))]) func _hist(a: Array) -> String: var h := {} for v in a: h[v] = int(h.get(v, 0)) + 1 var keys := h.keys() keys.sort() var parts: Array = [] for k in keys: parts.append("%d轮×%d" % [k, h[k]]) return "{" + ", ".join(parts) + "}"