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-06 18:16:17 +02:00
parent 0a612f91b3
commit 5bc8569717
15 changed files with 43 additions and 170 deletions

View File

@@ -68,7 +68,6 @@ static func evaluate(g: Game, p: Game.Player) -> float:
# --- Vorausschau ---------------------------------------------------------------
const MAX_TARGETS := 4 # je Angreifer nur die aussichtsreichsten Ziele prüfen
static var allow_upgrade := true # zum Messen: Verbessern erwägen
const DEEPEN := 8 # so viele beste Erstschritte bekommen Folgeschritte
const FOLLOW_UPS := 10 # Folgeschritte je vertieftem Kandidat
static var time_budget_ms := 150 # Zeitbudget je Entscheidung; danach der beste bisherige Zug
@@ -99,18 +98,9 @@ static func candidate_actions(game: Game, p: Game.Player, prune: bool = false) -
for card in p.hand:
if game.can_play(p, card)[0]:
out.append({"type": "play", "card": card, "face_down": false})
var has_target := not game.enemies_of(p).is_empty()
for cr in p.board:
if cr.hidden:
if game.can_reveal(p, cr)[0]:
out.append({"type": "reveal", "creature": cr})
elif allow_upgrade and has_target:
# Nur Verbesserungen, die sich noch in diesem Zug auszahlen können: Doppelschlag für
# ein Wesen, das gleich angreift; Flink für eines, das sonst erst nächsten Zug dürfte.
if cr.can_attack() and game.can_upgrade(p, cr, "Doppelschlag")[0]:
out.append({"type": "upgrade", "creature": cr, "keyword": "Doppelschlag"})
if not cr.ready and not cr.has("Flink") and cr.power() > 0 and game.can_upgrade(p, cr, "Flink")[0]:
out.append({"type": "upgrade", "creature": cr, "keyword": "Flink"})
if cr.hidden and game.can_reveal(p, cr)[0]:
out.append({"type": "reveal", "creature": cr})
var pairs := []
for i in p.board.size():
for j in range(i + 1, p.board.size()):
@@ -144,8 +134,6 @@ static func apply_on_clone(g2: Game, game: Game, action: Dictionary) -> void:
g2.play_card(p2, action.card, bool(action.get("face_down", false)))
"reveal":
g2.reveal(p2, g2.find_creature(action.creature.uid))
"upgrade":
g2.upgrade(p2, g2.find_creature(action.creature.uid), action.keyword)
"fuse":
g2.fuse(p2, g2.find_creature(action.creature.uid), g2.find_creature(action.other.uid))
_:
@@ -269,10 +257,6 @@ static func choose_action_greedy(game: Game, p: Game.Player) -> Dictionary:
var t = pick_attack_target(game, p, cr)
if t != null:
return {"type": "attack", "creature": cr, "target": t}
if not game.enemies_of(p).is_empty():
var strongest = _best(p.board.filter(func(c): return not c.hidden and game.can_upgrade(p, c, "Doppelschlag")[0]), func(c): return c.power())
if strongest != null:
return {"type": "upgrade", "creature": strongest, "keyword": "Doppelschlag"}
if game.can_draw(p)[0]:
return {"type": "draw"}
return {"type": "end"}
@@ -307,8 +291,6 @@ static func apply_action(game: Game, p: Game.Player, action: Dictionary) -> void
game.draw(p)
"reveal":
game.reveal(p, action.creature)
"upgrade":
game.upgrade(p, action.creature, action.keyword)
"attack":
game.attack(action.creature, action.target)
"fuse":

View File

