Deckstärke, Schwierigkeitsgrade mit Archetypen, Gegnerdecks entlang der Energiekurve, Starter mit zwei seltenen Karten
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QPgKvy5W9WRNFHjkm5QsHx
This commit is contained in:
114
scripts/game.gd
114
scripts/game.gd
@@ -778,43 +778,115 @@ static func validate_deck(cards: Array, owned = null) -> Array:
|
||||
return [true, ""]
|
||||
|
||||
|
||||
## Gegnerdeck aus den Elementen. bias > 1 bevorzugt seltene Karten (Kampagne).
|
||||
## Deckstärke: Durchschnitt der Kartenwerte (1 = nur gewöhnliche Karten, bis etwa 3,5).
|
||||
static func deck_rating(cards: Array) -> float:
|
||||
if cards.is_empty():
|
||||
return 0.0
|
||||
var sum := 0.0
|
||||
for c in cards:
|
||||
sum += Cards.card_value(c)
|
||||
return sum / cards.size()
|
||||
|
||||
|
||||
## 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
|
||||
|
||||
|
||||
# Gemessene Stärke der Elementkombinationen (Siegquote gegen das Feld, KI gegen KI, tests/strength_probe.gd).
|
||||
const ARCHETYPE_STRENGTH := {
|
||||
"Feuer": 33, "Wasser": 20, "Erde": 53, "Luft": 46,
|
||||
"Feuer+Wasser": 46, "Feuer+Erde": 60, "Feuer+Luft": 60,
|
||||
"Wasser+Erde": 23, "Wasser+Luft": 36, "Erde+Luft": 66,
|
||||
}
|
||||
|
||||
|
||||
## 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 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 := {}
|
||||
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() < 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)
|
||||
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
|
||||
|
||||
|
||||
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")
|
||||
## 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) >= 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)]
|
||||
|
||||
|
||||
## rules: {"human_perks": [...], "everyone_perk": String, "start_life": int}
|
||||
## 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 = START_LIFE, target: float = 0.0, tier: String = "") -> 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 Player.new("Gegner %d" % index, deck, false, life, Cards.combo_name(elements) + "-Magier")
|
||||
|
||||
|
||||
## rules: {"human_perks": [...], "everyone_perk": String, "start_life": int, "target_rating": float, "tier": String}
|
||||
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 target: float = float(rules.get("target_rating", 0.0))
|
||||
var tier: String = str(rules.get("tier", ""))
|
||||
var opps := []
|
||||
for i in n_opp:
|
||||
opps.append(random_opponent(r, i + 1))
|
||||
opps.append(random_opponent(r, i + 1, START_LIFE, target, tier))
|
||||
return new_battle_with(deck, opps, r, false, rules)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user