update
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

This commit is contained in:
Team3
2026-10-04 22:40:18 +02:00
parent 5f9442497a
commit 0a612f91b3
16 changed files with 177 additions and 79 deletions

View File

@@ -20,19 +20,19 @@ Der Spielstand liegt unter `user://spielstand.json`
## Spielsysteme ## Spielsysteme
- 77 Karten in `data/karten.json`, alle Wesen, jede mit genau einem der vier Elemente; Kombos sind die Namen verschmolzener Wesen - 81 Karten in `data/karten.json`, alle Wesen, jede mit genau einem der vier Elemente; Kombos sind die Namen verschmolzener Wesen
- Karten haben nur Element, Angriff und Leben; die Kosten sind zwei Drittel Angriff plus ein Drittel Leben, abgerundet, mindestens 1 - Karten haben nur Element, Angriff und Leben; die Kosten sind zwei Drittel Angriff plus ein Drittel Leben, abgerundet; je Element gibt es ein 0/2-Wesen für 0 Mana
- Mana: 5 im ersten Zug, jede Runde eins mehr ohne Grenze; was übrig bleibt, verfällt. Mana geht in Ziehen, Legen, Verbessern und Verschmelzen - Mana: 1 im ersten Zug, jede Runde eins mehr ohne Grenze; was übrig bleibt, verfällt. Mana geht in Ziehen, Legen, Verbessern und Verschmelzen
- Kein Deckbau: beide ziehen vom gemeinsamen Stapel (jede Karte dreimal). Zu Beginn fünf Karten, Ziehen kostet 1 Mana und bringt eine; es kommt nur, was das Mana dieses Zugs hergibt, der Rest wandert unter den Stapel - Kein Deckbau: beide ziehen vom gemeinsamen Stapel (jede Karte dreimal). Zu Beginn fünf Karten, alle im ersten Zug spielbar; jeder Zug beginnt mit einer Karte vom Stapel (nicht bei voller Hand), weiteres Ziehen kostet 2 Mana; es kommt nur, was das Mana dieses Zugs hergibt, der Rest wandert unter den Stapel
- Eigenschaften: Spott (2, muss zuerst angegriffen werden), Flink (2, greift sofort an), Regeneration (3, heilt am Zugende ganz) und Doppelschlag (4) lassen sich für Mana auf jede Hand- oder Brettkarte kaufen (Rechtsklick oder Y). Gelegte Wesen greifen erst im nächsten Zug an, außer sie sind flink; verletzte Wesen heilen nicht von selbst - Eigenschaften: Spott (2, muss zuerst angegriffen werden), Flink (2, greift sofort an), Regeneration (3, heilt am Zugende ganz) und Doppelschlag (4) lassen sich für Mana auf jede Hand- oder Brettkarte kaufen (Rechtsklick oder Y). Gelegte Wesen greifen erst im nächsten Zug an, außer sie sind flink; verletzte Wesen heilen nicht von selbst
- Verdeckt legen (mittlere Maustaste auf die Handkarte oder im Kartenmenü): gleich teuer, das Wesen greift nicht an und zählt nicht für die Elementboni; aufdecken ist kostenlos, frühestens im nächsten Zug, danach greift es sofort an; ein Angriff deckt es auf - Verdeckt legen (mittlere Maustaste auf die Handkarte oder im Kartenmenü): gleich teuer, das Wesen greift nicht an und zählt nicht für die Elementboni; aufdecken ist kostenlos, frühestens im nächsten Zug, danach greift es sofort an; ein Angriff deckt es auf
- Jede Karte gehört jedem von Anfang an. Gesammelt wird die Seltenheitsstufe, und die ist reine Optik wie bei Yu-Gi-Oh: Common matt, Rare silberner Name und Rahmen mit Glanzstreifen, Super Rare Holo-Bild, Ultra Rare goldener Name und Rahmen mit Holo-Bild, Ultimate Rare Relief, Holo auf Bild und Rahmen, Funkeln und schillernde Farben; auch auf dem Feld für alle sichtbar, Werte bleiben gleich. Verschmolzene Wesen tragen die höhere Stufe ihrer Eltern - Jede Karte gehört jedem von Anfang an. Gesammelt wird die Seltenheitsstufe, und die ist reine Optik wie bei Yu-Gi-Oh: Common matt, Rare silberner Name und Rahmen mit Glanzstreifen, Super Rare Holo-Bild, Ultra Rare goldener Name und Rahmen mit Holo-Bild, Ultimate Rare Relief, Holo auf Bild und Rahmen, Funkeln und schillernde Farben; auch auf dem Feld für alle sichtbar, Werte bleiben gleich. Verschmolzene Wesen tragen die höhere Stufe ihrer Eltern
- Booster (100 Münzen): fünf Karten mit gewürfelter Stufe (50/25/15/8/2 %, mindestens eine Rare), höhere werten die Karte auf, der Rest wird zu Staub (50/100/200/500/1000). Aufwerten kostet 100/200/500/1000 Staub, eine Stufe abgeben bringt den Zerlegewert zurück - Booster (100 Münzen): fünf Karten mit gewürfelter Stufe (50/25/15/8/2 %, mindestens eine Rare), höhere werten die Karte auf, der Rest wird zu Staub (50/100/200/500/1000). Aufwerten kostet 100/200/500/1000 Staub, eine Stufe abgeben bringt den Zerlegewert zurück
- Elementboni: zwei eigene offene Wesen eines Elements geben allen eigenen Wesen dieses Elements seinen Effekt (Erde Spott, Luft Flink, Wasser Regeneration, Feuer Doppelschlag); drei offene Wesen eines Elements auf dem Tisch geben dem Element +1/+1 (fällt der Bonus weg, sinkt nur das Höchstleben). Verschmolzene Wesen zählen für alle ihre Elemente und bekommen alle Boni - Elementboni: zwei eigene offene Wesen eines Elements geben allen eigenen Wesen dieses Elements seinen Effekt (Erde Spott, Luft Flink, Wasser Regeneration, Feuer Doppelschlag); drei offene Wesen eines Elements auf dem Tisch geben dem Element +1/+1 (fällt der Bonus weg, sinkt nur das Höchstleben). Verschmolzene Wesen zählen für alle ihre Elemente und bekommen alle Boni
- Elementvorteil beim Kampf (Wasser > Feuer > Luft > Erde > Wasser): der Angreifer macht 1 Schaden mehr im Vorteil und 1 weniger im Nachteil, der Konter bleibt gleich, gegen Helden nichts; ein Wesen mit mehreren Elementen kämpft mit dem, das gegen den Gegner am besten steht - Elementvorteil beim Kampf (Wasser > Feuer > Luft > Erde > Wasser): wer im Vorteil ist, macht 1 Schaden mehr, wer im Nachteil ist, 1 weniger, beim Angriff wie beim Konter, gegen Helden nichts; ein Wesen mit mehreren Elementen kämpft mit dem, das gegen den Gegner am besten steht. Ist ein Angreifer gewählt, zeigen die Gegnerkarten ihren Konter gegen ihn (grün mehr, rot weniger); über einem Ziel zeigt der Angreifer seinen Angriff mit Vorteil, und wer stirbt, trägt die Marke „stirbt“
- Angriffe treffen erst Wesen (Wesen mit Spott zuerst); das Leben ist nur ohne Wesen angreifbar. Nichts schlägt durch - Angriffe treffen erst Wesen (Wesen mit Spott zuerst); das Leben ist nur ohne Wesen angreifbar. Nichts schlägt durch
- Verschmelzen: eine Karte auf eine andere ziehen und eine Sekunde halten, auf dem Brett wie auf der Hand (Gamepad: Y, „Verschmelzen mit …“). Das neue Wesen hat je Wert den besseren plus ein Drittel des schwächeren (aufgerundet), Kosten passend dazu, Elemente und Eigenschaften beider; kostet 1 Mana, verdoppelt je weiterer Verschmelzung (1, 2, 4, 8); drei oder mehr Elemente ergeben eine Chimäre - Verschmelzen: eine Karte auf eine andere ziehen und eine Sekunde halten, auf dem Brett wie auf der Hand (Gamepad: Y, „Verschmelzen mit …“). Das neue Wesen hat je Wert den besseren plus ein Drittel des schwächeren (aufgerundet), Kosten passend dazu, Elemente und Eigenschaften beider; kostet 1 Mana, verdoppelt je weiterer Verschmelzung (1, 2, 4, 8); drei oder mehr Elemente ergeben eine Chimäre
- Duell mit ausgeloster Zugreihenfolge, wer nicht beginnt, bekommt einmalig 1 Mana; die Hand fasst 5 Karten, das Feld 5 Wesen - Duell mit ausgeloster Zugreihenfolge, beide beginnen mit 1 Mana; die Hand fasst 5 Karten, das Feld 5 Wesen
- Schwierigkeit Leicht/Normal/Schwer: Gegner mit 25/30/35 Leben, einfache oder Vorausschau-KI, Münzen 0,75/1/1,5-fach - Schwierigkeit Leicht/Normal/Schwer: Gegner mit 25/30/35 Leben, einfache oder Vorausschau-KI, Münzen 0,75/1/1,5-fach
- KI mit Vorausschau in zwei Stufen und Zeitbudget (150 ms), sie rechnet im Hintergrund-Thread - KI mit Vorausschau in zwei Stufen und Zeitbudget (150 ms), sie rechnet im Hintergrund-Thread
- Kampagne: drei Kapitel mit je zehn Magiern, Bosse mit ein bis drei Merkmalen, eigenes Merkmal nach Kapitel 1 - Kampagne: drei Kapitel mit je zehn Magiern, Bosse mit ein bis drei Merkmalen, eigenes Merkmal nach Kapitel 1

