Files
gplan-2/game/sim/battle_driver.gd

96 lines
5.1 KiB
GDScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# VERTICAL SLICE - NOT FOR PRODUCTION
# 战斗驱动循环单点——smoke 族探针共用的「挖掘至停手」整轮驱动。
# 策略 = AutoPlayer.choose_dig(O4 单一实现):挖最近可挖格(异格先移位)/ 无可挖或相位离 Active 停手。
# 语义锚(monte_carlo 原型逐位保持):guard 400 步防挂死;移位后相位/轮次离场即 break;
# 轮末仍 Active 且未换轮 → settle_batch stop。smoke_all._run_battle/_auto_play_round 为冒烟本体
# 不经本面(含回归断言,禁并入);fx_shot_probe 为带墙钟抓帧的异步变体亦不在列。
extends RefCounted
const AutoPlayer := preload("res://sim/auto_player.gd")
const GUARD_ACTIONS := 400 # 单轮动作步防挂死上限(被收口探针原值逐一同值)
# 驱动一整轮(驱动循环单点——通用签名)。still_running = 调用方战斗活性谓词(缺省恒真):
# monte_carlo / skill_rhythm 传 `func(): return out.result == ""`(battle_finished 已发的轮内不再动手),
# damage_probe 关卡驱动传 runner 绑定 + battle_result 谓词,battle 级探针传空即可。
# act_provider 契约(策略回调——func(settlement) -> Dictionary):
# {"kind":"stop"}/空字典 = 停手出环;单步 {"kind":"move"|"dig"|"seal"|"refresh","cell":V}(refresh 无 cell);
# 合步 {"kind":"move","cell":X,"next":{"kind":"dig","cell":Y}}——move 提交后查相位/轮次、变更即断弃 next。
# on_action 契约(日志回调——func(submit_kind:String, cell:Vector2i, ok:bool, round_index:int)):
# submit_kind ∈ {move, dig, seal_place, refresh, stop}——轮末统一 stop 也经此回调(accept 取 outcomes[0].accepted);
# 空 Callable = 不记日志。
# max_rejects:策略回调被拒连续上限(≤0 = 不设防——缺省形态零防线语义保持);ok 清零否则累加。
# 缺省两参形态(act_provider 空)= AutoPlayer.choose_dig 原语义逐位:guard 400、异格先移、合步 dig、
# 移位后相位/轮次离场断弃 next、轮末 stop——四调用方(dump_battle/monte_carlo/damage_probe/skill_rhythm)两参零改动。
static func drive_round(settlement, still_running: Callable = Callable(),
act_provider: Callable = Callable(), on_action: Callable = Callable(),
max_rejects: int = 0) -> void:
var start_round: int = settlement.round_index
var minesweeper = settlement.minesweeper
var guard := GUARD_ACTIONS
var rejects := 0
var using_provider: bool = not act_provider.is_null()
while _alive(still_running) and settlement.action_economy.phase == "Active" \
and settlement.round_index == start_round and guard > 0 \
and (max_rejects <= 0 or rejects < max_rejects):
guard -= 1
var act: Dictionary = act_provider.call(settlement) if using_provider else _default_act(minesweeper)
var kind := String(act.get("kind", "stop"))
if kind == "stop" or act.is_empty():
break
if _submit_act(settlement, act, on_action, start_round):
rejects = 0
else:
rejects += 1
if _alive(still_running) and settlement.action_economy.phase == "Active" and settlement.round_index == start_round:
var out_s: Array = settlement.minesweeper.settle_batch([{"action": "stop"}])
_on(on_action, "stop", Vector2i(-1, -1), _accepted(out_s), start_round)
# 缺省策略(AutoPlayer.choose_dig 原生 plan 专用):同位直接 dig、异位先移合步 next=dig
static func _default_act(minesweeper) -> Dictionary:
var plan := AutoPlayer.choose_dig(minesweeper)
if plan.is_empty():
return {"kind": "stop"}
if plan.hero_cell == minesweeper.hero_pos():
return {"kind": "dig", "cell": plan.dig_cell}
return {"kind": "move", "cell": plan.hero_cell, "next": {"kind": "dig", "cell": plan.dig_cell}}
# 单步提交分派(move/dig/seal→settle_batch——seal 提交动作名 seal_place;refresh→player_request_refresh;
# 合步 move→next 递归提交——相位/轮次离场断弃,与原内联逐位同)
static func _submit_act(settlement, act: Dictionary, on_action: Callable, ri: int) -> bool:
var kind := String(act.get("kind", "stop"))
var cell: Vector2i = act.get("cell", Vector2i(-1, -1))
var ok := false
if kind == "refresh":
ok = bool(settlement.player_request_refresh())
_on(on_action, "refresh", Vector2i(-1, -1), ok, ri)
return ok
var action_name := "seal_place" if kind == "seal" else kind
var out_s: Array = settlement.minesweeper.settle_batch([{"action": action_name, "cell": cell}])
ok = _accepted(out_s)
_on(on_action, action_name, cell, ok, ri)
if kind == "move":
var next: Dictionary = act.get("next", {})
if next.is_empty():
return ok
if settlement.action_economy.phase != "Active" or settlement.round_index != ri:
return ok # 移位后相位/轮次离场——断弃 next
_submit_act(settlement, next, on_action, ri)
return ok
static func _on(on_action: Callable, kind: String, cell: Vector2i, ok: bool, ri: int) -> void:
if not on_action.is_null():
on_action.call(kind, cell, ok, ri)
static func _accepted(outcomes: Array) -> bool:
return not outcomes.is_empty() and bool(outcomes[0].accepted)
static func _alive(still_running: Callable) -> bool:
return still_running.is_null() or bool(still_running.call())