@@ -11,7 +11,7 @@ const STAGES := [
{"name": "Nyx", "title": "Sturmruferin", "life": 26, "reward": 200, "perk": "extra_mana"},
{"name": "Ignis", "title": "Inferno-Priester", "life": 26, "reward": 250, "perk": "element_doppelt"},
{"name": "Thalassa", "title": "Tiefseekönigin", "life": 26, "reward": 250, "perk": "alle_flink"},
{"name": "Zephyra", "title": "Blitzkaiserin", "life": 28, "reward": 300, "perk": "verbessern_billig"},
{"name": "Zephyra", "title": "Blitzkaiserin", "life": 28, "reward": 300, "perk": "alle_flink"},
{"name": "Archimagier", "title": "Meister der Elemente", "life": 28, "reward": 500, "perk": "start_energie"},
]
@@ -21,12 +21,12 @@ const STAGES2 := [
{"name": "Fenn", "title": "Flammenzähmer", "life": 22, "reward": 300, "perks": ["feuer_angriff", "start_energie"]},
{"name": "Mara", "title": "Herrin der Gezeiten", "life": 22, "reward": 300, "perks": ["leben_35", "extra_mana"]},
{"name": "Borin", "title": "Fels der Ewigkeit", "life": 24, "reward": 350, "perks": ["wesen_angriff", "alle_flink"]},
{"name": "Liska", "title": "Sturmläuferin", "life": 24, "reward": 350, "perks": ["ziehen_gratis", "verbessern_billig"]},
{"name": "Liska", "title": "Sturmläuferin", "life": 24, "reward": 350, "perks": ["ziehen_gratis", "wesen_angriff"]},
{"name": "Vulkar", "title": "Aschekönig", "life": 24, "reward": 400, "perks": ["wesen_leben", "feuer_angriff"]},
{"name": "Nyx", "title": "Gewitterruferin", "life": 26, "reward": 400, "perks": ["extra_mana", "ziehen_gratis"]},
{"name": "Ignis", "title": "Inferno-Erzpriester", "life": 26, "reward": 450, "perks": ["element_doppelt", "wesen_leben"]},
{"name": "Thalassa", "title": "Kaiserin der Tiefe", "life": 26, "reward": 450, "perks": ["alle_flink", "leben_35"]},
{"name": "Zephyra", "title": "Blitzkaiserin", "life": 28, "reward": 500, "perks": ["verbessern_billig", "start_energie"]},
{"name": "Zephyra", "title": "Blitzkaiserin", "life": 28, "reward": 500, "perks": ["alle_flink", "start_energie"]},
{"name": "Archimagier", "title": "Herr aller Elemente", "life": 28, "reward": 800, "perks": ["start_energie", "element_doppelt"]},
]
@@ -36,12 +36,12 @@ const STAGES3 := [
{"name": "Fenn", "title": "Aschefürst", "life": 20, "reward": 600, "perks": ["feuer_angriff", "start_energie", "wesen_leben"]},
{"name": "Mara", "title": "Sturmflut", "life": 20, "reward": 600, "perks": ["leben_35", "extra_mana", "alle_flink"]},
{"name": "Borin", "title": "Berg selbst", "life": 22, "reward": 700, "perks": ["wesen_angriff", "alle_flink", "leben_35"]},
{"name": "Liska", "title": "Windbraut", "life": 22, "reward": 700, "perks": ["ziehen_gratis", "verbessern_billig", "extra_mana"]},
{"name": "Liska", "title": "Windbraut", "life": 22, "reward": 700, "perks": ["ziehen_gratis", "wesen_angriff", "extra_mana"]},
{"name": "Vulkar", "title": "Magmakaiser", "life": 22, "reward": 800, "perks": ["wesen_leben", "feuer_angriff", "wesen_angriff"]},
{"name": "Nyx", "title": "Orkanmutter", "life": 24, "reward": 800, "perks": ["extra_mana", "ziehen_gratis", "element_doppelt"]},
{"name": "Ignis", "title": "Sonnenpriester", "life": 24, "reward": 900, "perks": ["element_doppelt", "wesen_leben", "start_energie"]},
{"name": "Thalassa", "title": "Herz der Tiefe", "life": 24, "reward": 900, "perks": ["alle_flink", "leben_35", "element_doppelt"]},
{"name": "Zephyra", "title": "Gewitterkaiserin", "life": 26, "reward": 1000, "perks": ["verbessern_billig", "start_energie", "wesen_leben"]},
{"name": "Zephyra", "title": "Gewitterkaiserin", "life": 26, "reward": 1000, "perks": ["alle_flink", "start_energie", "wesen_leben"]},
{"name": "Archimagier", "title": "Ewiger Meister", "life": 26, "reward": 1200, "perks": ["start_energie", "element_doppelt", "leben_35"]},
]