View File

@@ -15,8 +15,8 @@
- Gesammelt wird die Seltenheitsstufe einer Karte (Common, Rare, Super Rare, Ultra Rare, Ultimate Rare), und die ist reine Optik wie bei Yu-Gi-Oh: silberner oder goldener Name, Holo-Bild, Relief, Funkeln, Schillern; auch im Netzwerkspiel sichtbar; Fusionen tragen die höhere Stufe der Eltern - Gesammelt wird die Seltenheitsstufe einer Karte (Common, Rare, Super Rare, Ultra Rare, Ultimate Rare), und die ist reine Optik wie bei Yu-Gi-Oh: silberner oder goldener Name, Holo-Bild, Relief, Funkeln, Schillern; auch im Netzwerkspiel sichtbar; Fusionen tragen die höhere Stufe der Eltern
- Booster öffnen (Chancen 50/25/15/8/2 %, mindestens eine Rare): höhere Stufen werten Karten auf, der Rest wird zu Staub (50/100/200/500/1000) - Booster öffnen (Chancen 50/25/15/8/2 %, mindestens eine Rare): höhere Stufen werten Karten auf, der Rest wird zu Staub (50/100/200/500/1000)
- Staub wertet gezielt auf (100/200/500/1000), eine Stufe abgeben bringt den Zerlegewert zurück - Staub wertet gezielt auf (100/200/500/1000), eine Stufe abgeben bringt den Zerlegewert zurück
- Kein Deckbau: alle ziehen vom gemeinsamen Stapel (jede Karte dreimal); zu Beginn fünf Karten; Ziehen kostet 1 Mana und bringt eine Karte; es kommt nur, was man sich in diesem Zug leisten kann - Kein Deckbau: alle ziehen vom gemeinsamen Stapel (jede Karte dreimal); zu Beginn fünf Karten, alle im ersten Zug spielbar; jeder Zug beginnt mit einer Karte vom Stapel (nicht bei voller Hand), weiteres Ziehen kostet 2 Mana; es kommt nur, was man sich in diesem Zug leisten kann
- Mana für Ziehen, Legen (Kosten = 2/3 Angriff + 1/3 Leben, abgerundet, mindestens 1), Verbessern und Verschmelzen; 5 im ersten Zug, dann je Runde eins mehr; der Rest verfällt; höchstens 5 Handkarten - Mana für Ziehen, Legen (Kosten = 2/3 Angriff + 1/3 Leben, abgerundet; 0/2 kostet 0), Verbessern und Verschmelzen; 1 im ersten Zug, dann je Runde eins mehr; der Rest verfällt; höchstens 5 Handkarten
- Karten haben nur Angriff und Leben; Eigenschaften kauft man per Rechtsklick für Mana dazu, auf Hand und Brett: Spott 2 (muss zuerst angegriffen werden), Flink 2 (greift sofort an), Regeneration 3 (heilt sich am Zugende ganz), Doppelschlag 4 - Karten haben nur Angriff und Leben; Eigenschaften kauft man per Rechtsklick für Mana dazu, auf Hand und Brett: Spott 2 (muss zuerst angegriffen werden), Flink 2 (greift sofort an), Regeneration 3 (heilt sich am Zugende ganz), Doppelschlag 4
- Gelegte Wesen greifen erst im nächsten Zug an, außer sie sind flink (auch im ersten Zug); verletzte Wesen heilen nicht von selbst - Gelegte Wesen greifen erst im nächsten Zug an, außer sie sind flink (auch im ersten Zug); verletzte Wesen heilen nicht von selbst
- Wesen lassen sich verdeckt legen (mittlere Maustaste oder Kartenmenü); aufdecken ist frei, frühestens im nächsten Zug, dann greift es sofort an; ein Angriff deckt auf - Wesen lassen sich verdeckt legen (mittlere Maustaste oder Kartenmenü); aufdecken ist frei, frühestens im nächsten Zug, dann greift es sofort an; ein Angriff deckt auf
@@ -28,6 +28,6 @@
- Kombos sind die Namen verschmolzener Wesen (Feuer + Wasser wird Dampf, gleiche Elemente werden Inferno, Tiefsee, Berg, Orkan) - Kombos sind die Namen verschmolzener Wesen (Feuer + Wasser wird Dampf, gleiche Elemente werden Inferno, Tiefsee, Berg, Orkan)
- Zwei eigene offene Wesen eines Elements: Sondereffekt für alle eigenen Wesen des Elements (Erde Spott, Luft Flink, Wasser Regeneration, Feuer Doppelschlag) - Zwei eigene offene Wesen eines Elements: Sondereffekt für alle eigenen Wesen des Elements (Erde Spott, Luft Flink, Wasser Regeneration, Feuer Doppelschlag)
- Drei offene Wesen eines Elements auf dem Tisch: +1/+1 für das Element, kein Malus; die Karte zeigt vorher, was sie bekäme; verschmolzene Wesen zählen für alle ihre Elemente und bekommen alle Effekte und Boni - Drei offene Wesen eines Elements auf dem Tisch: +1/+1 für das Element, kein Malus; die Karte zeigt vorher, was sie bekäme; verschmolzene Wesen zählen für alle ihre Elemente und bekommen alle Effekte und Boni
- Elementvorteil im Kreis: Wasser schlägt Feuer, Feuer schlägt Luft, Luft schlägt Erde, Erde schlägt Wasser (der Angreifer macht 1 Schaden mehr im Vorteil, 1 weniger im Nachteil, der Konter bleibt gleich; ein Wesen mit mehreren Elementen kämpft mit dem, das gegen den Gegner am besten steht) - Elementvorteil im Kreis: Wasser schlägt Feuer, Feuer schlägt Luft, Luft schlägt Erde, Erde schlägt Wasser (wer im Vorteil ist, macht 1 Schaden mehr, wer im Nachteil ist, 1 weniger, beim Angriff wie beim Konter; ein Wesen mit mehreren Elementen kämpft mit dem, das gegen den Gegner am besten steht)
- Verschmelzen: eine Karte auf eine andere ziehen und eine Sekunde stillhalten, auf dem Brett oder auf der Hand (Gamepad: über das Kartenmenü); das neue erbt Elemente und Eigenschaften beider, je Wert den besseren plus ein Drittel des schwächeren (aufgerundet), Kosten folgen den Werten; Fusionskosten 1 Mana, verdoppelt je weiterer Verschmelzung - Verschmelzen: eine Karte auf eine andere ziehen und eine Sekunde stillhalten, auf dem Brett oder auf der Hand (Gamepad: über das Kartenmenü); das neue erbt Elemente und Eigenschaften beider, je Wert den besseren plus ein Drittel des schwächeren (aufgerundet), Kosten folgen den Werten; Fusionskosten 1 Mana, verdoppelt je weiterer Verschmelzung
- Alle Karten sind Wesen: keine Zauber, keine Fallen; Effekte lösen beim Ausspielen aus - Alle Karten sind Wesen: keine Zauber, keine Fallen; Effekte lösen beim Ausspielen aus

View File

@@ -769,6 +769,46 @@
], ],
"attack": 5, "attack": 5,
"health": 4 "health": 4
},
{
"id": "f_funke",
"name": "Glutfunke",
"name_en": "Ember Spark",
"elements": [
"Feuer"
],
"attack": 0,
"health": 2
},
{
"id": "w_tropfen",
"name": "Tautropfen",
"name_en": "Dewdrop",
"elements": [
"Wasser"
],
"attack": 0,
"health": 2
},
{
"id": "e_kruemel",
"name": "Erdkrümel",
"name_en": "Soil Crumb",
"elements": [
"Erde"
],
"attack": 0,
"health": 2
},
{
"id": "l_hauch",
"name": "Windhauch",
"name_en": "Breeze",
"elements": [
"Luft"
],
"attack": 0,
"health": 2
} }
] ]
} }

