first commit

This commit is contained in:
team3
2026-08-29 11:42:30 +02:00
commit 9410d18ea5
47 changed files with 4382 additions and 0 deletions

210
scripts/core/Game.gd Normal file
View File

@@ -0,0 +1,210 @@
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