View File

@@ -34,10 +34,9 @@ const RARITY_COLORS := {
"Ultra Rare": Color("f0aa28"),
"Ultimate Rare": Color("ff5a8c"),
}
# Eigenschaften haben Karten nicht von Haus aus; sie werden für Mana dazugekauft (Game.upgrade)
# oder kommen vom Elementpaar der eigenen Seite (ELEMENT_KEYWORD, siehe Game.Creature.has).
# Eigenschaften haben Karten nicht von Haus aus; sie kommen allein vom Elementpaar der eigenen
# Seite (ELEMENT_KEYWORD, siehe Game.Creature.has).
const KEYWORDS: Array[String] = ["Spott", "Flink", "Regeneration", "Doppelschlag"]
const KEYWORD_COST := {"Spott": 2, "Flink": 2, "Regeneration": 3, "Doppelschlag": 4}
const KEYWORD_TEXT := {
"Spott": "Muss zuerst angegriffen werden",
"Flink": "Greift sofort an",
@@ -158,8 +157,8 @@ class Card extends RefCounted:
var elements: Array
var attack: int = 0
var health: int = 0
var cost: int = 0 # abgeleitet: (2 · Angriff + Leben) / 3, abgerundet, mindestens 1
var keywords: Array = [] # dazugekauft, je Exemplar
var cost: int = 0 # abgeleitet: (2 · Angriff + Leben) / 3, abgerundet
var keywords: Array = [] # feste Eigenschaften der Karte (Elementpaare geben ihre zur Laufzeit)
var fusions := 0 # wie oft verschmolzen (Verschmelzen kostet 1, 2, 4, 8 …)
var look := -1 # Seltenheitsstufe verschmolzener Karten (die höhere der Eltern); -1: aus der Sammlung des Besitzers
@@ -211,7 +210,7 @@ class Card extends RefCounted:
func fusion_cost() -> int:
return 1 << fusions
## Kartentext: die gekauften Eigenschaften.
## Kartentext: die festen Eigenschaften.
func text() -> String:
if keywords.is_empty():
return ""

View File

@@ -1,7 +1,7 @@
class_name Game
extends RefCounted
## Spielregeln: Duell, Mana je Zug, Ziehen aus dem gemeinsamen Stapel, verdeckte Wesen,
## Elementboni, Verschmelzen und Verbessern.
## Elementboni und Verschmelzen.
const MAX_BOARD := 5
const MAX_HAND := 5
@@ -21,7 +21,6 @@ const PERKS := {
"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",
}
@@ -65,8 +64,8 @@ class Creature extends RefCounted:
func owner():
return owner_ref.get_ref()
## Eigenschaft: gekauft, als Sondereffekt des Elementpaars der eigenen Seite oder als
## Boss-Merkmal. Verdeckte Wesen haben keine.
## Eigenschaft: als Sondereffekt des Elementpaars der eigenen Seite, aus der Karte
## (verschmolzene erben) oder als Boss-Merkmal. Verdeckte Wesen haben keine.
func has(kw: String) -> bool:
if hidden:
return false
@@ -322,12 +321,6 @@ 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]:
@@ -455,43 +448,6 @@ func _flip(cr: Creature) -> void:
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:

View File