View File

@@ -39,7 +39,7 @@ Stand: September 2026. Kein Enddesign, sondern die Richtung mit umgesetztem Entw
Nur, was gerade zählt, ist zu sehen. Von unten nach oben: Nur, was gerade zählt, ist zu sehen. Von unten nach oben:
- **Hand** als bis zu fünf große Karten, links der Zieh-Stapel mit „◆ 1“ (Klick zieht), rechts - **Hand** als bis zu fünf große Karten, links der Zieh-Stapel mit „◆ 2“ (Klick zieht), rechts
„Zug beenden“. „Zug beenden“.
- **Verschmelzen** per Ziehen: eine Hand- oder Brettkarte auf eine passende ziehen, ein - **Verschmelzen** per Ziehen: eine Hand- oder Brettkarte auf eine passende ziehen, ein
goldener Ring füllt sich eine Sekunde lang, dann verschmelzen die beiden. Mit Gamepad über goldener Ring füllt sich eine Sekunde lang, dann verschmelzen die beiden. Mit Gamepad über

View File

@@ -94,9 +94,9 @@ static func best_element(own: Array, other: Array) -> String:
return best return best
## Kosten einer Karte: zwei Drittel Angriff plus ein Drittel Leben, abgerundet, mindestens 1. ## Kosten einer Karte: zwei Drittel Angriff plus ein Drittel Leben, abgerundet (0/2 kostet nichts).
static func cost_of(attack: int, health: int) -> int: static func cost_of(attack: int, health: int) -> int:
return maxi(1, (2 * attack + health) / 3) return (2 * attack + health) / 3
## Wert eines verschmolzenen Wesens: der bessere Wert plus ein Drittel des schwächeren, ## Wert eines verschmolzenen Wesens: der bessere Wert plus ein Drittel des schwächeren,

View File

@@ -6,8 +6,8 @@ extends RefCounted
const MAX_BOARD := 5 const MAX_BOARD := 5
const MAX_HAND := 5 const MAX_HAND := 5
const START_HAND := 5 # Startkarten, alle im ersten Zug bezahlbar 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 START_MANA := 1 # Mana in Zug 1; jede Runde eins mehr, ohne Grenze; Rest verfällt
const DRAW_COST := 1 # Ziehen: eine bezahlbare Karte vom Stapel const DRAW_COST := 2 # Ziehen: eine bezahlbare Karte vom Stapel
const START_LIFE := 30 const START_LIFE := 30
const ELEMENT_PAIR := 2 # so viele eigene offene Wesen eines Elements: Sondereffekt des Elements 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 const ELEMENT_STACK := 3 # so viele offene Wesen eines Elements auf dem Tisch: +1/+1
@@ -131,7 +131,6 @@ class Player extends RefCounted:
var board: Array = [] var board: Array = []
var energy := 0 # Mana in diesem Zug; der Rest verfällt var energy := 0 # Mana in diesem Zug; der Rest verfällt
var max_energy := 0 # Mana zu Zugbeginn (START_MANA + eigene Züge) 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 order := 0 # Platz in der Zugreihenfolge
var perks: Array = [] # Boss-Merkmale (siehe PERKS) var perks: Array = [] # Boss-Merkmale (siehe PERKS)
var looks := {} # Karten-ID -> Seltenheitsstufe, reine Optik (siehe Profile) var looks := {} # Karten-ID -> Seltenheitsstufe, reine Optik (siehe Profile)
@@ -161,7 +160,6 @@ class Player extends RefCounted:
p.hand = hand.duplicate() p.hand = hand.duplicate()
p.energy = energy p.energy = energy
p.max_energy = max_energy p.max_energy = max_energy
p.start_bonus = start_bonus
p.order = order p.order = order
p.perks = perks.duplicate() p.perks = perks.duplicate()
p.looks = looks p.looks = looks
@@ -277,9 +275,7 @@ func start(fixed_order: bool = false) -> void:
for i in players.size(): for i in players.size():
var p: Player = players[i] var p: Player = players[i]
p.order = i p.order = i
# Wer nicht beginnt, bekommt im ersten Zug einmalig 1 Mana extra. for c in draw_cards(START_HAND, START_MANA):
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()) p.hand.append(c.copy())
turn = 1 turn = 1
current_idx = 0 current_idx = 0
@@ -349,16 +345,19 @@ func begin_turn(p: Player) -> void:
p.energy = p.max_energy + (1 if "extra_mana" in p.perks else 0) # der Rest vom letzten Zug ist verfallen 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: if p.turns == 0 and "start_energie" in p.perks:
p.energy += 2 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: for c in p.board:
c.ready = true # lag eine Runde: greift jetzt an, Verdecktes darf aufgedeckt werden c.ready = true # lag eine Runde: greift jetzt an, Verdecktes darf aufgedeckt werden
c.attacks_done = 0 c.attacks_done = 0
p.turns += 1 p.turns += 1
say("--- Zug %d: %s ---" % [turn, p.name]) say("--- Zug %d: %s ---" % [turn, p.name])
emit({"type": "turn", "player": players.find(p)}) emit({"type": "turn", "player": players.find(p)})
# Jeder Zug beginnt mit einer Karte vom Stapel, die man sich in diesem Zug leisten kann.
if p.hand.size() < MAX_HAND:
p.hand.append(draw_cards(1, p.max_energy)[0].copy())
say("%s zieht eine Karte." % p.name)
emit({"type": "draw", "player": players.find(p)})
else:
say("%s hat die Hand voll und zieht nicht." % p.name)
## Zugende: Wesen mit Regeneration heilen ganz, dann ist der andere dran. ## Zugende: Wesen mit Regeneration heilen ganz, dann ist der andere dran.
@@ -661,15 +660,15 @@ func attack(cr: Creature, target) -> String:
say("%s greift %s an." % [cr.name, target.name]) say("%s greift %s an." % [cr.name, target.name])
emit({"type": "attack", "attacker": ref_of(cr), "target": ref_of(target)}) emit({"type": "attack", "attacker": ref_of(cr), "target": ref_of(target)})
damage(target, cr.power() + matchup_bonus(cr, 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 if target is Creature and target.power() > 0: # wer nichts austeilt, kontert auch nicht
damage(cr, target.power()) damage(cr, target.power() + matchup_bonus(target, cr))
cleanup() cleanup()
check_end() check_end()
return "" return ""
## Elementvorteil beim Zuschlagen: +1 im Vorteil, -1 im Nachteil (die Verteidigung des ## Elementvorteil beim Zuschlagen und beim Konter: +1 im Vorteil, -1 im Nachteil,
## Ziels), gegen Helden gibt es keinen. ## gegen Helden gibt es keinen.
static func matchup_bonus(source: Creature, target) -> int: static func matchup_bonus(source: Creature, target) -> int:
if not (target is Creature): if not (target is Creature):
return 0 return 0

View File

@@ -26,7 +26,7 @@ static func snapshot(g: Game, viewer: int) -> Dictionary:
"max_health": 0 if masked else c.max_health, "attacks_done": c.attacks_done, "max_health": 0 if masked else c.max_health, "attacks_done": c.attacks_done,
"ready": c.ready, "hidden": c.hidden, "aura": c.aura}) "ready": c.ready, "hidden": c.hidden, "aura": c.aura})
ps.append({"name": p.name, "title": p.title, "is_human": p.is_human, "life": p.life, "max_life": p.max_life, 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, "start_bonus": p.start_bonus, "energy": p.energy, "max_energy": p.max_energy,
"order": p.order, "perks": p.perks, "looks": p.looks, "turns": p.turns, "order": p.order, "perks": p.perks, "looks": p.looks, "turns": p.turns,
"hand": p.hand.map(card_ref) if mine else p.hand.size(), "board": board}) "hand": p.hand.map(card_ref) if mine else p.hand.size(), "board": board})
var evs := [] var evs := []
@@ -50,7 +50,6 @@ static func from_snapshot(d: Dictionary) -> Game:
p.max_life = int(pd.max_life) p.max_life = int(pd.max_life)
p.energy = int(pd.energy) p.energy = int(pd.energy)
p.max_energy = int(pd.max_energy) p.max_energy = int(pd.max_energy)
p.start_bonus = int(pd.start_bonus)
p.order = int(pd.order) p.order = int(pd.order)
p.perks = Array(pd.perks) p.perks = Array(pd.perks)
for cid in pd.get("looks", {}): for cid in pd.get("looks", {}):

