class_name DeckGen ## Deckstärke, Gegnerdecks und Archetypen (ausgelagert aus den Spielregeln). const OPPONENT_RARITY_WEIGHT := {"gewöhnlich": 6.0, "selten": 3.0, "episch": 2.0, "legendär": 1.0, "mythisch": 0.3} # Abnehmende Resonanz je Anzahl gleichelementiger Partner (Index = Partner). const RESONANCE_STEPS := [0, 1, 2, 3, 3] # Gemessene Stärke der Elementkombinationen (Siegquote gegen das Feld, KI gegen KI, tests/strength_probe.gd). const ARCHETYPE_STRENGTH := { "Feuer": 36, "Wasser": 30, "Erde": 63, "Luft": 50, "Feuer+Wasser": 46, "Feuer+Erde": 70, "Feuer+Luft": 40, "Wasser+Erde": 46, "Wasser+Luft": 20, "Erde+Luft": 46, } ## Elementkombinationen einer Stufe: "leicht" die schwächere Hälfte, "schwer" die stärkere, sonst alle. static func archetypes(tier: String = "") -> Array: var keys: Array = ARCHETYPE_STRENGTH.keys() keys.sort_custom(func(a, b): return ARCHETYPE_STRENGTH[a] < ARCHETYPE_STRENGTH[b]) var half: int = keys.size() / 2 match tier: "leicht": return keys.slice(0, half) "schwer": return keys.slice(half) return keys # Energiekurve der Gegnerdecks: Anzahl Karten je Kosten (6 = sechs und mehr). const CURVE := {1: 3, 2: 5, 3: 4, 4: 3, 5: 2, 6: 3} ## Gegnerdeck aus den Elementen entlang der Energiekurve. bias > 1 bevorzugt seltene Karten, bias < 1 gewöhnliche. 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 deck := [] var counts := {} for bucket in CURVE: for i in CURVE[bucket]: var c = _pick_for_cost(r, pool, counts, bucket, bias) if c != null: counts[c.id] = counts.get(c.id, 0) + 1 deck.append(c) while deck.size() < Game.DECK_SIZE: var c = _pick_for_cost(r, pool, counts, r.randi_range(1, 6), bias) if c == null: break counts[c.id] = counts.get(c.id, 0) + 1 deck.append(c) return deck ## Karte nahe den gewünschten Kosten, gewichtet nach Seltenheit (bias) und Kostenabstand. ## Bei extremer Seltenheitsvorgabe darf die Kurve nachgeben, sonst wäre das Ziel unerreichbar. static func _pick_for_cost(r: RandomNumberGenerator, pool: Array, counts: Dictionary, cost: int, bias: float): var candidates := [] var weights := PackedFloat32Array() for c in pool: if counts.get(c.id, 0) >= Game.MAX_COPIES: continue var spread: int = absi(mini(c.cost, 6) - cost) var w: float = OPPONENT_RARITY_WEIGHT[c.rarity] w = w / bias if c.rarity == "gewöhnlich" else w * bias w *= pow(0.3, spread) candidates.append(c) weights.append(w) if candidates.is_empty(): return null return candidates[r.rand_weighted(weights)] ## Gegnerdeck auf eine Zielstärke: die Gewichtung wird angepasst, bis das Deck nahe am Ziel liegt. static func build_opponent_deck_rated(r: RandomNumberGenerator, elements: Array, target: float, tolerance: float = 0.12) -> Array: var bias := 1.0 var best := [] var best_diff := 99.0 for attempt in 24: var deck := build_opponent_deck(r, elements, bias) var diff := deck_rating(deck) - target if absf(diff) < best_diff: best = deck best_diff = absf(diff) if absf(diff) <= tolerance: break bias = clampf(bias * (1.35 if diff < 0 else 1.0 / 1.35), 0.05, 20.0) return best ## Zufälliger Gegner. target > 0: Deck auf diese Stärke gebaut. tier: Archetypen der Schwierigkeit. static func random_opponent(r: RandomNumberGenerator, index: int, life: int = Game.START_LIFE, target: float = 0.0, tier: String = "") -> Game.Player: var options := archetypes(tier) var elements: Array = options[r.randi_range(0, options.size() - 1)].split("+") var deck := build_opponent_deck_rated(r, elements, target) if target > 0.0 else build_opponent_deck(r, elements) return Game.Player.new("Gegner %d" % index, deck, false, life, Cards.combo_name(elements) + "-Magier") ## Kartenwert, bei genug Partien mit der Siegquote der Karte gewichtet (Statistik aus Spieltests). static func card_value(c: Cards.Card, stats: Dictionary = {}) -> float: var v := Cards.card_value(c) if stats.has(c.id): var st: Dictionary = stats[c.id] if int(st.games) >= 10: v *= 0.5 + float(st.wins) / float(st.games) return v ## Abzug für schlechte Energiekurven: zu wenig günstige oder zu viele teure Karten. static func curve_penalty(cards: Array) -> float: var cheap := 0 var expensive := 0 for c in cards: if c.cost <= 2: cheap += 1 if c.cost >= 5: expensive += 1 return minf(0.05 * maxi(0, 6 - cheap) + 0.05 * maxi(0, expensive - 5), 0.4) ## Deckstärke: Durchschnitt der Kartenwerte minus Kurvenabzug (1 = nur gewöhnliche Karten, bis etwa 3,5). static func deck_rating(cards: Array, stats: Dictionary = {}) -> float: if cards.is_empty(): return 0.0 var sum := 0.0 for c in cards: sum += card_value(c, stats) return sum / cards.size() - curve_penalty(cards)