96 lines
5.1 KiB
GDScript
96 lines
5.1 KiB
GDScript
# 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())
|