class_name Game extends RefCounted ## Spielregeln: Spieler, Wesen, Züge, Angriffe, Effekte, Feldkombos, Resonanz. const MAX_BOARD := 5 const MAX_HAND := 8 const MAX_ENERGY := 10 const START_HAND := 4 # Jeder gegen jeden: alle Spieler haben dieselben Regeln und dasselbe Startleben. const START_LIFE := 30 const MAX_OPPONENTS := 5 const RESONANCE_MAX := 4 const MAX_TRAPS := 2 # Merkmale der Kampagnen-Bosse (Player.perks). const PERKS := { "feuer_angriff": "Feuerwesen haben +1 Angriff", "flink_angriff": "Flinke Wesen haben +1 Angriff", "waechter_leben": "Wächter kommen mit +2 Leben", "extra_karten": "Zieht 2 Karten pro Zug", "leben_35": "Beginnt mit 35 Leben", "zauber_schaden": "Schadenszauber machen +1 Schaden", "schild_wesen": "Wesen kommen mit Schild", "alle_flink": "Alle Wesen sind flink", "start_energie": "Beginnt mit 3 Energie", "resonanz_doppelt": "Resonanz 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 const DECK_SIZE := 20 const MAX_COPIES := 3 const MAX_DECK_ELEMENTS := 2 const OPPONENT_RARITY_WEIGHT := {"gewöhnlich": 6.0, "selten": 3.0, "episch": 2.0, "legendär": 1.0, "mythisch": 0.3} 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 resonance := 0 # +1, wenn ein anderes eigenes Wesen ein Element teilt var health: int var max_health: int var shield: bool var frozen := false var ready: bool var attacks_left: int 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 shield = "Schild" in c.keywords ready = "Flink" in c.keywords attacks_left = attacks_per_turn() if ready else 0 func owner(): return owner_ref.get_ref() ## Angriffswert inklusive Resonanz und Boss-Merkmalen. func power() -> int: var v := attack + resonance var o = owner() if o != null: if "feuer_angriff" in o.perks and "Feuer" in card.elements: v += 1 if "flink_angriff" in o.perks and "Flink" in card.keywords: v += 1 return v func attacks_per_turn() -> int: return 2 if "Doppelschlag" in card.keywords else 1 func taunt() -> bool: return "Wächter" in card.keywords func can_attack() -> bool: return ready and attacks_left > 0 and not frozen and power() > 0 func clone(new_owner) -> Creature: var c := Creature.new(card, new_owner, uid) c.attack = attack c.resonance = resonance c.health = health c.max_health = max_health c.shield = shield c.frozen = frozen c.ready = ready c.attacks_left = attacks_left return c class Player extends RefCounted: var name: String var title: String var is_human: bool var life: int var max_life: int var deck: Array = [] var hand: Array = [] var board: Array = [] var energy := 0 var max_energy := 0 var fatigue := 0 var start_bonus := 0 # einmalige Extra-Energie im ersten Zug (Ausgleich fürs Nachziehen) var traps: Array = [] # verdeckt liegende Fallenkarten var order := 0 # Platz in der Zugreihenfolge var perks: Array = [] # Boss-Merkmale (siehe PERKS) var turns := 0 # begonnene eigene Züge var mulligan_done := false func _init(n: String, d: Array, h: bool, l: int, t: String = "") -> void: name = n title = t is_human = h life = l max_life = l deck = d.duplicate() func alive() -> bool: return life > 0 func clone() -> Player: var p := Player.new(name, deck, is_human, life, title) p.max_life = max_life p.hand = hand.duplicate() p.energy = energy p.max_energy = max_energy p.fatigue = fatigue p.start_bonus = start_bonus p.traps = traps.duplicate() p.order = order p.perks = perks.duplicate() p.turns = turns p.mulligan_done = mulligan_done 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 (Hotseat: mehrere) var opponents: Array = [] # alle außer dem ersten Menschen var players: Array = [] # in Zugreihenfolge var winner_player: Player = null var rng: RandomNumberGenerator 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, opps: Array, r: RandomNumberGenerator = null) -> void: human = h opponents = opps players = [h] + opps 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)} ## Jeder gegen jeden: alle anderen lebenden Spieler sind Gegner. func enemies_of(p: Player) -> Array: return players.filter(func(o): return o != p and o.alive()) 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 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.slice(0, idx) + copies.slice(idx + 1), RandomNumberGenerator.new()) g.players = copies # gleiche Zugreihenfolge wie das Original 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 p: fremde Fallen sind unbekannt und fehlen (die KI schummelt nicht). func clone_for(p: Player) -> Game: var g := clone() var p2: Player = g.players[players.find(p)] for q in g.players: if q != p2: q.traps = [] return g # --- Zugablauf --------------------------------------------------------------- func start(fixed_order: bool = false) -> void: if not fixed_order: shuffle(players, rng) # zufällige Zugreihenfolge for i in players.size(): var p: Player = players[i] p.order = i if "start_energie" in p.perks: p.max_energy = 2 shuffle(p.deck, rng) # Wer nicht beginnt, startet mit einer Karte mehr und hat im ersten Zug # einmalig 1 Energie extra (Ausgleich für den Startvorteil). draw(p, START_HAND + (0 if i == 0 else 1)) p.start_bonus = 0 if i == 0 else 1 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()) func draw(p: Player, n: int = 1) -> void: for i in n: if p.deck.is_empty(): p.fatigue += 1 say("%s hat keine Karten mehr und erleidet %d Schaden." % [p.name, p.fatigue]) damage(p, p.fatigue) continue var card = p.deck.pop_back() if p.hand.size() >= MAX_HAND: say("%s: Hand voll, %s verbrennt." % [p.name, card.name]) else: p.hand.append(card) check_end() func begin_turn(p: Player) -> void: p.max_energy = mini(MAX_ENERGY, p.max_energy + 1) p.energy = p.max_energy if p.start_bonus > 0: p.energy += p.start_bonus say("%s erhält %d Energie extra für den späteren Start." % [p.name, p.start_bonus]) p.start_bonus = 0 for c in p.board: c.ready = true if c.frozen: c.frozen = false c.attacks_left = 0 say("%s taut auf und setzt aus." % c.name) else: c.attacks_left = c.attacks_per_turn() p.turns += 1 say("--- Zug %d: %s ---" % [turn, p.name]) emit({"type": "turn", "player": players.find(p)}) draw(p, 2 if "extra_karten" in p.perks else 1) ## Tauscht einmalig im ersten eigenen Zug Karten der Hand: zurück ins Deck, mischen, nachziehen. func mulligan(p: Player, cards: Array) -> String: if p.mulligan_done: return "Schon getauscht" if p != current() or p.turns != 1: return "Nur im ersten eigenen Zug" p.mulligan_done = true var n := 0 for c in cards: if c in p.hand: p.hand.erase(c) p.deck.append(c) n += 1 if n > 0: shuffle(p.deck, rng) draw(p, n) say("%s tauscht %d Karten." % [p.name, n] if n > 0 else "%s behält die Hand." % p.name) emit({"type": "mulligan", "player": players.find(p), "count": n}) return "" func end_turn() -> void: if winner != "": return 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 ---------------------------------------------------------- func can_play(p: Player, card: Cards.Card) -> Array: if winner != "" or p != current(): return [false, "Nicht dein Zug"] if p.energy < card.cost: return [false, "Nicht genug Energie"] if card.is_creature() and p.board.size() >= MAX_BOARD: return [false, "Dein Feld ist voll"] if card.is_trap() and p.traps.size() >= MAX_TRAPS: return [false, "Höchstens %d Fallen" % MAX_TRAPS] var tt := card.target_type() if tt != "" and not card.is_creature() and valid_targets(p, tt).is_empty(): return [false, "Kein gültiges Ziel"] return [true, ""] func valid_targets(p: Player, target_type: String) -> Array: var enemies := enemies_of(p) var creatures := [] for e in enemies: creatures.append_array(e.board) match target_type: "enemy_creature": return creatures "enemy_any": return creatures + enemies "friendly_creature": return p.board.duplicate() return [] ## Spielt eine Karte. Gibt "" zurück oder eine Fehlermeldung. func play_card(p: Player, card: Cards.Card, target = null) -> String: var ok := can_play(p, card) if not ok[0]: return ok[1] var tt := card.target_type() if tt != "": var valid := valid_targets(p, tt) if not valid.is_empty() and not (target in valid): return "Ungültiges Ziel" if valid.is_empty(): target = null p.hand.erase(card) p.energy -= card.cost if card.is_trap(): p.traps.append(card) say("%s legt eine Falle." % p.name) emit({"type": "trap_set", "player": players.find(p)}) check_end() return "" say("%s spielt %s." % [p.name, card.name]) var cr: Creature = null var bonus := 0 if card.is_creature(): cr = _enter(p, card) else: emit({"type": "spell", "player": players.find(p), "card": card}) if "zauber_schaden" in p.perks: bonus = 1 for fx in card.effects: resolve(p, fx, target, cr, bonus) if cr != null and cr in p.board: for e in enemies_of(p): if not (cr in p.board): break trigger_trap(e, "creature_played", cr) if cr != null and cr in p.board: trigger_field_combos(p, cr) update_resonance() cleanup() check_end() return "" ## Feldkombo: das neue Wesen bildet mit dem ersten eigenen Wesen aus anderem ## Element eine Kombo; pro Ausspielen löst höchstens eine Kombo aus. func trigger_field_combos(p: Player, cr: Creature) -> void: for other in p.board: if other == cr: continue for e1 in cr.card.elements: for e2 in other.card.elements: if e1 == e2: continue var name := Cards.combo_name([e1, e2]) say("Feldkombo %s: %s." % [name, Cards.FIELD_COMBO_TEXT[name]]) emit({"type": "combo", "name": name, "player": players.find(p), "uid": cr.uid}) resolve(p, Cards.field_combo_effect(name), null, cr) return ## Löst die erste passende Falle von owner aus. Gibt true zurück, wenn der ## auslösende Angriff abgebrochen wird. func trigger_trap(owner: Player, trigger: String, source: Creature) -> bool: for trap in owner.traps: if trap.trigger != trigger: continue owner.traps.erase(trap) say("Falle von %s: %s!" % [owner.name, trap.name]) emit({"type": "trap", "player": players.find(owner), "card": trap, "source": ref_of(source)}) var cancel := false for fx in trap.effects: if fx.kind == "cancel": cancel = true else: resolve(owner, fx, null, source) cleanup() return cancel return false ## Wesen betritt das Feld; Boss-Merkmale wirken beim Eintritt. func _enter(p: Player, card: Cards.Card) -> Creature: var cr := Creature.new(card, p, next_uid) next_uid += 1 if "waechter_leben" in p.perks and cr.taunt(): cr.health += 2 cr.max_health += 2 if "schild_wesen" in p.perks: cr.shield = true if "alle_flink" in p.perks: cr.ready = true cr.attacks_left = cr.attacks_per_turn() p.board.append(cr) emit({"type": "play", "player": players.find(p), "uid": cr.uid, "card": card}) return cr ## Setzt ein Wesen aufs Feld (Fallen, Effekte). func summon(p: Player, card: Cards.Card) -> void: if p.board.size() >= MAX_BOARD: say("%s: kein Platz für %s." % [p.name, card.name]) return _enter(p, card) say("%s beschwört %s." % [p.name, card.name]) update_resonance() ## Resonanz: +1 Angriff je anderem eigenen Wesen, das ein Element teilt (höchstens +4). func update_resonance() -> void: for p in players: for c in p.board: var bonus := 0 for o in p.board: if o != c and o.card.elements.any(func(e): return e in c.card.elements): bonus += 1 c.resonance = mini(bonus, RESONANCE_MAX) * (2 if "resonanz_doppelt" in p.perks else 1) func resolve(p: Player, fx: Cards.Effect, target, source: Creature = null, bonus: int = 0) -> void: var enemies := enemies_of(p) var enemy_creatures := [] for e in enemies: enemy_creatures.append_array(e.board) var targets := [] if fx.target in Cards.CHOSEN_TARGETS: targets = [target] if target != null else [] elif fx.target == "all_enemy_creatures": targets = enemy_creatures elif fx.target == "all_enemy_heroes": targets = enemies.duplicate() elif fx.target == "all_enemies": targets = enemy_creatures + enemies elif fx.target == "all_friendly_creatures": targets = p.board.duplicate() elif fx.target == "self_hero": targets = [p] elif fx.target == "self_creature" or fx.target == "trigger_source": targets = [source] if source != null else [] elif fx.target == "strongest_enemy_creature": var best: Creature = null for c in enemy_creatures: if best == null or c.power() > best.power(): best = c targets = [best] if best != null else [] elif fx.target == "weakest_enemy_hero": var weakest: Player = null for e in enemies: if weakest == null or e.life < weakest.life: weakest = e targets = [weakest] if weakest != null else [] else: targets = [null] for t in targets: match fx.kind: "damage": damage(t, fx.amount + bonus) "heal": t.life = mini(t.max_life, t.life + fx.amount) say("%s heilt sich um %d." % [t.name, fx.amount]) emit({"type": "heal", "target": ref_of(t), "amount": fx.amount}) "draw": draw(p, fx.amount) "freeze": t.frozen = true t.attacks_left = 0 say("%s ist eingefroren." % t.name) emit({"type": "freeze", "target": ref_of(t)}) "bounce": var o = t.owner() o.board.erase(t) emit({"type": "bounce", "target": ref_of(t)}) if o.hand.size() < MAX_HAND: o.hand.append(t.card) say("%s kehrt auf die Hand zurück." % t.name) else: say("%s wird weggeweht (Hand voll)." % t.name) "buff": t.attack += fx.amount t.health += fx.amount2 t.max_health += fx.amount2 say("%s erhält +%d/+%d." % [t.name, fx.amount, fx.amount2]) emit({"type": "buff", "target": ref_of(t), "text": "+%d/+%d" % [fx.amount, fx.amount2]}) "shield": t.shield = true say("%s erhält Schild." % t.name) emit({"type": "buff", "target": ref_of(t), "text": "Schild"}) "summon": summon(p, Cards.by_id(fx.card_id)) # --- Kampf ------------------------------------------------------------------- func can_attack(cr: Creature, target) -> Array: if winner != "" or cr.owner() != current(): return [false, "Nicht dein Zug"] 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"] var has_taunt := false for c in tgt_owner.board: if c.taunt(): has_taunt = true if has_taunt and not (target is Creature and target.taunt()): return [false, "Ein Wächter muss zuerst angegriffen werden"] return [true, ""] ## Greift an. 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_left -= 1 say("%s greift %s an." % [cr.name, target.name]) emit({"type": "attack", "attacker": ref_of(cr), "target": ref_of(target)}) var tgt_owner: Player = target.owner() if target is Creature else target var cancelled := trigger_trap(tgt_owner, "attacked", cr) if cancelled or not (cr in cr.owner().board) or (target is Creature and not (target in tgt_owner.board)): check_end() return "" damage(target, cr.power()) if target is Creature: damage(cr, target.power()) cleanup() check_end() return "" func damage(target, amount: int) -> void: if amount <= 0: return if target is Creature: if target.shield: target.shield = false say("Schild von %s blockt den Schaden." % target.name) emit({"type": "blocked", "target": ref_of(target)}) return target.health -= amount else: target.life -= amount say("%s erleidet %d Schaden." % [target.name, amount]) emit({"type": "damage", "target": ref_of(target), "amount": amount}) func cleanup() -> void: var died := false for p in players: for c in p.board.duplicate(): if c.health <= 0: p.board.erase(c) say("%s stirbt." % c.name) emit({"type": "death", "target": ref_of(c)}) died = true if died: update_resonance() ## Ende: nur noch ein Spieler übrig, oder kein Mensch mehr am Tisch. func check_end() -> void: if winner != "": return var alive := players.filter(func(p): return p.alive()) var humans_alive := alive.filter(func(p): return p.is_human) if humans_alive.is_empty(): winner = "opponents" winner_player = alive[0] if alive.size() == 1 else null say("Du wurdest besiegt." if humans.size() == 1 else "Alle Spieler sind besiegt, die KI gewinnt.") elif alive.size() == 1: winner = "human" winner_player = alive[0] say("Alle Gegner sind besiegt!" if humans.size() == 1 else "%s gewinnt!" % winner_player.name) # --- Zustand für das Netzwerk ------------------------------------------------- ## Serialisierter Zustand aus Sicht eines Spielers: fremde Hände, Decks und Fallen nur als Anzahl. func snapshot(viewer: int) -> Dictionary: var ps := [] for i in players.size(): var p: Player = players[i] var mine := i == viewer var board := [] for c in p.board: board.append({"uid": c.uid, "card": c.card.id, "attack": c.attack, "resonance": c.resonance, "health": c.health, "max_health": c.max_health, "shield": c.shield, "frozen": c.frozen, "ready": c.ready, "attacks_left": c.attacks_left}) ps.append({"name": p.name, "title": p.title, "is_human": p.is_human, "life": p.life, "max_life": p.max_life, "energy": p.energy, "max_energy": p.max_energy, "fatigue": p.fatigue, "start_bonus": p.start_bonus, "order": p.order, "perks": p.perks, "turns": p.turns, "mulligan_done": p.mulligan_done, "hand": p.hand.map(func(c): return c.id) if mine else p.hand.size(), "deck": p.deck.size(), "traps": p.traps.map(func(c): return c.id) if mine else p.traps.size(), "board": board}) var evs := [] for ev in events: var e: Dictionary = ev.duplicate() if e.has("card"): e["card"] = e.card.id evs.append(e) return {"players": ps, "human": players.find(human), "turn": turn, "current_idx": current_idx, "winner": winner, "winner_player": players.find(winner_player) if winner_player != null else -1, "next_uid": next_uid, "log": log.slice(maxi(0, log.size() - 80)), "events": evs} ## Baut einen Spielzustand aus einem Snapshot; verborgene Karten werden durch Platzhalter ersetzt. static func from_snapshot(d: Dictionary) -> Game: var placeholder: Cards.Card = Cards.all()[0] var list := [] for pd in d.players: var p := Player.new(pd.name, [], pd.is_human, int(pd.life), pd.title) p.max_life = int(pd.max_life) p.energy = int(pd.energy) p.max_energy = int(pd.max_energy) p.fatigue = int(pd.fatigue) p.start_bonus = int(pd.start_bonus) p.order = int(pd.order) p.perks = Array(pd.perks) p.turns = int(pd.turns) p.mulligan_done = bool(pd.mulligan_done) if pd.hand is Array: for cid in pd.hand: p.hand.append(Cards.by_id(cid)) else: for i in int(pd.hand): p.hand.append(placeholder) for i in int(pd.deck): p.deck.append(placeholder) if pd.traps is Array: for cid in pd.traps: p.traps.append(Cards.by_id(cid)) else: for i in int(pd.traps): p.traps.append(placeholder) for cd in pd.board: var c := Creature.new(Cards.by_id(cd.card), p, int(cd.uid)) c.attack = int(cd.attack) c.resonance = int(cd.resonance) c.health = int(cd.health) c.max_health = int(cd.max_health) c.shield = bool(cd.shield) c.frozen = bool(cd.frozen) c.ready = bool(cd.ready) c.attacks_left = int(cd.attacks_left) p.board.append(c) list.append(p) var hi := int(d.human) var g := Game.new(list[hi], list.slice(0, hi) + list.slice(hi + 1), RandomNumberGenerator.new()) g.players = list g.turn = int(d.turn) g.current_idx = int(d.current_idx) g.winner = d.winner g.winner_player = list[int(d.winner_player)] if int(d.winner_player) >= 0 else null g.next_uid = int(d.next_uid) for line in d.log: g.log.append(str(line)) for ev in d.events: var e: Dictionary = ev.duplicate() if e.has("card"): e["card"] = Cards.by_id(e.card) for key in ["player", "uid", "count", "amount"]: if e.has(key): e[key] = int(e[key]) for key in ["target", "attacker", "source"]: if e.has(key): var r: Dictionary = e[key] if r.has("uid"): r["uid"] = int(r.uid) if r.has("index"): r["index"] = int(r.index) g.events.append(e) return g # --- Deckregeln und Gegner --------------------------------------------------- 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 static func deck_elements(cards: Array) -> Array: var out := [] for c in cards: for e in c.elements: if not (e in out): out.append(e) return out ## Prüft ein Deck. owned: Dictionary id -> Anzahl (null = keine Besitzprüfung). static func validate_deck(cards: Array, owned = null) -> Array: if cards.size() != DECK_SIZE: return [false, "Deck braucht genau %d Karten (%d)" % [DECK_SIZE, cards.size()]] var counts := {} for c in cards: counts[c.id] = counts.get(c.id, 0) + 1 for cid in counts: if counts[cid] > MAX_COPIES: return [false, "Höchstens %dx dieselbe Karte" % MAX_COPIES] if owned != null and owned.get(cid, 0) < counts[cid]: return [false, "Karte nicht oft genug in der Sammlung"] if deck_elements(cards).size() > MAX_DECK_ELEMENTS: return [false, "Höchstens %d Elemente im Deck" % MAX_DECK_ELEMENTS] return [true, ""] ## Gegnerdeck aus den Elementen. bias > 1 bevorzugt seltene Karten (Kampagne). static func build_opponent_deck(r: RandomNumberGenerator, elements: Array, bias: float = 1.0) -> Array: var pool := Cards.all().filter(func(c): return c.elements.all(func(e): return e in elements)) var weights := PackedFloat32Array() for c in pool: var w: float = OPPONENT_RARITY_WEIGHT[c.rarity] weights.append(w / bias if c.rarity == "gewöhnlich" else w * bias) var deck := [] var counts := {} while deck.size() < DECK_SIZE: var c = pool[r.rand_weighted(weights)] if counts.get(c.id, 0) < MAX_COPIES: counts[c.id] = counts.get(c.id, 0) + 1 deck.append(c) return deck static func random_opponent(r: RandomNumberGenerator, index: int, life: int = START_LIFE) -> Player: var elements: Array if r.randf() < 0.4: elements = [Cards.ELEMENTS[r.randi_range(0, Cards.ELEMENTS.size() - 1)]] else: var pool: Array = Cards.ELEMENTS.duplicate() shuffle(pool, r) elements = [pool[0], pool[1]] return Player.new("Gegner %d" % index, build_opponent_deck(r, elements), false, life, Cards.combo_name(elements) + "-Magier") ## rules: {"human_perks": [...], "everyone_perk": String, "start_life": int} static func new_battle(deck: Array, n_opp: int, r: RandomNumberGenerator = null, rules: Dictionary = {}) -> Game: assert(n_opp >= 1 and n_opp <= MAX_OPPONENTS, "1 bis 5 Gegner") if r == null: r = make_rng() var opps := [] for i in n_opp: opps.append(random_opponent(r, i + 1)) return new_battle_with(deck, opps, r, false, rules) static func new_battle_with(deck: Array, opps: Array, r: RandomNumberGenerator = null, fixed_order: bool = false, rules: Dictionary = {}) -> Game: if r == null: r = make_rng() var human := Player.new("Du", deck, true, START_LIFE) human.perks = Array(rules.get("human_perks", [])) var g := Game.new(human, opps, r) var everyone: String = rules.get("everyone_perk", "") for p in g.players: if everyone != "" and not (everyone in p.perks): p.perks.append(everyone) if rules.has("start_life"): p.life = int(rules.start_life) p.max_life = p.life elif "leben_35" in p.perks and p.life == START_LIFE: p.life = 35 p.max_life = 35 g.start(fixed_order) return g ## Hotseat: mehrere Menschen am selben Rechner, dazu KI-Gegner. static func new_hotseat(human_decks: Array, n_ai: int, r: RandomNumberGenerator = null) -> Game: assert(human_decks.size() >= 1 and human_decks.size() + n_ai <= MAX_OPPONENTS + 1, "höchstens 6 am Tisch") if r == null: r = make_rng() var first := Player.new("Spieler 1", human_decks[0], true, START_LIFE) var others := [] for i in range(1, human_decks.size()): others.append(Player.new("Spieler %d" % (i + 1), human_decks[i], true, START_LIFE)) for i in n_ai: others.append(random_opponent(r, i + 1)) var g := Game.new(first, others, r) g.start() return g