@@ -398,12 +398,6 @@ func _apply(id: int, action: Dictionary) -> void:
var cr := game.find_creature(int(action.get("uid", -1)))
if cr != null:
game.reveal(p, cr)
"upgrade":
var cr := game.find_creature(int(action.get("uid", -1)))
var hi := int(action.get("hand", -1))
var target = cr if cr != null else (p.hand[hi] if hi >= 0 and hi < p.hand.size() else null)
if target != null:
game.upgrade(p, target, str(action.get("keyword", "")))
"attack":
var cr := game.find_creature(int(action.get("uid", -1)))
var t = resolve_ref(action.get("target"))

View File

@@ -40,10 +40,6 @@ func play_events(evs: Array, centers: Dictionary) -> void:
"reveal":
sfx("combo")
popup(tr("Aufgedeckt!"), Style.GOLD, screen.target_center({"kind": "creature", "uid": ev.uid}), 0.0)
"upgrade":
sfx("heal")
if ev.uid >= 0:
popup("+" + tr(ev.keyword), Style.GREEN, screen.target_center({"kind": "creature", "uid": ev.uid}), 0.0)
"fuse":
sfx("combo")
popup("Verschmolzen!", Style.PURPLE, screen.target_center({"kind": "player", "index": ev.player}), 0.0)

View File

@@ -661,21 +661,6 @@ func do_reveal(cr: Game.Creature) -> void:
after_action()
## Kauft eine Eigenschaft für eine Handkarte oder ein eigenes Wesen.
func do_upgrade(target, keyword: String) -> void:
clear_undo()
if is_client():
if target is Game.Creature:
net.send_action({"type": "upgrade", "uid": target.uid, "keyword": keyword})
else:
net.send_action({"type": "upgrade", "hand": perspective.hand.find(target), "keyword": keyword})
return
var err := game.upgrade(perspective, target, keyword)
if err != "":
say(tr(err))
after_action()
## Kartenmenü neben einer Handkarte oder einem eigenen Wesen. full: auch verschmelzen anbieten
## (Gamepad, das nicht ziehen kann).
func open_menu(target, anchor: Rect2, full: bool = false) -> void:
@@ -700,13 +685,9 @@ func open_menu(target, anchor: Rect2, full: bool = false) -> void:
fuse_first = target
say(tr("Ziel wählen"))
refresh(), not partners.is_empty()])
for kw in Cards.KEYWORDS:
var k: String = kw
var ok := game.can_upgrade(perspective, target, k)
var text := "%s ◆ %d" % [tr(k), game.upgrade_cost(perspective, k)]
if k in card.keywords:
text = "%s ✓" % tr(k)
entries.append([text, func(): do_upgrade(target, k), ok[0]])
if entries.is_empty(): # offenes eigenes Wesen per Maus: nichts zu wählen
close_menu()
return
var w := 230.0
var h := 18.0 + entries.size() * 44 + 34
var x := clampf(anchor.end.x + 8, 10, 1270 - w)

View File

@@ -81,7 +81,7 @@ func _draw() -> void:
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 in der Farbe des Elements, das sie gibt
var icon := 7.0 if small else 9.0
var kws := creature.keywords()
var avail := size.x - (bw + 8 if badge != "" else 0.0)
@@ -89,10 +89,9 @@ func _draw() -> void:
var y := Style.fs(fs) + 14 + icon
for k in kws:
var col := Color("f2f2f6")
if not (k in creature.card.keywords):
for e in creature.card.elements:
if Cards.ELEMENT_KEYWORD.get(e, "") == k:
col = Style.lighter(Cards.ELEMENT_COLORS[e], 0.45)
for e in creature.card.elements:
if Cards.ELEMENT_KEYWORD.get(e, "") == k:
col = Style.lighter(Cards.ELEMENT_COLORS[e], 0.45)
Style.keyword_icon(self, k, Vector2(x + icon, y), icon, col)
x += icon * 2 + 6
_draw_stats(15 if small else 17, 12.0 if small else 14.0)