147 lines
6.1 KiB
GDScript
147 lines
6.1 KiB
GDScript
class_name DeckGen
|
|
## Stapel: Elementstapel nach Manakurve (Startstapel und Gegner), Auffüllen aus der Sammlung,
|
|
## zufällige Stapel für Tests und Simulation.
|
|
|
|
const CHEAP_IN_RANDOM := 12 # so viele im ersten Zug bezahlbare Karten hat ein zufälliger Stapel mindestens
|
|
## Manakurve eines Stapels mit DECK_MIN Karten: so viele Karten je Kosten 0 bis 10.
|
|
const CURVE: Array[int] = [2, 5, 6, 6, 5, 3, 2, 1, 0, 0, 0]
|
|
|
|
|
|
## Zufälliger Stapel aus DECK_MIN verschiedenen Karten bis max_cost, mit genug billigen für den Anfang.
|
|
static func random_deck(r: RandomNumberGenerator, max_cost: int = 99) -> Array:
|
|
var cheap := Cards.all().filter(func(c): return c.cost <= Game.START_MANA)
|
|
var rest := Cards.all().filter(func(c): return c.cost > Game.START_MANA and c.cost <= max_cost)
|
|
Game.shuffle(cheap, r)
|
|
Game.shuffle(rest, r)
|
|
var out := []
|
|
for c in cheap.slice(0, CHEAP_IN_RANDOM) + rest + cheap.slice(CHEAP_IN_RANDOM):
|
|
if out.size() >= Game.DECK_MIN:
|
|
break
|
|
out.append(c.id)
|
|
return out
|
|
|
|
|
|
## Alle Karten eines Elements (oder die neutralen) in Datenreihenfolge: die Startsammlung.
|
|
static func element_deck(element: String) -> Array:
|
|
return Cards.all().filter(func(c): return c.elements[0] == element).map(func(c): return c.id)
|
|
|
|
|
|
## Stapel eines Elements nach der Manakurve: DECK_MIN verschiedene Karten des Elements bis max_cost,
|
|
## je Kosten so viele wie CURVE vorgibt, zufällig gewählt. Was über dem Deckel fehlt, füllen die
|
|
## erlaubten Kosten reihum von billig nach teuer auf.
|
|
static func curve_deck(element: String, r: RandomNumberGenerator, max_cost: int = 10) -> Array:
|
|
return curve_pick(Cards.all().filter(func(c): return c.elements[0] == element), r, max_cost)
|
|
|
|
|
|
## Wählt aus cards (Karten) nach der Manakurve bis max_cost, siehe curve_deck; liefert IDs.
|
|
static func curve_pick(cards: Array, r: RandomNumberGenerator, max_cost: int) -> Array:
|
|
var by_cost := {}
|
|
for c in cards:
|
|
if c.cost <= max_cost:
|
|
if not by_cost.has(c.cost):
|
|
by_cost[c.cost] = []
|
|
by_cost[c.cost].append(c.id)
|
|
for cost in by_cost:
|
|
Game.shuffle(by_cost[cost], r)
|
|
var out := []
|
|
for cost in CURVE.size():
|
|
for k in CURVE[cost]:
|
|
if by_cost.has(cost) and not by_cost[cost].is_empty():
|
|
out.append(by_cost[cost].pop_back())
|
|
while out.size() < Game.DECK_MIN:
|
|
var added := false
|
|
for cost in range(0, max_cost + 1):
|
|
if out.size() >= Game.DECK_MIN:
|
|
break
|
|
if by_cost.has(cost) and not by_cost[cost].is_empty():
|
|
out.append(by_cost[cost].pop_back())
|
|
added = true
|
|
if not added:
|
|
break
|
|
return out
|
|
|
|
|
|
## Stapel mit Effektkarten (Kapitel 6 bis 10): wie curve_deck, aber jede Karte trägt keyword_count
|
|
## Eigenschaften. Zur Wahl stehen alle Grundkarten des Elements mit jeder Eigenschaftskombination,
|
|
## deren Kosten (Cards.cost_with) unter max_cost bleiben; die gewählten werden registriert (IDs g_…).
|
|
static func effect_deck(element: String, r: RandomNumberGenerator, max_cost: int, keyword_count: int) -> Array:
|
|
var pool := {}
|
|
for b in Cards.all():
|
|
if b.elements[0] != element:
|
|
continue
|
|
for combo in combos(Array(Cards.KEYWORDS), keyword_count):
|
|
var kws := Cards.KEYWORDS.filter(func(k): return k in combo)
|
|
var short := "".join(kws.map(func(k): return k.substr(0, 1)))
|
|
var c := Cards.custom("g_%s_%s" % [b.id, short], b.name, b.elements, b.attack, b.health, kws)
|
|
c.name_en = b.name_en
|
|
pool[c.id] = c
|
|
var out := curve_pick(pool.values(), r, max_cost)
|
|
for id in out:
|
|
Cards.register(pool[id])
|
|
return out
|
|
|
|
|
|
## Alle Teilmengen von items mit k Einträgen, in der Reihenfolge von items.
|
|
static func combos(items: Array, k: int) -> Array:
|
|
if k == 0:
|
|
return [[]]
|
|
var out := []
|
|
for i in items.size():
|
|
for rest in combos(items.slice(i + 1), k - 1):
|
|
var combo := [items[i]]
|
|
combo.append_array(rest)
|
|
out.append(combo)
|
|
return out
|
|
|
|
|
|
## Füllt deck aus pool (Karten-IDs) bis limit auf, die wertvollsten zuerst (Card.value in der Stufe
|
|
## aus levels: ID -> Stufe); bei gleichem Wert zuerst, was der Manakurve (CURVE, auf limit
|
|
## skaliert) noch fehlt, dann die billigeren, dann nach ID; nichts doppelt.
|
|
static func fill(deck: Array, pool: Array, limit: int, levels: Dictionary = {}) -> Array:
|
|
var out := deck.duplicate()
|
|
var cards := pool.filter(func(id): return not (id in out)).map(func(id): return Cards.by_id(id).leveled(int(levels.get(id, 0))))
|
|
cards.sort_custom(func(a, b):
|
|
if a.value() != b.value():
|
|
return a.value() > b.value()
|
|
return a.cost < b.cost if a.cost != b.cost else a.id < b.id)
|
|
var have := {} # Kosten -> Karten im Stapel
|
|
for id in out:
|
|
var slot := clampi(Cards.by_id(id).cost, 0, CURVE.size() - 1)
|
|
have[slot] = int(have.get(slot, 0)) + 1
|
|
var i := 0
|
|
while i < cards.size() and out.size() < limit:
|
|
var j := i
|
|
while j < cards.size() and cards[j].value() == cards[i].value():
|
|
j += 1
|
|
var rest := []
|
|
for c in cards.slice(i, j):
|
|
if out.size() >= limit:
|
|
break
|
|
var slot := clampi(c.cost, 0, CURVE.size() - 1)
|
|
if int(have.get(slot, 0)) < CURVE[slot] * limit / Game.DECK_MIN:
|
|
out.append(c.id)
|
|
have[slot] = int(have.get(slot, 0)) + 1
|
|
else:
|
|
rest.append(c)
|
|
for c in rest:
|
|
if out.size() >= limit:
|
|
break
|
|
out.append(c.id)
|
|
i = j
|
|
return out
|
|
|
|
|
|
## Stärke eines Stapels: der mittlere Kartenwert (Card.value) in den Stufen aus levels,
|
|
## 100 für lauter Grundkarten bis 500 für lauter Ultimate Rare; leer: 0.
|
|
static func strength(deck: Array, levels: Dictionary) -> int:
|
|
if deck.is_empty():
|
|
return 0
|
|
var sum := 0
|
|
for id in deck:
|
|
sum += Cards.by_id(id).leveled(int(levels.get(id, 0))).value()
|
|
return roundi(float(sum) / deck.size())
|
|
|
|
|
|
static func random_opponent(life: int = Game.START_LIFE) -> Game.Player:
|
|
return Game.Player.new("Gegner", false, life)
|