Spiel untersucht und optimiert: KI-Suche, Hintergrund-Threads, Dialog-Helfer

- KI-Vorausschau in zwei Stufen mit Zielvorauswahl (höchstens 4 Ziele je Karte
  und Angreifer, die 8 besten Erstschritte vertieft): 5 Gegner 2063 ms -> 101 ms,
  1 Gegner 160 ms -> 36 ms, alle Kandidaten im Budget; 52:48 gegen die alte Suche
- KI rechnet im WorkerThreadPool auf einer Spielkopie, schon während der Wartezeit;
  längste Bildzeit im Kampf mit 5 Gegnern 108 ms -> 38 ms
- Musik wird im Hintergrund erzeugt (Start 185 ms schneller)
- Style.overlay und Style.confirm ersetzen vier kopierte Dialoge
- Übersetzungen für Abbrechen und Zurücksetzen, Archetypen neu gemessen
- tests/perf_probe.gd (Laufzeiten) und tests/frame_probe.tscn (Bildzeiten)

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QPgKvy5W9WRNFHjkm5QsHx
This commit is contained in:
Team3
2026-09-16 17:53:27 +02:00
parent e1e6211ef8
commit b6d5916a8d
16 changed files with 307 additions and 86 deletions

View File

@@ -19,6 +19,9 @@ var pending = null # Cards.Card, wartet auf Ziel
var msg_t := 0.0
var ai_delay := 0.75
var ai_timer := 0.0
var ai_task := -1 # WorkerThreadPool-Aufgabe der laufenden KI-Berechnung
var ai_copy: Game # Spielkopie, auf der die KI rechnet
var ai_box := {} # Ergebnis der Berechnung (vom Thread befüllt)
var reward := -1
var ended := false
var zoom_player = null
@@ -301,12 +304,7 @@ func _build_static() -> void:
notice_label = Style.label(overlay, "", 18, Style.GREEN, Rect2(0, 385, 1280, 80), "center")
Style.button(overlay, "Zurück zum Menü", Rect2(530, 480, 220, 50), leave, Color("285a3c"))
# Aufgeben bestätigen
confirm = _overlay()
Style.panel(confirm, Rect2(390, 300, 500, 180), Style.PANEL, 10, Style.MUTED, 2)
Style.label(confirm, "Wirklich aufgeben?", 26, Style.TEXT, Rect2(390, 320, 500, 40), "center", true)
Style.label(confirm, "Der Kampf endet ohne Münzen.", 16, Style.MUTED, Rect2(390, 365, 500, 24), "center")
Style.button(confirm, "Ja, aufgeben", Rect2(420, 410, 200, 46), leave, Color("5a2828"))
Style.button(confirm, "Weiterspielen", Rect2(660, 410, 200, 46), func(): confirm.visible = false)
confirm = Style.confirm(self, "Wirklich aufgeben?", "Der Kampf endet ohne Münzen.", "Ja, aufgeben", leave, "Weiterspielen")
# Übergabe zwischen Menschen (Hotseat)
handover = _overlay()
Style.panel(handover, Rect2(340, 280, 600, 220), Style.PANEL, 10, Style.GOLD, 2)
@@ -323,16 +321,7 @@ func _layer() -> Control:
func _overlay() -> Control:
var o := Control.new()
o.set_anchors_preset(Control.PRESET_FULL_RECT)
o.visible = false
var dark := ColorRect.new()
dark.color = Color(0, 0, 0, 0.67)
dark.set_anchors_preset(Control.PRESET_FULL_RECT)
dark.mouse_filter = Control.MOUSE_FILTER_STOP
o.add_child(dark)
add_child(o)
return o
return Style.overlay(self)
func leave() -> void:
@@ -520,9 +509,22 @@ func _process(delta: float) -> void:
ai_timer = 0.0
else:
ai_timer -= delta
if ai_timer > 0:
return
var action := AI.choose_action(game, cur) if smart_ai else AI.choose_action_greedy(game, cur)
var action: Dictionary
if smart_ai:
# Die Vorausschau rechnet im Hintergrund auf einer Kopie, schon während der Wartezeit,
# damit Animationen nicht stocken.
if ai_task < 0:
_start_ai(cur)
if ai_timer > 0 or not WorkerThreadPool.is_task_completed(ai_task):
return
WorkerThreadPool.wait_for_task_completion(ai_task)
ai_task = -1
action = AI.map_action(game, ai_copy, ai_box.action)
ai_copy = null
else:
if ai_timer > 0:
return
action = AI.choose_action_greedy(game, cur)
AI.apply_action(game, cur, action)
ai_timer = ai_delay * (0.6 if action.type == "end" else 1.0)
if game.current().is_human:
@@ -530,6 +532,20 @@ func _process(delta: float) -> void:
after_action()
func _start_ai(cur: Game.Player) -> void:
ai_copy = game.clone_for(cur)
var copy := ai_copy
var idx := game.current_idx
var box := ai_box
ai_task = WorkerThreadPool.add_task(func(): box["action"] = AI.choose_action(copy, copy.players[idx]))
func _exit_tree() -> void:
if ai_task >= 0:
WorkerThreadPool.wait_for_task_completion(ai_task)
ai_task = -1
func _finish() -> void:
var won := game.winner == "human"
if counts_for_profile():