Files
cards/scripts/game.gd
team3 5f9442497a
Some checks failed
Tests / headless (push) Has been cancelled
Tests / fenster-schnell (push) Has been cancelled
Tests / fenster-voll (push) Has been cancelled
Tests / balance (push) Has been cancelled
update
2026-10-03 01:17:27 +02:00

765 lines
26 KiB
GDScript

class_name Game
extends RefCounted
## Spielregeln: Duell, Mana je Zug, Ziehen aus dem gemeinsamen Stapel, verdeckte Wesen,
## Elementboni, Verschmelzen und Verbessern.
const MAX_BOARD := 5
const MAX_HAND := 5
const START_HAND := 5 # Startkarten, alle im ersten Zug bezahlbar
const START_MANA := 5 # Mana in Zug 1; jede Runde eins mehr, ohne Grenze; Rest verfällt
const DRAW_COST := 1 # Ziehen: eine bezahlbare Karte vom Stapel
const START_LIFE := 30
const ELEMENT_PAIR := 2 # so viele eigene offene Wesen eines Elements: Sondereffekt des Elements
const ELEMENT_STACK := 3 # so viele offene Wesen eines Elements auf dem Tisch: +1/+1
# Merkmale der Kampagnen-Bosse (Player.perks).
const PERKS := {
"feuer_angriff": "Feuerwesen haben +1 Angriff",
"ziehen_gratis": "Ziehen kostet kein Mana",
"wesen_angriff": "Wesen kommen mit +1 Angriff",
"wesen_leben": "Wesen kommen mit +1 Leben",
"extra_mana": "Bekommt jeden Zug 1 Mana mehr",
"leben_35": "Beginnt mit 5 Leben mehr",
"alle_flink": "Wesen greifen sofort an",
"verbessern_billig": "Eigenschaften kosten 1 weniger",
"start_energie": "Hat im ersten Zug 2 Mana mehr",
"element_doppelt": "Elementbonus zählt doppelt",
}
## Für wiederholbare Tests: 0 = echter Zufall, sonst Startwert (zählt je Aufruf hoch).
static var test_seed := 0
static func make_rng() -> RandomNumberGenerator:
var r := RandomNumberGenerator.new()
if test_seed != 0:
r.seed = test_seed
test_seed += 1
else:
r.randomize()
return r
class Creature extends RefCounted:
var card: Cards.Card
var owner_ref: WeakRef # schwache Referenz: kein Zyklus Spieler <-> Wesen
var name: String
var uid: int
var attack: int
var health: int
var max_health: int
var attacks_done := 0 # Angriffe in diesem Zug
var ready := false # lag schon zu Beginn des eigenen Zugs: darf angreifen und aufgedeckt werden
var hidden := false # verdeckt gelegt: greift nicht an, zählt nicht für die Elementboni
var aura := 0 # Elementbonus (+1 je Element mit vollem Stapel); das Leben ist schon angepasst
func _init(c: Cards.Card, o, id: int) -> void:
card = c
owner_ref = weakref(o)
name = c.name
uid = id
attack = c.attack
health = c.health
max_health = c.health
func owner():
return owner_ref.get_ref()
## Eigenschaft: gekauft, als Sondereffekt des Elementpaars der eigenen Seite oder als
## Boss-Merkmal. Verdeckte Wesen haben keine.
func has(kw: String) -> bool:
if hidden:
return false
if kw in card.keywords:
return true
var o = owner()
if o == null:
return false
if kw == "Flink" and "alle_flink" in o.perks:
return true
for e in card.elements:
if Cards.ELEMENT_KEYWORD.get(e, "") == kw and o.element_count(e) >= Game.ELEMENT_PAIR:
return true
return false
## Alle wirksamen Eigenschaften in fester Reihenfolge.
func keywords() -> Array[String]:
var out: Array[String] = []
for k in Cards.KEYWORDS:
if has(k):
out.append(k)
return out
## Angriffswert inklusive Elementbonus und Boss-Merkmalen.
func power() -> int:
var v := attack + aura
var o = owner()
if o != null:
if "feuer_angriff" in o.perks and "Feuer" in card.elements:
v += 1
return v
func attacks_per_turn() -> int:
return 2 if has("Doppelschlag") else 1
func attacks_left() -> int:
return maxi(0, attacks_per_turn() - attacks_done)
## Angreifen darf, wer offen liegt, noch Angriffe hat und seit einer Runde liegt (oder flink ist).
func can_attack() -> bool:
return not hidden and attacks_left() > 0 and power() > 0 and (ready or has("Flink"))
func clone(new_owner) -> Creature:
var c := Creature.new(card, new_owner, uid)
c.attack = attack
c.health = health
c.max_health = max_health
c.attacks_done = attacks_done
c.ready = ready
c.hidden = hidden
c.aura = aura
return c
class Player extends RefCounted:
var name: String
var title: String
var is_human: bool
var life: int
var max_life: int
var hand: Array = [] # eigene Exemplare (Cards.Card.copy)
var board: Array = []
var energy := 0 # Mana in diesem Zug; der Rest verfällt
var max_energy := 0 # Mana zu Zugbeginn (START_MANA + eigene Züge)
var start_bonus := 0 # einmaliges Extra-Mana für den späteren Start
var order := 0 # Platz in der Zugreihenfolge
var perks: Array = [] # Boss-Merkmale (siehe PERKS)
var looks := {} # Karten-ID -> Seltenheitsstufe, reine Optik (siehe Profile)
var turns := 0 # begonnene eigene Züge
func _init(n: String, h: bool, l: int, t: String = "") -> void:
name = n
title = t
is_human = h
life = l
max_life = l
func alive() -> bool:
return life > 0
## Offene eigene Wesen mit diesem Element.
func element_count(e: String) -> int:
var n := 0
for c in board:
if not c.hidden and e in c.card.elements:
n += 1
return n
func clone() -> Player:
var p := Player.new(name, is_human, life, title)
p.max_life = max_life
p.hand = hand.duplicate()
p.energy = energy
p.max_energy = max_energy
p.start_bonus = start_bonus
p.order = order
p.perks = perks.duplicate()
p.looks = looks
p.turns = turns
for c in board:
p.board.append(c.clone(p))
return p
var human: Player # der (erste) menschliche Spieler
var humans: Array = [] # alle menschlichen Spieler (im Netz zwei)
var opponent: Player # der andere am Tisch
var players: Array = [] # in Zugreihenfolge
var winner_player: Player = null
var rng: RandomNumberGenerator
var deck: Array = [] # gemeinsamer Stapel, das Ende ist oben (nur Host und Einzelspieler)
var turn := 0
var current_idx := 0
var log: Array[String] = []
var events: Array = [] # strukturierte Ereignisse für Animationen
var winner := "" # "", "human" oder "opponents"
var quiet := false # in KI-Kopien: kein Verlauf, keine Ereignisse
var next_uid := 1
func _init(h: Player, o: Player, r: RandomNumberGenerator = null) -> void:
human = h
opponent = o
players = [h, o]
humans = players.filter(func(p): return p.is_human)
if r == null:
r = make_rng()
rng = r
func current() -> Player:
return players[current_idx]
func say(t: String) -> void:
if not quiet:
log.append(t)
func emit(ev: Dictionary) -> void:
if not quiet:
events.append(ev)
func ref_of(target) -> Dictionary:
if target is Creature:
return {"kind": "creature", "uid": target.uid}
return {"kind": "player", "index": players.find(target)}
## Der Gegner von p, solange er lebt (als Liste, für Schleifen über Ziele).
func enemies_of(p: Player) -> Array:
return players.filter(func(o): return o != p and o.alive())
func enemy_of(p: Player) -> Player:
return opponent if p == human else human
func find_creature(uid: int) -> Creature:
for p in players:
for c in p.board:
if c.uid == uid:
return c
return null
## Unabhängige Kopie des Spielzustands (für Rückgängig und die KI-Vorausschau).
func clone() -> Game:
var copies := []
for p in players:
copies.append(p.clone())
var idx := players.find(human)
var g := Game.new(copies[idx], copies[1 - idx], RandomNumberGenerator.new())
g.players = copies # gleiche Zugreihenfolge wie das Original
g.deck = deck.duplicate()
g.turn = turn
g.current_idx = current_idx
g.winner = winner
g.next_uid = next_uid
g.quiet = true
return g
## Kopie aus Sicht von viewer: verdeckte gegnerische Wesen sind unbekannt (für die KI).
func masked_clone(viewer: Player) -> Game:
var g := clone()
var me: Player = g.players[players.find(viewer)]
for p in g.players:
if p == me:
continue
for c in p.board:
if c.hidden:
c.card = Cards.unknown()
c.name = c.card.name
c.attack = c.card.attack
c.health = c.card.health
c.max_health = c.card.health
return g
# --- Zugablauf ---------------------------------------------------------------
func start(fixed_order: bool = false) -> void:
if not fixed_order:
shuffle(players, rng) # zufällige Zugreihenfolge
deck = DeckGen.deck(rng)
for i in players.size():
var p: Player = players[i]
p.order = i
# Wer nicht beginnt, bekommt im ersten Zug einmalig 1 Mana extra.
p.start_bonus = 0 if i == 0 else 1
for c in draw_cards(START_HAND, START_MANA + p.start_bonus):
p.hand.append(c.copy())
turn = 1
current_idx = 0
say("Zugreihenfolge: " + ", ".join(players.map(func(p): return p.name)))
for p in players:
for perk in p.perks:
say("%s: %s." % [p.name, PERKS[perk]])
begin_turn(current())
## Nimmt n Karten vom Stapel, die höchstens max_cost kosten (was man sich in diesem Zug leisten
## kann); zu teure wandern unter den Stapel. Ist der Stapel leer, kommt ein frischer.
func draw_cards(n: int, max_cost: int) -> Array:
var out := []
var skipped := []
var looked := 0
while out.size() < n:
if deck.is_empty():
deck = DeckGen.deck(rng)
say("Der Stapel wird neu gemischt.")
var c: Cards.Card = deck.pop_back()
looked += 1
if c.cost <= max_cost or looked > deck.size() + skipped.size() + n:
out.append(c)
else:
skipped.append(c)
for c in skipped:
deck.insert(0, c)
return out
## Ziehen: kostet DRAW_COST, eine Karte kommt auf die Hand.
func can_draw(p: Player) -> Array:
if winner != "" or p != current():
return [false, "Nicht dein Zug"]
if p.hand.size() >= MAX_HAND:
return [false, "Deine Hand ist voll"]
if p.energy < draw_cost(p):
return [false, "Nicht genug Mana"]
return [true, ""]
func draw_cost(p: Player) -> int:
return 0 if "ziehen_gratis" in p.perks else DRAW_COST
## Preis einer Eigenschaft für p.
func upgrade_cost(p: Player, keyword: String) -> int:
var c := int(Cards.KEYWORD_COST[keyword])
return maxi(1, c - 1) if "verbessern_billig" in p.perks else c
func draw(p: Player) -> String:
var ok := can_draw(p)
if not ok[0]:
return ok[1]
p.energy -= draw_cost(p)
var card: Cards.Card = draw_cards(1, p.max_energy)[0]
p.hand.append(card.copy())
say("%s zieht." % p.name)
emit({"type": "draw", "player": players.find(p)})
return ""
func begin_turn(p: Player) -> void:
p.max_energy = START_MANA + p.turns
p.energy = p.max_energy + (1 if "extra_mana" in p.perks else 0) # der Rest vom letzten Zug ist verfallen
if p.turns == 0 and "start_energie" in p.perks:
p.energy += 2
if p.start_bonus > 0:
p.energy += p.start_bonus
say("%s erhält %d Mana extra für den späteren Start." % [p.name, p.start_bonus])
p.start_bonus = 0
for c in p.board:
c.ready = true # lag eine Runde: greift jetzt an, Verdecktes darf aufgedeckt werden
c.attacks_done = 0
p.turns += 1
say("--- Zug %d: %s ---" % [turn, p.name])
emit({"type": "turn", "player": players.find(p)})
## Zugende: Wesen mit Regeneration heilen ganz, dann ist der andere dran.
func end_turn() -> void:
if winner != "":
return
for c in current().board:
if c.has("Regeneration") and c.health < c.max_health:
c.health = c.max_health
say("%s regeneriert sich." % c.name)
emit({"type": "heal", "uid": c.uid})
var n := players.size()
for i in range(1, n + 1):
var idx := (current_idx + i) % n
if players[idx].alive():
if idx <= current_idx:
turn += 1
current_idx = idx
break
begin_turn(current())
# --- Karten spielen ----------------------------------------------------------
## Ausspielen kostet die Kartenkosten; verdeckt gelegt ist es gleich teuer.
func can_play(p: Player, card: Cards.Card) -> Array:
if winner != "" or p != current():
return [false, "Nicht dein Zug"]
if not (card in p.hand):
return [false, "Nicht auf der Hand"]
if p.board.size() >= MAX_BOARD:
return [false, "Dein Feld ist voll"]
if p.energy < card.cost:
return [false, "Nicht genug Mana"]
return [true, ""]
## Spielt eine Karte, offen oder verdeckt. Das Wesen greift erst im nächsten Zug an (außer flink).
## Gibt "" zurück oder eine Fehlermeldung.
func play_card(p: Player, card: Cards.Card, face_down: bool = false) -> String:
var ok := can_play(p, card)
if not ok[0]:
return ok[1]
p.hand.erase(card)
p.energy -= card.cost
var cr := Creature.new(card, p, next_uid)
next_uid += 1
cr.hidden = face_down
if "wesen_angriff" in p.perks:
cr.attack += 1
if "wesen_leben" in p.perks:
cr.health += 1
cr.max_health += 1
p.board.append(cr)
if face_down:
say("%s legt ein Wesen verdeckt." % p.name)
else:
say("%s spielt %s." % [p.name, card.name])
emit({"type": "play", "player": players.find(p), "uid": cr.uid, "card": card, "hidden": face_down})
apply_auras()
cleanup()
check_end()
return ""
## Ein eigenes verdecktes Wesen aufdecken: kostenlos, im eigenen Zug, frühestens einen Zug
## nach dem Legen. Aufgedeckt greift es sofort an.
func can_reveal(p: Player, cr: Creature) -> Array:
if winner != "" or p != current():
return [false, "Nicht dein Zug"]
if cr == null or not (cr in p.board):
return [false, "Nur eigene Wesen"]
if not cr.hidden:
return [false, "%s liegt schon offen" % cr.name]
if not cr.ready:
return [false, "%s kann erst im nächsten Zug aufgedeckt werden" % cr.name]
return [true, ""]
func reveal(p: Player, cr: Creature) -> String:
var ok := can_reveal(p, cr)
if not ok[0]:
return ok[1]
_flip(cr)
cleanup()
check_end()
return ""
## Deckt ein Wesen auf; die Elementboni gelten sofort.
func _flip(cr: Creature) -> void:
cr.hidden = false
say("%s wird aufgedeckt." % cr.name)
emit({"type": "reveal", "uid": cr.uid, "card": cr.card})
apply_auras()
# --- Verbessern: Eigenschaften für Mana -------------------------------------
## target: eigenes Wesen auf dem Brett oder eigene Handkarte.
func can_upgrade(p: Player, target, keyword: String) -> Array:
if winner != "" or p != current():
return [false, "Nicht dein Zug"]
if not Cards.KEYWORD_COST.has(keyword):
return [false, "Unbekannte Eigenschaft"]
var card: Cards.Card = target.card if target is Creature else target
if target is Creature:
if not (target in p.board):
return [false, "Nur eigene Wesen"]
elif not (target in p.hand):
return [false, "Nicht auf der Hand"]
if keyword in card.keywords:
return [false, "%s hat schon %s" % [card.name, keyword]]
if p.energy < upgrade_cost(p, keyword):
return [false, "Nicht genug Mana"]
return [true, ""]
func upgrade(p: Player, target, keyword: String) -> String:
var ok := can_upgrade(p, target, keyword)
if not ok[0]:
return ok[1]
p.energy -= upgrade_cost(p, keyword)
var card: Cards.Card = (target.card if target is Creature else target).copy() # Kopien (KI) teilen sich Karten
card.keywords.append(keyword)
if target is Creature:
target.card = card
else:
p.hand[p.hand.find(target)] = card
say("%s bekommt %s." % [card.name, keyword])
emit({"type": "upgrade", "player": players.find(p), "keyword": keyword, "uid": target.uid if target is Creature else -1})
return ""
# --- Verschmelzen ------------------------------------------------------------
static func card_of(x) -> Cards.Card:
return x.card if x is Creature else x
## Seltenheitsstufe einer Karte von p: bei verschmolzenen die geerbte, sonst die der Sammlung.
static func look_of(p: Player, c: Cards.Card) -> int:
return c.look if c.look >= 0 else int(p.looks.get(c.id, 0))
## Kosten: 1 Mana, verdoppelt je Verschmelzung, die schon in einer der beiden steckt.
static func fusion_cost(a, b) -> int:
return 1 << maxi(card_of(a).fusions, card_of(b).fusions)
## a und b: zwei eigene Wesen auf dem Brett oder zwei eigene Handkarten.
func can_fuse(p: Player, a, b) -> Array:
if winner != "" or p != current():
return [false, "Nicht dein Zug"]
if a == null or b == null or a == b:
return [false, "Zwei verschiedene Karten wählen"]
if a is Creature and b is Creature:
if not (a in p.board) or not (b in p.board):
return [false, "Nur eigene Wesen"]
elif a is Creature or b is Creature:
return [false, "Nur Hand mit Hand oder Brett mit Brett"]
elif not (a in p.hand) or not (b in p.hand):
return [false, "Nicht auf der Hand"]
if p.energy < fusion_cost(a, b):
return [false, "Nicht genug Mana"]
return [true, ""]
## Verschmilzt zwei Handkarten zu einer oder zwei eigene Wesen zu einem offenen: Elemente und
## Eigenschaften beider, Werte nach Cards.fusion_value, Kosten folgen den Werten.
func fuse(p: Player, a, b) -> String:
var ok := can_fuse(p, a, b)
if not ok[0]:
return ok[1]
p.energy -= fusion_cost(a, b)
if a is Creature:
return _fuse_board(p, a, b)
var card := Cards.fused(a, b)
card.look = maxi(look_of(p, a), look_of(p, b))
var at := p.hand.find(a)
p.hand.erase(a)
p.hand.erase(b)
p.hand.insert(mini(at, p.hand.size()), card)
say("%s und %s verschmelzen auf der Hand zu %s (%d/%d)." % [a.name, b.name, card.name, card.attack, card.health])
emit({"type": "fuse", "player": players.find(p), "uid": -1, "gone": []})
return ""
## Auf dem Brett zählen die Werte der Wesen (ohne Elementbonus, den bekommt das neue selbst);
## das Ergebnis ist nur angriffsbereit, wenn beide es waren.
func _fuse_board(p: Player, a: Creature, b: Creature) -> String:
var card := Cards.fused(a.card, b.card, Cards.fusion_value(a.attack, b.attack),
Cards.fusion_value(a.max_health - a.aura, b.max_health - b.aura))
card.look = maxi(look_of(p, a.card), look_of(p, b.card))
var cr := Creature.new(card, p, next_uid)
next_uid += 1
cr.health = mini(card.health, Cards.fusion_value(mini(a.health, a.max_health - a.aura), mini(b.health, b.max_health - b.aura)))
cr.ready = a.ready and b.ready
cr.attacks_done = maxi(a.attacks_done, b.attacks_done)
var at := p.board.find(a)
p.board.erase(a)
p.board.erase(b)
p.board.insert(mini(at, p.board.size()), cr)
say("%s und %s verschmelzen zu %s (%d/%d)." % [a.name, b.name, cr.name, cr.attack, cr.health])
emit({"type": "fuse", "player": players.find(p), "uid": cr.uid, "gone": [a.uid, b.uid]})
apply_auras()
cleanup()
check_end()
return ""
# --- Elementboni ---------------------------------------------------------------
## Offene Wesen je Element auf dem ganzen Tisch.
func element_counts() -> Dictionary:
var counts := {}
for e in Cards.ELEMENTS:
counts[e] = 0
for p in players:
for c in p.board:
if not c.hidden:
for e in c.card.elements:
counts[e] += 1
return counts
## +1 je Element mit ELEMENT_STACK offenen Wesen auf dem Tisch; mehrere Elemente zählen alle.
static func aura_of(elements: Array, counts: Dictionary) -> int:
var a := 0
for e in elements:
if int(counts[e]) >= ELEMENT_STACK:
a += 1
return a
## Bonus eines offenen Wesens; verdeckte zählen nicht und bekommen nichts.
func aura_for(c: Creature, counts: Dictionary) -> int:
if c.hidden:
return 0
var a := aura_of(c.card.elements, counts)
var o = c.owner()
if o != null and "element_doppelt" in o.perks:
a *= 2
return a
## Rechnet die Elementboni neu. Kommt ein Bonus, steigen Leben und Höchstleben; geht er,
## sinkt nur das Höchstleben, das Leben wird höchstens darauf gekappt.
func apply_auras() -> void:
var counts := element_counts()
for p in players:
for c in p.board:
var a := aura_for(c, counts)
if a != c.aura:
var delta: int = a - c.aura
c.max_health += delta
c.health = mini(c.health + maxi(delta, 0), c.max_health)
c.aura = a
## Vorschau fürs Ausspielen: der Elementbonus, den die Karte offen gelegt bekäme.
func forecast(p: Player, card: Cards.Card) -> int:
var counts := element_counts()
for e in card.elements:
counts[e] += 1
return aura_of(card.elements, counts) * (2 if "element_doppelt" in p.perks else 1)
# --- Kampf -------------------------------------------------------------------
## Leben lassen sich erst angreifen, wenn der Gegner kein Wesen mehr hat; Wesen mit Spott
## müssen vor den anderen angegriffen werden.
func can_attack(cr: Creature, target) -> Array:
if winner != "" or cr.owner() != current():
return [false, "Nicht dein Zug"]
if cr.hidden:
return [false, "%s liegt verdeckt" % cr.name]
if not cr.ready and not cr.has("Flink") and cr.attacks_left() > 0 and cr.power() > 0:
return [false, "%s greift erst im nächsten Zug an" % cr.name]
if not cr.can_attack():
return [false, "%s kann nicht angreifen" % cr.name]
var tgt_owner = target.owner() if target is Creature else target
if not (tgt_owner in enemies_of(cr.owner())):
return [false, "Kein gültiges Ziel"]
if not (target is Creature) and not tgt_owner.board.is_empty():
return [false, "Erst die Wesen"]
if target is Creature and not target.has("Spott") and tgt_owner.board.any(func(c): return c.has("Spott")):
return [false, "Erst die Wesen mit Spott"]
return [true, ""]
## Greift an. Ein verdecktes Ziel wird zuerst aufgedeckt. Gibt "" zurück oder eine Fehlermeldung.
func attack(cr: Creature, target) -> String:
var ok := can_attack(cr, target)
if not ok[0]:
return ok[1]
cr.attacks_done += 1
if target is Creature and target.hidden:
_flip(target)
say("%s greift %s an." % [cr.name, target.name])
emit({"type": "attack", "attacker": ref_of(cr), "target": ref_of(target)})
damage(target, cr.power() + matchup_bonus(cr, target))
if target is Creature and target.power() > 0: # wer nichts austeilt, kontert auch nicht; der Konter kennt keinen Elementvorteil
damage(cr, target.power())
cleanup()
check_end()
return ""
## Elementvorteil beim Zuschlagen: +1 im Vorteil, -1 im Nachteil (die Verteidigung des
## Ziels), gegen Helden gibt es keinen.
static func matchup_bonus(source: Creature, target) -> int:
if not (target is Creature):
return 0
return Cards.matchup(source.card.elements, target.card.elements)
func damage(target, amount: int) -> void:
if amount <= 0:
return
if target is Creature:
target.health -= amount
else:
target.life -= amount
say("%s erleidet %d Schaden." % [target.name, amount])
emit({"type": "damage", "target": ref_of(target), "amount": amount})
## Räumt Tote ab; danach gelten die Elementboni neu, was weitere Tote nach sich ziehen kann.
func cleanup() -> void:
for round in 8:
var died := false
for p in players:
for c in p.board.duplicate():
# Wer besiegt ist, nimmt seine Wesen mit vom Tisch.
if c.health <= 0 or not p.alive():
p.board.erase(c)
if c.health > 0:
say("%s verschwindet mit %s." % [c.name, p.name])
else:
say("%s stirbt." % c.name)
emit({"type": "death", "target": ref_of(c)})
died = true
if not died:
return
apply_auras()
## Ende: einer von beiden hat kein Leben mehr.
func check_end() -> void:
if winner != "":
return
var alive := players.filter(func(p): return p.alive())
if alive.size() != 1:
return
winner_player = alive[0]
winner = "human" if winner_player.is_human else "opponents"
if humans.size() == 1:
say("Der Gegner ist besiegt!" if winner == "human" else "Du wurdest besiegt.")
else:
say("%s gewinnt!" % winner_player.name)
# --- Zustand für das Netzwerk (siehe NetState) ------------------------------------
func snapshot(viewer: int) -> Dictionary:
return NetState.snapshot(self, viewer)
static func from_snapshot(d: Dictionary) -> Game:
return NetState.from_snapshot(d)
# --- Gegner und Kampfstart ---------------------------------------------------
static func shuffle(arr: Array, r: RandomNumberGenerator) -> void:
for i in range(arr.size() - 1, 0, -1):
var j := r.randi_range(0, i)
var tmp = arr[i]
arr[i] = arr[j]
arr[j] = tmp
## Duell gegen einen zufälligen Gegner. rules: {"human_perks": [...], "opponent_life": int, "looks": {...}}
static func new_battle(r: RandomNumberGenerator = null, rules: Dictionary = {}) -> Game:
if r == null:
r = make_rng()
return new_battle_with(DeckGen.random_opponent(int(rules.get("opponent_life", START_LIFE))), r, false, rules)
static func new_battle_with(opp: Player, r: RandomNumberGenerator = null, fixed_order: bool = false, rules: Dictionary = {}) -> Game:
if r == null:
r = make_rng()
var human := Player.new("Spieler", true, START_LIFE)
human.perks = Array(rules.get("human_perks", []))
human.looks = rules.get("looks", {})
var g := Game.new(human, opp, r)
for p in g.players:
if "leben_35" in p.perks:
p.life += 5
p.max_life += 5
g.start(fixed_order)
return g