View File

@@ -16,6 +16,7 @@ var game: Game
var perspective: Game.Player # der Mensch, dessen Hand unten liegt var perspective: Game.Player # der Mensch, dessen Hand unten liegt
var fx: BattleFx var fx: BattleFx
var selected = null # Game.Creature var selected = null # Game.Creature
var hover_target = null # fremdes Wesen unter der Maus: Vorschau des Angriffs mit dem gewählten Wesen
var fuse_first = null # Karte oder Wesen, für das im Menü „Verschmelzen mit …“ gewählt wurde var fuse_first = null # Karte oder Wesen, für das im Menü „Verschmelzen mit …“ gewählt wurde
var hold_time := 1.0 # so lange still über dem Ziel halten, dann verschmelzen die beiden var hold_time := 1.0 # so lange still über dem Ziel halten, dann verschmelzen die beiden
var press_target = null # Karte oder Wesen unter der gedrückten linken Maustaste var press_target = null # Karte oder Wesen unter der gedrückten linken Maustaste
@@ -928,6 +929,23 @@ func matchup_for(cr: Game.Creature) -> int:
return 0 return 0
## Vorschau für den Angriff auf das Wesen unter der Maus: der Angreifer zeigt seinen Angriff mit
## Elementvorteil, und wer dabei stirbt, bekommt die Marke. Verdeckte Ziele bleiben unbekannt.
func update_attack_preview() -> void:
var target = hover_target
if selected == null or target == null or target.hidden or not game.can_attack(selected, target)[0]:
target = null
for v in enemy_views:
v.doomed = target != null and v.creature == target and selected.power() + Game.matchup_bonus(selected, target) >= target.health
v.queue_redraw()
for v in own_views:
var mine: bool = v.creature == selected
v.attack_shift = Game.matchup_bonus(selected, target) if mine and target != null else 0
var counter: int = target.power() + Game.matchup_bonus(target, selected) if target != null and target.power() > 0 else 0
v.doomed = mine and target != null and counter >= selected.health
v.queue_redraw()
class Deck extends Control: class Deck extends Control:
signal clicked signal clicked
var enabled := true var enabled := true
@@ -1078,6 +1096,8 @@ func refresh() -> void:
tile_layer.add_child(tile) tile_layer.add_child(tile)
var looks: Dictionary = p.looks var looks: Dictionary = p.looks
tile.hover.connect(func(card, entered): tile.hover.connect(func(card, entered):
hover_target = tile.creature if entered else null
update_attack_preview()
if tile.secret: if tile.secret:
show_preview(null, false) show_preview(null, false)
else: else:
@@ -1123,6 +1143,7 @@ func refresh() -> void:
var t: CreatureView = tiles[uid] var t: CreatureView = tiles[uid]
tiles.erase(uid) tiles.erase(uid)
fx.fade_out(t) fx.fade_out(t)
update_attack_preview()
deck.visible = not spectating() deck.visible = not spectating()
deck.enabled = mine and g.can_draw(h)[0] deck.enabled = mine and g.can_draw(h)[0]
deck.count = g.deck.size() deck.count = g.deck.size()

View File

@@ -161,7 +161,7 @@ func _draw_full(rect: Rect2, frame: Color, seed: int) -> void:
fx, HORIZONTAL_ALIGNMENT_LEFT, box.size.x - 8, tsize, max_lines, Style.TEXT) fx, HORIZONTAL_ALIGNMENT_LEFT, box.size.x - 8, tsize, max_lines, Style.TEXT)
# Werte springen aus den unteren Ecken, dazwischen der Edelstein # Werte springen aus den unteren Ecken, dazwischen der Edelstein
Style.stat_circle(self, Vector2(r_stat - 2, size.y - r_stat + 2), r_stat, str(card.attack), Style.AMBER, f_stat) Style.stat_circle(self, Vector2(r_stat - 2, size.y - r_stat + 2), r_stat, str(card.attack), Style.AMBER, f_stat)
Style.stat_circle(self, Vector2(size.x - r_stat + 2, size.y - r_stat + 2), r_stat, str(card.health), Style.CRIMSON, f_stat) Style.stat_circle(self, Vector2(size.x - r_stat + 2, size.y - r_stat + 2), r_stat, str(card.health), Style.LIFE, f_stat)
if bonus > 0: if bonus > 0:
Style.text_outlined(self, "+%d/+%d" % [bonus, bonus], f_text, Style.GREEN, Vector2(r_stat * 2 + 4, size.y - r_stat + 2), "midleft", 3) Style.text_outlined(self, "+%d/+%d" % [bonus, bonus], f_text, Style.GREEN, Vector2(r_stat * 2 + 4, size.y - r_stat + 2), "midleft", 3)
Style.rarity_gem(self, rarity, Vector2(size.x / 2, size.y - r_stat + 1), 8 if big else 6, seed) Style.rarity_gem(self, rarity, Vector2(size.x / 2, size.y - r_stat + 1), 8 if big else 6, seed)
@@ -188,7 +188,7 @@ func _draw_compact(rect: Rect2, frame: Color, seed: int) -> void:
Style.keyword_icon(self, k, Vector2(x + 6, size.y - 13), 6) Style.keyword_icon(self, k, Vector2(x + 6, size.y - 13), 6)
x += 18 x += 18
Style.stat_circle(self, Vector2(11, size.y - 11), 10, str(card.attack), Style.AMBER, 12) Style.stat_circle(self, Vector2(11, size.y - 11), 10, str(card.attack), Style.AMBER, 12)
Style.stat_circle(self, Vector2(size.x - 11, size.y - 11), 10, str(card.health), Style.CRIMSON, 12) Style.stat_circle(self, Vector2(size.x - 11, size.y - 11), 10, str(card.health), Style.LIFE, 12)
if bonus > 0: if bonus > 0:
Style.text_outlined(self, "+%d" % bonus, 10, Style.GREEN, Vector2(4, size.y - 29), "midleft", 3) Style.text_outlined(self, "+%d" % bonus, 10, Style.GREEN, Vector2(4, size.y - 29), "midleft", 3)
Style.rarity_sparkle(self, rect, rarity, seed) Style.rarity_sparkle(self, rect, rarity, seed)

View File

