class_name Game extends RefCounted ## Rundenlogik: 8 Spieler (1 Mensch, 7 Bots), geteilter Pool, Paarungen, Kämpfe. enum Phase { PLANNING, COMBAT, RESULT, GAMEOVER } const BOT_NAMES := [ "Ignis", "Nerea", "Zephyr", "Volt", "Terra", "Frost", "Umbra", ] var players: Array = [] var pool: Pool var rand := RandomNumberGenerator.new() var round_no: int = 0 var phase: int = Phase.PLANNING var human: PlayerState var bots := {} ## pid -> BotAI var pairings: Array = [] ## [{a, b, ghost}] var player_combat: Combat = null var pending: Array = [] ## Ergebnisse der Bot-Kämpfe var last_round_log: Array = [] func start(seed_value: int = 0) -> void: rand.seed = seed_value if seed_value != 0 else randi() pool = Pool.new() players.clear() human = PlayerState.new(0, "Du", false) players.append(human) for i in BOT_NAMES.size(): var b := PlayerState.new(i + 1, BOT_NAMES[i], true) players.append(b) var ai := BotAI.new() ai.setup(rand) bots[b.pid] = ai round_no = 1 for p in players: p.roll_shop(pool, rand) _run_bot_turns() phase = Phase.PLANNING func alive_players() -> Array: var out := [] for p in players: if p.alive: out.append(p) return out # --- Planungsphase --------------------------------------------------------- func _run_bot_turns() -> void: for p in players: if p.is_bot and p.alive: bots[p.pid].take_turn(p, pool, rand, round_no) # --- Kampfphase ------------------------------------------------------------ func start_combat() -> void: if phase != Phase.PLANNING: return pairings = _make_pairings() pending.clear() player_combat = null for pr in pairings: if pr["a"] == human or pr["b"] == human: player_combat = Combat.new(pr["a"], pr["b"], rand.randi()) pending.append({"pair": pr, "combat": player_combat}) else: var c := Combat.new(pr["a"], pr["b"], rand.randi()) c.run_to_end() pending.append({"pair": pr, "combat": c}) phase = Phase.COMBAT if player_combat == null: resolve_round() func update_combat(dt: float) -> void: if phase != Phase.COMBAT or player_combat == null: return player_combat.step(dt) if player_combat.finished: resolve_round() func skip_combat() -> void: if phase == Phase.COMBAT and player_combat != null: player_combat.run_to_end() resolve_round() func _make_pairings() -> Array: var alive := alive_players() alive.shuffle() var out := [] var i := 0 while i + 1 < alive.size(): out.append({"a": alive[i], "b": alive[i + 1], "ghost": false}) i += 2 if i < alive.size(): # Ungerade Anzahl: der Rest kämpft gegen ein Abbild eines Gegners. var lone: PlayerState = alive[i] var others := alive.filter(func(p): return p != lone) if others.is_empty(): return out var src: PlayerState = others[rand.randi_range(0, others.size() - 1)] out.append({"a": lone, "b": _make_ghost(src), "ghost": true}) return out func _make_ghost(src: PlayerState) -> PlayerState: var g := PlayerState.new(-1, src.pname + " (Abbild)", true) for hex in src.board: g.board[hex] = src.board[hex] return g # --- Auswertung ------------------------------------------------------------ func resolve_round() -> void: last_round_log.clear() for item in pending: var pr: Dictionary = item["pair"] var c: Combat = item["combat"] if not c.finished: c.run_to_end() var a: PlayerState = pr["a"] var b: PlayerState = pr["b"] var dmg := 3 + c.survivors if c.winner == 0: _apply_result(a, b, dmg, pr["ghost"]) elif c.winner == 1: _apply_result(b, a, dmg, pr["ghost"]) else: a.register_result(false) a.take_damage(3) if not pr["ghost"]: b.register_result(false) b.take_damage(3) last_round_log.append("%s vs %s: unentschieden" % [a.pname, b.pname]) pending.clear() player_combat = null _check_eliminations() if alive_players().size() <= 1: phase = Phase.GAMEOVER for p in alive_players(): p.placement = 1 return _next_round() func _apply_result(winner: PlayerState, loser: PlayerState, dmg: int, ghost: bool) -> void: winner.register_result(true) if loser.pid >= 0: loser.register_result(false) loser.take_damage(dmg) last_round_log.append("%s schlägt %s (-%d Leben)" % [winner.pname, loser.pname, dmg]) func _check_eliminations() -> void: var still := alive_players().size() for p in players: if p.hp <= 0 and p.alive: p.alive = false p.placement = still still -= 1 last_round_log.append(p.pname + " ist ausgeschieden") func _next_round() -> void: round_no += 1 for p in players: if not p.alive: continue p.gold += p.round_income() p.roll_shop(pool, rand) _run_bot_turns() phase = Phase.PLANNING # --- Hilfen für die UI ---------------------------------------------------- func human_opponent() -> PlayerState: for pr in pairings: if pr["a"] == human: return pr["b"] if pr["b"] == human: return pr["a"] return null func human_team_in_combat() -> int: for pr in pairings: if pr["a"] == human: return 0 if pr["b"] == human: return 1 return 0 func ranking() -> Array: var sorted := players.duplicate() sorted.sort_custom(func(a, b): if a.alive != b.alive: return a.alive return a.hp > b.hp) return sorted