120 lines
4.2 KiB
GDScript
120 lines
4.2 KiB
GDScript
# 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) + "}"
|