@@ -11,7 +11,9 @@ var width := 2
var flash := false # kurz rot: wurde gerade angegriffen var flash := false # kurz rot: wurde gerade angegriffen
var slot: Vector2 # Zielposition im Raster var slot: Vector2 # Zielposition im Raster
var busy := false # wird gerade animiert (kein Neupositionieren) var busy := false # wird gerade animiert (kein Neupositionieren)
var matchup := 0 # Elementvorteil des gewählten Angreifers gegen dieses Wesen (+1/-1) var matchup := 0 # Elementvorteil des gewählten Angreifers gegen dieses fremde Wesen (+1/-1); die Werte zeigen dann, was ihn erwartet
var attack_shift := 0 # eigener Angreifer: Elementvorteil gegen das Ziel unter der Maus
var doomed := false # stirbt bei dem Angriff, der gerade unter der Maus liegt
var secret := false # verdecktes fremdes Wesen: nur der Rücken ist zu sehen var secret := false # verdecktes fremdes Wesen: nur der Rücken ist zu sehen
var hold := 0.0 # Fortschritt des Verschmelzens, wenn eine Karte darüber gehalten wird (0..1) var hold := 0.0 # Fortschritt des Verschmelzens, wenn eine Karte darüber gehalten wird (0..1)
@@ -66,14 +68,24 @@ func _draw() -> void:
bcol = Style.RED bcol = Style.RED
Style.border(self, rect, bcol, 4 if flash else width, 6) Style.border(self, rect, bcol, 4 if flash else width, 6)
var fs := 11 if small else 13 var fs := 11 if small else 13
var name_w: float = size.x - (30 if matchup != 0 else 6) # Platz für den Vorteilskreis oben rechts var name_w: float = size.x - 6
var nf := Style.fit_size(creature.card.title(), true, fs, name_w) var nf := Style.fit_size(creature.card.title(), true, fs, name_w)
draw_rect(Rect2(2, 2, size.x - 4, Style.fs(fs) + 8), Color(0.05, 0.05, 0.08, 0.7)) draw_rect(Rect2(2, 2, size.x - 4, Style.fs(fs) + 8), Color(0.05, 0.05, 0.08, 0.7))
Style.text(self, Style.fit(creature.card.title(), true, Style.fs(nf), name_w), nf, Style.rarity_name_color(rar, seed), Vector2(3 + name_w / 2, 6), "midtop", true) Style.text(self, Style.fit(creature.card.title(), true, Style.fs(nf), name_w), nf, Style.rarity_name_color(rar, seed), Vector2(3 + name_w / 2, 6), "midtop", true)
# Elementbonus als ein Abzeichen rechts unter dem Namen; die Symbole rücken dafür nach links
var badge := ""
var bw := 0.0
if creature.aura != 0:
badge = "+%d/+%d" % [creature.aura, creature.aura] if creature.aura > 0 else "−%d/−%d" % [-creature.aura, -creature.aura]
bw = Style.FONT_BOLD.get_string_size(badge, HORIZONTAL_ALIGNMENT_LEFT, -1, Style.fs(10)).x + 8
var by := Style.fs(fs) + 13
draw_rect(Rect2(size.x - bw - 4, by, bw, 16), Color(0.05, 0.05, 0.08, 0.75))
Style.text(self, badge, 10, Style.GREEN if creature.aura > 0 else Style.RED, Vector2(size.x - 4 - bw / 2, by + 8), "center", true)
# Eigenschaften als Symbole: gekaufte weiß, die vom Elementpaar in der Elementfarbe # Eigenschaften als Symbole: gekaufte weiß, die vom Elementpaar in der Elementfarbe
var icon := 7.0 if small else 9.0 var icon := 7.0 if small else 9.0
var kws := creature.keywords() var kws := creature.keywords()
var x := size.x / 2 - (kws.size() * (icon * 2 + 6) - 6) / 2.0 var avail := size.x - (bw + 8 if badge != "" else 0.0)
var x := avail / 2 - (kws.size() * (icon * 2 + 6) - 6) / 2.0
var y := Style.fs(fs) + 14 + icon var y := Style.fs(fs) + 14 + icon
for k in kws: for k in kws:
var col := Color("f2f2f6") var col := Color("f2f2f6")
@@ -83,11 +95,14 @@ func _draw() -> void:
col = Style.lighter(Cards.ELEMENT_COLORS[e], 0.45) col = Style.lighter(Cards.ELEMENT_COLORS[e], 0.45)
Style.keyword_icon(self, k, Vector2(x + icon, y), icon, col) Style.keyword_icon(self, k, Vector2(x + icon, y), icon, col)
x += icon * 2 + 6 x += icon * 2 + 6
_draw_stats(15 if small else 17, 10 if small else 12, 12.0 if small else 14.0) _draw_stats(15 if small else 17, 12.0 if small else 14.0)
Style.rarity_gem(self, rar, Vector2(size.x / 2, size.y - 9), 5, seed) Style.rarity_gem(self, rar, Vector2(size.x / 2, size.y - 9), 5, seed)
if creature.hidden: if creature.hidden:
draw_rect(rect, Color(0.05, 0.05, 0.1, 0.55)) draw_rect(rect, Color(0.05, 0.05, 0.1, 0.55))
Style.text_outlined(self, tr("verdeckt"), 12, Style.TEXT, Vector2(size.x / 2, size.y / 2), "center", 3) Style.text_outlined(self, tr("verdeckt"), 12, Style.TEXT, Vector2(size.x / 2, size.y / 2), "center", 3)
if doomed:
draw_rect(Rect2(0, size.y * 0.5 - 12, size.x, 24), Color(0.05, 0.02, 0.02, 0.8))
Style.text_outlined(self, tr("stirbt"), 14, Style.RED, Vector2(size.x / 2, size.y * 0.5), "center", 3)
Style.rarity_sparkle(self, rect, rar, seed) Style.rarity_sparkle(self, rect, rar, seed)
_draw_hold() _draw_hold()
@@ -110,21 +125,26 @@ func _draw_back(rect: Rect2) -> void:
draw_style_box(Style.box(Color("3c285a"), 6, bcol, 4 if flash else maxi(width, 2)), rect) draw_style_box(Style.box(Color("3c285a"), 6, bcol, 4 if flash else maxi(width, 2)), rect)
Style.border(self, rect.grow(-10), Color("5a3c82"), 2, 6) Style.border(self, rect.grow(-10), Color("5a3c82"), 2, 6)
Style.text(self, "?", 36, Style.GOLD, size / 2, "center", true) Style.text(self, "?", 36, Style.GOLD, size / 2, "center", true)
if matchup != 0:
pass # unbekanntes Element: kein Vorteil anzeigen
## Angriff (grün, wenn der Elementbonus ihn hebt), Leben, Elementvorteil des Angreifers. ## Angriff und Leben für den Angriff, der gerade vorbereitet wird. Fremdes Wesen bei gewähltem
func _draw_stats(f: int, f_small: int, r: float) -> void: ## Angreifer: sein Konter gegen ihn (im Nachteil 1 weniger, wer 0 Angriff hat, kontert gar nicht),
var extra := creature.power() - creature.attack ## das Leben bleibt echt. Eigener Angreifer über einem Ziel: sein Angriff mit Elementvorteil.
var atk_col := Style.GREEN if extra > 0 else (Style.RED if extra < 0 else Style.AMBER) func shown_stats() -> Array:
Style.stat_circle(self, Vector2(r - 1, size.y - r + 1), r, str(creature.power()), atk_col, f)
if extra != 0:
Style.text_outlined(self, ("+%d" % extra) if extra > 0 else ("−%d" % -extra), f_small, Style.GREEN if extra > 0 else Style.RED, Vector2(r * 2 + 3, size.y - r + 1), "midleft", 3)
var hp_col := Style.CRIMSON if creature.health < creature.max_health else Color("b84a4a")
Style.stat_circle(self, Vector2(size.x - r + 1, size.y - r + 1), r, str(creature.health), hp_col, f)
if matchup != 0: if matchup != 0:
var c := Vector2(size.x - 12, 12) var atk := 0 if creature.power() <= 0 else maxi(0, creature.power() - matchup)
draw_circle(c, 11, Color("14141c")) return [atk, creature.health]
Style.text(self, ("+%d" % matchup) if matchup > 0 else ("−%d" % -matchup), f_small + 1, if attack_shift != 0:
Style.GREEN if matchup > 0 else Style.RED, c, "center", true) return [maxi(0, creature.power() + attack_shift), creature.health]
return [creature.power(), creature.health]
## Angriff gelb, Leben blau; die Zahl wird grün, wenn sie für den aktuellen Angriff steigt, rot, wenn sie sinkt.
func _draw_stats(f: int, r: float) -> void:
var shown := shown_stats()
var atk: int = shown[0]
var hp: int = shown[1]
var atk_text := Style.GREEN if atk > creature.power() else (Style.RED if atk < creature.power() else Color.WHITE)
var hp_text := Style.GREEN if hp > creature.health else (Style.RED if hp < creature.health else Color.WHITE)
Style.stat_circle(self, Vector2(r - 1, size.y - r + 1), r, str(atk), Style.AMBER, f, atk_text)
Style.stat_circle(self, Vector2(size.x - r + 1, size.y - r + 1), r, str(hp), Style.LIFE, f, hp_text)

View File

@@ -68,9 +68,6 @@ func _draw() -> void:
draw_circle(c, 4.5, Color(0, 0, 0, 0.35)) draw_circle(c, 4.5, Color(0, 0, 0, 0.35))
draw_arc(c, 4.5, 0, TAU, 16, Color(col, 0.6), 1.0) draw_arc(c, 4.5, 0, TAU, 16, Color(col, 0.6), 1.0)
px += Game.ELEMENT_STACK * 13 + 2 px += Game.ELEMENT_STACK * 13 + 2
if n > Game.ELEMENT_STACK:
Style.text(self, "+%d" % (n - Game.ELEMENT_STACK), 11, Style.MUTED, Vector2(px, cy), "midleft")
px += 18
if paired(e): if paired(e):
Style.keyword_icon(self, Cards.ELEMENT_KEYWORD[e], Vector2(px + 10, cy), 7, Style.lighter(col, 0.45)) Style.keyword_icon(self, Cards.ELEMENT_KEYWORD[e], Vector2(px + 10, cy), 7, Style.lighter(col, 0.45))
if strong(e): if strong(e):

View File

@@ -19,7 +19,7 @@ const YELLOW := Color("ffe65a")
const ORANGE := Color("ff963c") const ORANGE := Color("ff963c")
const PURPLE := Color("8c78dc") const PURPLE := Color("8c78dc")
const AMBER := Color("f0b429") # Angriff const AMBER := Color("f0b429") # Angriff
const CRIMSON := Color("d64545") # Leben const LIFE := Color("4a86d8") # Leben
const MANA := Color("3a78d2") # Kostenkristall const MANA := Color("3a78d2") # Kostenkristall
const PRIMARY := Color("285a3c") # Hauptaktion const PRIMARY := Color("285a3c") # Hauptaktion
const DANGER := Color("5a2828") const DANGER := Color("5a2828")
@@ -476,11 +476,11 @@ static func cost_crystal(ci: CanvasItem, c: Vector2, r: float, cost: int, font:
## Wertkreis (Angriff gelb, Leben rot) mit Zahl. ## Wertkreis (Angriff gelb, Leben rot) mit Zahl.
static func stat_circle(ci: CanvasItem, c: Vector2, r: float, value: String, color: Color, font: int) -> void: static func stat_circle(ci: CanvasItem, c: Vector2, r: float, value: String, color: Color, font: int, text_col: Color = Color.WHITE) -> void:
ci.draw_circle(c, r + 2, Color("14141c")) ci.draw_circle(c, r + 2, Color("14141c"))
ci.draw_circle(c, r, color) ci.draw_circle(c, r, color)
ci.draw_circle(c + Vector2(-r * 0.25, -r * 0.3), r * 0.45, Color(1, 1, 1, 0.16)) ci.draw_circle(c + Vector2(-r * 0.25, -r * 0.3), r * 0.45, Color(1, 1, 1, 0.16))
text_outlined(ci, value, font, Color.WHITE, c, "center", 3) text_outlined(ci, value, font, text_col, c, "center", 3)
## Eigenschaft als Symbol auf dunklem Kreis: Spott Ausrufezeichen, Flink Doppelpfeil, ## Eigenschaft als Symbol auf dunklem Kreis: Spott Ausrufezeichen, Flink Doppelpfeil,

View File

@@ -90,14 +90,14 @@ func _initialize() -> void:
func test_kartenpool() -> void: func test_kartenpool() -> void:
check(Cards.all().size() == 77, "77 Karten") check(Cards.all().size() == 81, "81 Karten")
check(Cards.all().all(func(c): return c.elements.size() == 1 and c.elements[0] in Cards.ELEMENTS), "jede Karte hat genau ein Element") check(Cards.all().all(func(c): return c.elements.size() == 1 and c.elements[0] in Cards.ELEMENTS), "jede Karte hat genau ein Element")
for e in Cards.ELEMENTS: for e in Cards.ELEMENTS:
check(Cards.all().any(func(c): return c.elements == [e]), "Element vorhanden: " + e) check(Cards.all().any(func(c): return c.elements == [e]), "Element vorhanden: " + e)
for c in Cards.all(): for c in Cards.all():
check(c.health > 0 and c.cost == maxi(1, floori((2 * c.attack + c.health) / 3.0)), "Kosten = (2 · Angriff + Leben) / 3, abgerundet: " + c.id) check(c.health > 0 and c.cost == floori((2 * c.attack + c.health) / 3.0), "Kosten = (2 · Angriff + Leben) / 3, abgerundet: " + c.id)
check(c.keywords.is_empty() and c.text() == "", "Karten kommen ohne Eigenschaften: " + c.id) check(c.keywords.is_empty() and c.text() == "", "Karten kommen ohne Eigenschaften: " + c.id)
check(C("f_kobold").cost == 1 and C("f_wolf").cost == 2 and C("ff_phoenix").cost == 7 and C("e_wall").cost == 1 and C("f_drache").cost == 5, "Kostenbeispiele") check(C("f_funke").cost == 0 and C("f_kobold").cost == 1 and C("f_wolf").cost == 2 and C("ff_phoenix").cost == 7 and C("e_wall").cost == 1 and C("f_drache").cost == 5, "Kostenbeispiele, 0/2 kostet nichts")
var a := C("f_wolf") var a := C("f_wolf")
var b := C("f_wolf") var b := C("f_wolf")
a.keywords.append("Spott") a.keywords.append("Spott")
@@ -111,7 +111,7 @@ func test_kombos() -> void:
check(Cards.combo_name(["Feuer", "Wasser", "Erde"]) == "Chimäre", "drei Elemente sind eine Chimäre") check(Cards.combo_name(["Feuer", "Wasser", "Erde"]) == "Chimäre", "drei Elemente sind eine Chimäre")
## Mana: 5 im ersten Zug, jede Runde eins mehr, der Rest verfällt; Legen kostet die Kartenkosten. ## Mana: 1 im ersten Zug, jede Runde eins mehr, der Rest verfällt; Legen kostet die Kartenkosten.
func test_mana_und_kosten() -> void: func test_mana_und_kosten() -> void:
var s := setup(["f_wolf", "f_drache"]) var s := setup(["f_wolf", "f_drache"])
var g: Game = s[0] var g: Game = s[0]
@@ -120,10 +120,10 @@ func test_mana_und_kosten() -> void:
h.max_energy = 0 h.max_energy = 0
h.turns = 0 h.turns = 0
g.begin_turn(h) g.begin_turn(h)
check(h.energy == Game.START_MANA and h.max_energy == Game.START_MANA, "erster Zug: 5 Mana") check(h.energy == Game.START_MANA and h.max_energy == Game.START_MANA, "erster Zug: 1 Mana")
h.energy = 3 # Rest h.energy = 3 # Rest
g.begin_turn(h) g.begin_turn(h)
check(h.energy == Game.START_MANA + 1, "zweiter Zug: 6 Mana, der Rest ist verfallen") check(h.energy == Game.START_MANA + 1, "zweiter Zug: 2 Mana, der Rest ist verfallen")
for i in 20: for i in 20:
g.begin_turn(h) g.begin_turn(h)
check(h.energy == Game.START_MANA + 21, "keine Obergrenze") check(h.energy == Game.START_MANA + 21, "keine Obergrenze")
@@ -133,7 +133,8 @@ func test_mana_und_kosten() -> void:
check(Cards.fusion_value(4, 2) == 5 and Cards.fusion_value(8, 1) == 9 and Cards.fusion_value(2, 2) == 3 and Cards.fusion_value(0, 4) == 4 and Cards.fusion_value(8, 8) == 11, "Fusionswert: besserer Wert plus ein Drittel des schwächeren") check(Cards.fusion_value(4, 2) == 5 and Cards.fusion_value(8, 1) == 9 and Cards.fusion_value(2, 2) == 3 and Cards.fusion_value(0, 4) == 4 and Cards.fusion_value(8, 8) == 11, "Fusionswert: besserer Wert plus ein Drittel des schwächeren")
## Start: fünf bezahlbare Karten, keine Wahl. Ziehen: 1 Mana, eine bezahlbare Karte vom Stapel. ## Start: fünf im ersten Zug bezahlbare Karten, keine Wahl. Jeder Zug beginnt mit einer Karte;
## Ziehen: 2 Mana, eine weitere bezahlbare Karte vom Stapel.
func test_ziehen() -> void: func test_ziehen() -> void:
var r := RandomNumberGenerator.new() var r := RandomNumberGenerator.new()
r.seed = 1 r.seed = 1
@@ -141,14 +142,17 @@ func test_ziehen() -> void:
check(g.deck.size() == Cards.all().size() * DeckGen.COPIES - 2 * Game.START_HAND, "Stapel: jede Karte dreimal, zehn schon ausgegeben") check(g.deck.size() == Cards.all().size() * DeckGen.COPIES - 2 * Game.START_HAND, "Stapel: jede Karte dreimal, zehn schon ausgegeben")
for p in g.players: for p in g.players:
check(p.hand.size() == Game.START_HAND, "Start: fünf Karten auf der Hand") check(p.hand.size() == Game.START_HAND, "Start: fünf Karten auf der Hand")
check(p.hand.all(func(c): return c.cost <= Game.START_MANA + p.start_bonus), "nur Karten, die im ersten Zug spielbar sind") check(p.hand.all(func(c): return c.cost <= Game.START_MANA), "nur Karten, die im ersten Zug spielbar sind")
check(p.hand.all(func(c): return c != Cards.by_id(c.id)), "eigene Exemplare") check(p.hand.all(func(c): return c != Cards.by_id(c.id)), "eigene Exemplare")
var first: Game.Player = g.current() var first: Game.Player = g.current()
check(g.draw(first) == "Deine Hand ist voll", "fünf Handkarten sind das Limit") check(g.draw(first) == "Deine Hand ist voll", "fünf Handkarten sind das Limit")
first.hand.pop_back() first.hand.pop_back()
check(g.draw(first) == "Nicht genug Mana", "im ersten Zug reicht das Mana nicht zum Ziehen")
first.energy = 3
first.max_energy = 3
var mana := first.energy var mana := first.energy
var deck_before := g.deck.size() var deck_before := g.deck.size()
check(g.draw(first) == "" and first.energy == mana - 1 and first.hand.size() == Game.START_HAND, "gezogen: 1 Mana, eine Karte") check(g.draw(first) == "" and first.energy == mana - Game.DRAW_COST and first.hand.size() == Game.START_HAND, "gezogen: 2 Mana, eine Karte")
check(first.hand.back().cost <= first.max_energy and g.deck.size() == deck_before - 1, "bezahlbar; zu teure liegen wieder unten im Stapel") check(first.hand.back().cost <= first.max_energy and g.deck.size() == deck_before - 1, "bezahlbar; zu teure liegen wieder unten im Stapel")
first.energy = 0 first.energy = 0
first.hand.pop_back() first.hand.pop_back()
@@ -165,10 +169,25 @@ func test_ziehen() -> void:
var seen_expensive := false var seen_expensive := false
for i in 12: for i in 12:
first.hand = [] first.hand = []
first.energy = 20
g.draw(first) g.draw(first)
if first.hand[0].cost > 5: if first.hand[0].cost > 5:
seen_expensive = true seen_expensive = true
check(seen_expensive, "mit 20 Mana kommen auch teure Karten") check(seen_expensive, "mit 20 Mana kommen auch teure Karten")
# Zugbeginn: eine Karte gratis, solange die Hand nicht voll ist
var g5 := Game.new_battle(r)
var p1: Game.Player = g5.current()
var p2: Game.Player = g5.enemy_of(p1)
g5.end_turn()
check(p2.hand.size() == Game.START_HAND, "volle Hand: zu Zugbeginn kommt nichts")
p2.hand.pop_back()
p2.hand.pop_back()
g5.end_turn()
p1.hand.pop_back()
g5.end_turn()
check(p2.hand.size() == Game.START_HAND - 1 and p2.hand.back().cost <= p2.max_energy, "Zugbeginn: eine bezahlbare Karte gezogen")
g5.end_turn()
check(p1.hand.size() == Game.START_HAND, "auch der Startspieler zieht zu Zugbeginn")
## Gelegte Wesen greifen erst im nächsten Zug an; kein Durchschlag, keine Heilung zu Zugbeginn. ## Gelegte Wesen greifen erst im nächsten Zug an; kein Durchschlag, keine Heilung zu Zugbeginn.
@@ -219,12 +238,12 @@ func test_elementvorteil() -> void:
var wolf := put(o, "f_wolf") # Feuer 3/2 var wolf := put(o, "f_wolf") # Feuer 3/2
var life: int = o.life var life: int = o.life
g.attack(tiefen, wolf) g.attack(tiefen, wolf)
check(wolf.health <= 0 and o.life == life and tiefen.health == 6 - 3, "Wasser gegen Feuer: +1 beim Zuschlagen, der Konter bleibt gleich, nichts schlägt durch") check(wolf.health <= 0 and o.life == life and tiefen.health == 6 - 2, "Wasser gegen Feuer: +1 beim Zuschlagen, der Konter macht 1 weniger, nichts schlägt durch")
var s3 := setup() var s3 := setup()
var w3 := put(s3[1], "f_wolf") var w3 := put(s3[1], "f_wolf")
var t3 := put(s3[2], "w_tiefen") var t3 := put(s3[2], "w_tiefen")
s3[0].attack(w3, t3) s3[0].attack(w3, t3)
check(t3.health == 6 - 2 and w3.health <= 0, "Feuer gegen Wasser: 1 weniger Schaden, der Konter bleibt gleich") check(t3.health == 6 - 2 and w3.health == 2 - 5, "Feuer gegen Wasser: 1 weniger Schaden, der Konter macht 1 mehr")
var s2 := setup() var s2 := setup()
var g2: Game = s2[0] var g2: Game = s2[0]
var geist := put(s2[1], "w_geist") # Wasser 2/2 var geist := put(s2[1], "w_geist") # Wasser 2/2
@@ -451,7 +470,7 @@ func test_verschmelzen() -> void:
check(hc.name == "Dampf" and hc.attack == 5 and hc.health == 7 and hc.cost == 5 and hc.fusions == 1, "Dampf auf der Hand mit 5/7 und Kosten 5") check(hc.name == "Dampf" and hc.attack == 5 and hc.health == 7 and hc.cost == 5 and hc.fusions == 1, "Dampf auf der Hand mit 5/7 und Kosten 5")
var lava := Cards.fused(C("f_asche"), C("f_kobold"), 4, 8) var lava := Cards.fused(C("f_asche"), C("f_kobold"), 4, 8)
check(Cards.fused(lava, C("f_kobold")).attack == 5 and Cards.fused(lava, C("f_kobold")).health == 9, "ein Kobold macht Lava 4/8 zu 5/9, nie schlechter") check(Cards.fused(lava, C("f_kobold")).attack == 5 and Cards.fused(lava, C("f_kobold")).health == 9, "ein Kobold macht Lava 4/8 zu 5/9, nie schlechter")
check(g4.play_card(h4, hc) == "" and h4.energy == 4 and h4.board.back().name == "Dampf", "Ausspielen kostet die neuen Kosten") check(g4.play_card(h4, hc) == "" and h4.energy == 9 - 5 and h4.board.back().name == "Dampf", "Ausspielen kostet die neuen Kosten")
check(g4.log.any(func(l): return l == "Flammenwolf und Tiefenwächter verschmelzen auf der Hand zu Dampf (5/7)."), "Verlauf") check(g4.log.any(func(l): return l == "Flammenwolf und Tiefenwächter verschmelzen auf der Hand zu Dampf (5/7)."), "Verlauf")
@@ -475,18 +494,16 @@ func test_gleiche_regeln() -> void:
r.seed = 0 r.seed = 0
var g := Game.new_battle(r) var g := Game.new_battle(r)
var first: Game.Player = g.players[0] var first: Game.Player = g.players[0]
check(g.current() == first and first.max_energy == Game.START_MANA and first.energy == Game.START_MANA, "Startspieler: 5 Mana, kein Bonus") check(g.current() == first and first.max_energy == Game.START_MANA and first.energy == Game.START_MANA, "Startspieler: 1 Mana, kein Bonus")
check(g.players.all(func(p): return p.hand.size() == Game.START_HAND), "jeder beginnt mit fünf Karten") check(g.players.all(func(p): return p.hand.size() == Game.START_HAND), "jeder beginnt mit fünf Karten")
for p in g.players: for p in g.players:
check(p.life == Game.START_LIFE, "gleiches Leben") check(p.life == Game.START_LIFE, "gleiches Leben")
if p != first:
check(p.start_bonus == 1, "Nachzieher: Bonus offen")
g.end_turn() g.end_turn()
var second: Game.Player = g.players[1] var second: Game.Player = g.players[1]
check(g.current() == second and second.energy == Game.START_MANA + 1, "Nachzieher: 5 Mana plus Ausgleich") check(g.current() == second and second.energy == Game.START_MANA, "Nachzieher: ebenfalls 1 Mana, kein Ausgleich")
for i in 2: for i in 2:
g.end_turn() g.end_turn()
check(g.current() == second and second.max_energy == Game.START_MANA + 1 and second.energy == Game.START_MANA + 1, "Ausgleich nur einmal, Rest verfällt") check(g.current() == second and second.max_energy == Game.START_MANA + 1 and second.energy == Game.START_MANA + 1, "zweiter Zug: 2 Mana")
func test_zugreihenfolge() -> void: func test_zugreihenfolge() -> void:
@@ -756,11 +773,11 @@ func test_merkmale() -> void:
var archi: Game.Player = g3.opponent var archi: Game.Player = g3.opponent
check(g3.players[0] == g3.human, "feste Reihenfolge: Mensch beginnt") check(g3.players[0] == g3.human, "feste Reihenfolge: Mensch beginnt")
g3.end_turn() g3.end_turn()
check(g3.current() == archi and archi.energy == Game.START_MANA + 2 + 1, "Archimagier hat im ersten Zug 2 Mana mehr (plus Ausgleich)") check(g3.current() == archi and archi.energy == Game.START_MANA + 2, "Archimagier hat im ersten Zug 2 Mana mehr")
var nyx: Game.Player = Campaign.opponent(5) var nyx: Game.Player = Campaign.opponent(5)
var g4 := Game.new_battle_with(nyx, r, true) var g4 := Game.new_battle_with(nyx, r, true)
g4.end_turn() g4.end_turn()
check(nyx.max_energy == Game.START_MANA and nyx.energy == Game.START_MANA + 1 + 1, "Nyx bekommt 1 Mana mehr je Zug") check(nyx.max_energy == Game.START_MANA and nyx.energy == Game.START_MANA + 1, "Nyx bekommt 1 Mana mehr je Zug")
func test_ki_zwei_schritte() -> void: func test_ki_zwei_schritte() -> void:

View File

@@ -331,7 +331,7 @@ func test_gegnerauswahl() -> void:
var opp: Game.Player = hard.game.opponent var opp: Game.Player = hard.game.opponent
check(opp.max_life == Profile.DIFFICULTIES.schwer.life and hard.smart_ai, "Schwer: Gegner mit %d Leben, Vorausschau-KI" % Profile.DIFFICULTIES.schwer.life) check(opp.max_life == Profile.DIFFICULTIES.schwer.life and hard.smart_ai, "Schwer: Gegner mit %d Leben, Vorausschau-KI" % Profile.DIFFICULTIES.schwer.life)
check(hard.game.human.hand.size() == Game.START_HAND and hard.hand_views.size() == Game.START_HAND, "Start: fünf Karten auf der Hand") check(hard.game.human.hand.size() == Game.START_HAND and hard.hand_views.size() == Game.START_HAND, "Start: fünf Karten auf der Hand")
check(hard.game.human.hand.all(func(c): return c.cost <= Game.START_MANA + 1), "alle fünf sind im ersten Zug spielbar") check(hard.game.human.hand.all(func(c): return c.cost <= Game.START_MANA), "alle fünf sind im ersten Zug spielbar")
await shot("05_starthand") await shot("05_starthand")
check(hard.hero_panels[opp].position == BattleLayout.OPP_BOX.position, "der Gegner steht oben in der Mitte") check(hard.hero_panels[opp].position == BattleLayout.OPP_BOX.position, "der Gegner steht oben in der Mitte")
await shot("11_kampf") await shot("11_kampf")
@@ -507,7 +507,7 @@ func test_kampf_steuerung() -> void:
g.human.hand = [Cards.by_id("f_wolf").copy()] g.human.hand = [Cards.by_id("f_wolf").copy()]
scr.refresh() scr.refresh()
check(scr.hand_views.size() == 1 and scr.deck.enabled, "eine Handkarte, Stapel bereit") check(scr.hand_views.size() == 1 and scr.deck.enabled, "eine Handkarte, Stapel bereit")
# Ziehen: 1 Mana weg, eine Karte landet auf der Hand # Ziehen: 2 Mana weg, eine Karte landet auf der Hand
var mana: int = g.human.energy var mana: int = g.human.energy
await click(scr.deck) await click(scr.deck)
check(g.human.energy == mana - Game.DRAW_COST and g.human.hand.size() == 2 and scr.hand_views.size() == 2, "gezogen: eine Karte auf der Hand") check(g.human.energy == mana - Game.DRAW_COST and g.human.hand.size() == 2 and scr.hand_views.size() == 2, "gezogen: eine Karte auf der Hand")
@@ -636,7 +636,7 @@ func test_kampf_elemente_und_verschmelzen() -> void:
# Rechtsklick auf die Handkarte: Menü mit Verdeckt legen und Eigenschaften, ohne Verschmelzen # Rechtsklick auf die Handkarte: Menü mit Verdeckt legen und Eigenschaften, ohne Verschmelzen
await hover(scr.hand_views[0]) await hover(scr.hand_views[0])
await right_click_at(scr.hand_views[0].get_global_rect().get_center()) await right_click_at(scr.hand_views[0].get_global_rect().get_center())
check(scr.menu_layer.visible and find_button("Verdeckt legen ◆ 1", scr.menu_layer) != null and find_button("Spott ◆ 2", scr.menu_layer) != null, "Rechtsklick öffnet das Kartenmenü") check(scr.menu_layer.visible and find_button("Verdeckt legen ◆ %d" % g.human.hand[0].cost, scr.menu_layer) != null and find_button("Spott ◆ 2", scr.menu_layer) != null, "Rechtsklick öffnet das Kartenmenü")
check(find_button("Verschmelzen mit … ◆ 1", scr.menu_layer) == null, "kein Verschmelzen im Rechtsklick-Menü") check(find_button("Verschmelzen mit … ◆ 1", scr.menu_layer) == null, "kein Verschmelzen im Rechtsklick-Menü")
await shot("23_kartenmenue") await shot("23_kartenmenue")
await right_click() await right_click()
@@ -665,6 +665,10 @@ func test_kampf_elemente_und_verschmelzen() -> void:
# Angriffsziel: Elementvorteil, verdeckte ohne Anzeige, Held gesperrt # Angriffsziel: Elementvorteil, verdeckte ohne Anzeige, Held gesperrt
await click(scr.own_views[0]) await click(scr.own_views[0])
check(scr.selected == wolf and scr.tiles[falke.uid].matchup == 1 and scr.tiles[secret.uid].matchup == 0, "Angreifer gewählt: +1 gegen Luft, nichts gegen Verdeckte") check(scr.selected == wolf and scr.tiles[falke.uid].matchup == 1 and scr.tiles[secret.uid].matchup == 0, "Angreifer gewählt: +1 gegen Luft, nichts gegen Verdeckte")
check(scr.tiles[falke.uid].shown_stats() == [falke.power() - 1, falke.health], "der Falke zeigt seinen Konter gegen den Wolf (1 weniger) und sein echtes Leben")
await hover(scr.tiles[falke.uid])
check(scr.tiles[falke.uid].doomed and not scr.own_views[0].doomed and scr.own_views[0].shown_stats()[0] == wolf.power() + 1, "über dem Falken: er stirbt, der Wolf nicht, der Wolf schlägt mit +1 zu")
await shot("23c_angreifer_gewaehlt")
check(not g.can_attack(wolf, scr.opponent())[0], "Held nicht angreifbar, solange Wesen stehen") check(not g.can_attack(wolf, scr.opponent())[0], "Held nicht angreifbar, solange Wesen stehen")
await click(scr.tiles[secret.uid]) await click(scr.tiles[secret.uid])
check(not secret.hidden and not (secret in scr.opponent().board), "Angriff deckt auf und tötet den Kobold") check(not secret.hidden and not (secret in scr.opponent().board), "Angriff deckt auf und tötet den Kobold")
@@ -686,7 +690,7 @@ func test_kampf_elemente_und_verschmelzen() -> void:
check(g.human.hand.size() == 2 and g.human.hand[0].name == "Inferno" and g.human.hand[0].attack == 3 and g.human.hand[0].health == 2, "zwei Kobolde auf der Hand zu Inferno 3/2") check(g.human.hand.size() == 2 and g.human.hand[0].name == "Inferno" and g.human.hand[0].attack == 3 and g.human.hand[0].health == 2, "zwei Kobolde auf der Hand zu Inferno 3/2")
# Gamepad-Menü (Y) bietet Verdeckt legen und Verschmelzen mit … # Gamepad-Menü (Y) bietet Verdeckt legen und Verschmelzen mit …
scr.open_menu(g.human.hand[0], Rect2(scr.hand_views[0].position, scr.hand_views[0].size), true) scr.open_menu(g.human.hand[0], Rect2(scr.hand_views[0].position, scr.hand_views[0].size), true)
check(find_button("Verdeckt legen ◆ 2", scr.menu_layer) != null, "Gamepad-Menü: Verdeckt legen") check(find_button("Verdeckt legen ◆ %d" % g.human.hand[0].cost, scr.menu_layer) != null and g.human.hand[0].cost == 2, "Gamepad-Menü: Verdeckt legen, Inferno kostet 2")
await click(find_button("Verschmelzen mit … ◆ 2", scr.menu_layer)) await click(find_button("Verschmelzen mit … ◆ 2", scr.menu_layer))
check(scr.fuse_first == g.human.hand[0], "Partner wählen") check(scr.fuse_first == g.human.hand[0], "Partner wählen")
await click(scr.hand_views[1]) await click(scr.hand_views[1])

View File

@@ -154,3 +154,4 @@ Erst die Wesen mit Spott,Taunting creatures first
greift erst im nächsten Zug an,attacks next turn greift erst im nächsten Zug an,attacks next turn
%s durch das Element,%s from its element %s durch das Element,%s from its element
Zwei verschiedene Karten wählen,Pick two different cards Zwei verschiedene Karten wählen,Pick two different cards
stirbt,dies
1 keys en
154 greift erst im nächsten Zug an attacks next turn
155 %s durch das Element %s from its element
156 Zwei verschiedene Karten wählen Pick two different cards
157 stirbt dies

Binary file not shown.