update
This commit is contained in:
149
scripts/game.gd
149
scripts/game.gd
@@ -7,11 +7,11 @@ const MAX_BOARD := 5
|
||||
const MAX_HAND := 5
|
||||
const START_HAND := 5 # Startkarten, alle im ersten Zug bezahlbar
|
||||
const START_MANA := 1 # Mana in Zug 1; jede Runde eins mehr, ohne Grenze; Rest verfällt
|
||||
const DRAW_COST := 2 # Ziehen: eine bezahlbare Karte vom Stapel
|
||||
const DECK_MIN := 40 # Karten je Stapel: mindestens so viele (Gegner und Startdecks genau so viele)
|
||||
const DECK_MAX := 60 # und höchstens so viele
|
||||
const START_LIFE := 30
|
||||
const ELEMENT_PAIR := 2 # so viele eigene offene Wesen eines Elements: Sondereffekt des Elements
|
||||
const ELEMENT_MANA := 10 # je so viel Mana an ausgespielten Karten eines Elements: +1 Angriff für die eigenen Wesen des Elements
|
||||
const OFFER := 3 # offene Karten auf dem Stapel, höchstens so teuer wie das Mana des Zugs; beim Ziehen wählt man eine, eine neue rückt nach
|
||||
const ELEMENT_COUNT := 5 # je so viele ausgespielte Karten eines Elements: +1/+1 für die eigenen Wesen des Elements
|
||||
const DISCARD_GAIN := 1 # Abwerfen einer Handkarte bringt so viel Mana
|
||||
|
||||
# Merkmale der Kampagnen-Bosse (Player.perks).
|
||||
@@ -129,17 +129,18 @@ class Player extends RefCounted:
|
||||
var is_human: bool
|
||||
var life: int
|
||||
var max_life: int
|
||||
var hand: Array = [] # eigene Exemplare (Cards.Card.copy)
|
||||
var hand: Array = [] # eigene Exemplare (Cards.Card, in der eigenen Seltenheitsstufe)
|
||||
var board: Array = []
|
||||
var deck: Array = [] # eigener Stapel: Karten-IDs, das Ende ist oben
|
||||
var energy := 0 # Mana in diesem Zug; der Rest verfällt
|
||||
var max_energy := 0 # Mana zu Zugbeginn (START_MANA + eigene Züge)
|
||||
var order := 0 # Platz in der Zugreihenfolge
|
||||
var perks: Array = [] # Boss-Merkmale (siehe PERKS)
|
||||
var looks := {} # Karten-ID -> Seltenheitsstufe, reine Optik (siehe Profile)
|
||||
var turns := 0 # begonnene eigene Züge
|
||||
var played := {} # Element -> Mana der bisher ausgespielten Karten (je ELEMENT_MANA: +1 Angriff)
|
||||
var offer: Array = [] # die OFFER offenen Karten auf dem Stapel (nur der Besitzer sieht sie)
|
||||
var drew := false # in diesem Zug schon gezogen? Das erste Ziehen ist frei
|
||||
var played := {} # Element -> bisher ausgespielte Karten (je ELEMENT_COUNT: +1/+1)
|
||||
var draws := 0 # in diesem Zug gezogene Karten; die n-te kostet n-1 Mana
|
||||
var fatigue := 0 # Züge ohne Karten im Stapel: so viel Leben kostet der nächste
|
||||
|
||||
func _init(n: String, h: bool, l: int, t: String = "") -> void:
|
||||
name = n
|
||||
@@ -153,6 +154,25 @@ class Player extends RefCounted:
|
||||
func alive() -> bool:
|
||||
return life > 0
|
||||
|
||||
## Eine Karte vom eigenen Stapel als Exemplar in der eigenen Stufe; null, wenn er leer ist.
|
||||
## Mit max_cost kommt die oberste bezahlbare, zu teure wandern nach unten (Starthand).
|
||||
func draw_from_deck(max_cost: int = 99) -> Cards.Card:
|
||||
var skipped := []
|
||||
var id := ""
|
||||
while not deck.is_empty():
|
||||
var top: String = deck.pop_back()
|
||||
if Cards.by_id(top).cost <= max_cost:
|
||||
id = top
|
||||
break
|
||||
skipped.append(top)
|
||||
if id == "" and not skipped.is_empty(): # nichts Bezahlbares: dann eben die oberste
|
||||
id = skipped.pop_front()
|
||||
for sk in skipped:
|
||||
deck.insert(0, sk)
|
||||
if id == "":
|
||||
return null
|
||||
return Cards.by_id(id).leveled(int(looks.get(id, 0)))
|
||||
|
||||
## Offene eigene Wesen mit diesem Element.
|
||||
func element_count(e: String) -> int:
|
||||
var n := 0
|
||||
@@ -172,20 +192,19 @@ class Player extends RefCounted:
|
||||
p.looks = looks
|
||||
p.turns = turns
|
||||
p.played = played.duplicate()
|
||||
p.offer = offer.duplicate()
|
||||
p.drew = drew
|
||||
p.deck = deck.duplicate()
|
||||
p.draws = draws
|
||||
p.fatigue = fatigue
|
||||
for c in board:
|
||||
p.board.append(c.clone(p))
|
||||
return p
|
||||
|
||||
|
||||
var human: Player # der (erste) menschliche Spieler
|
||||
var humans: Array = [] # alle menschlichen Spieler (im Netz zwei)
|
||||
var human: Player # der menschliche Spieler
|
||||
var opponent: Player # der andere am Tisch
|
||||
var players: Array = [] # in Zugreihenfolge
|
||||
var winner_player: Player = null
|
||||
var rng: RandomNumberGenerator
|
||||
var deck: Array = [] # gemeinsamer Stapel, das Ende ist oben (nur Host und Einzelspieler)
|
||||
var turn := 0
|
||||
var current_idx := 0
|
||||
var log: Array[String] = []
|
||||
@@ -199,7 +218,6 @@ func _init(h: Player, o: Player, r: RandomNumberGenerator = null) -> void:
|
||||
human = h
|
||||
opponent = o
|
||||
players = [h, o]
|
||||
humans = players.filter(func(p): return p.is_human)
|
||||
if r == null:
|
||||
r = make_rng()
|
||||
rng = r
|
||||
@@ -250,7 +268,6 @@ func clone() -> Game:
|
||||
var idx := players.find(human)
|
||||
var g := Game.new(copies[idx], copies[1 - idx], RandomNumberGenerator.new())
|
||||
g.players = copies # gleiche Zugreihenfolge wie das Original
|
||||
g.deck = deck.duplicate()
|
||||
g.turn = turn
|
||||
g.current_idx = current_idx
|
||||
g.winner = winner
|
||||
@@ -278,16 +295,21 @@ func masked_clone(viewer: Player) -> Game:
|
||||
|
||||
# --- Zugablauf ---------------------------------------------------------------
|
||||
|
||||
## Jeder spielt mit seinem eigenen Stapel (leer: ein zufälliger); die Starthand sind fünf im
|
||||
## ersten Zug bezahlbare Karten daraus.
|
||||
func start(fixed_order: bool = false) -> void:
|
||||
if not fixed_order:
|
||||
shuffle(players, rng) # zufällige Zugreihenfolge
|
||||
deck = DeckGen.deck(rng)
|
||||
for i in players.size():
|
||||
var p: Player = players[i]
|
||||
p.order = i
|
||||
for c in draw_cards(START_HAND, START_MANA):
|
||||
p.hand.append(c.copy())
|
||||
p.offer = draw_cards(OFFER, START_MANA)
|
||||
if p.deck.is_empty():
|
||||
p.deck = DeckGen.random_deck(rng)
|
||||
shuffle(p.deck, rng)
|
||||
for k in START_HAND:
|
||||
var c := p.draw_from_deck(START_MANA)
|
||||
if c != null:
|
||||
p.hand.append(c)
|
||||
turn = 1
|
||||
current_idx = 0
|
||||
say("Zugreihenfolge: " + ", ".join(players.map(func(p): return p.name)))
|
||||
@@ -297,54 +319,31 @@ func start(fixed_order: bool = false) -> void:
|
||||
begin_turn(current())
|
||||
|
||||
|
||||
## Nimmt n Karten vom Stapel, die höchstens max_cost kosten; zu teure wandern unter den Stapel.
|
||||
## Ist der Stapel leer, kommt ein frischer.
|
||||
func draw_cards(n: int, max_cost: int = 999) -> Array:
|
||||
var out := []
|
||||
var skipped := []
|
||||
var looked := 0
|
||||
while out.size() < n:
|
||||
if deck.is_empty():
|
||||
deck = DeckGen.deck(rng)
|
||||
say("Der Stapel wird neu gemischt.")
|
||||
var c: Cards.Card = deck.pop_back()
|
||||
looked += 1
|
||||
if c.cost <= max_cost or looked > deck.size() + skipped.size() + n:
|
||||
out.append(c)
|
||||
else:
|
||||
skipped.append(c)
|
||||
for c in skipped:
|
||||
deck.insert(0, c)
|
||||
return out
|
||||
|
||||
|
||||
## Ziehen: eine der offenen Karten kommt auf die Hand, eine neue rückt nach. Das erste Ziehen
|
||||
## im Zug ist frei, jedes weitere kostet DRAW_COST.
|
||||
## Ziehen: eine Karte vom eigenen Stapel. Die erste im Zug ist frei, jede weitere kostet eins
|
||||
## mehr (0, 1, 2 …).
|
||||
func can_draw(p: Player) -> Array:
|
||||
if winner != "" or p != current():
|
||||
return [false, "Nicht dein Zug"]
|
||||
if p.hand.size() >= MAX_HAND:
|
||||
return [false, "Deine Hand ist voll"]
|
||||
if p.deck.is_empty():
|
||||
return [false, "Dein Stapel ist leer"]
|
||||
if p.energy < draw_cost(p):
|
||||
return [false, "Nicht genug Mana"]
|
||||
return [true, ""]
|
||||
|
||||
|
||||
func draw_cost(p: Player) -> int:
|
||||
return 0 if not p.drew or "ziehen_gratis" in p.perks else DRAW_COST
|
||||
return maxi(0, p.draws - (1 if "ziehen_gratis" in p.perks else 0))
|
||||
|
||||
|
||||
func draw(p: Player, index: int = 0) -> String:
|
||||
func draw(p: Player) -> String:
|
||||
var ok := can_draw(p)
|
||||
if not ok[0]:
|
||||
return ok[1]
|
||||
if index < 0 or index >= p.offer.size():
|
||||
return "Keine Karte gewählt"
|
||||
p.energy -= draw_cost(p)
|
||||
p.drew = true
|
||||
var card: Cards.Card = p.offer[index]
|
||||
p.hand.append(card.copy())
|
||||
p.offer[index] = draw_cards(1, maxi(p.max_energy, START_MANA))[0] # nie teurer als das Mana dieses Zugs
|
||||
p.draws += 1
|
||||
p.hand.append(p.draw_from_deck())
|
||||
say("%s zieht." % p.name)
|
||||
emit({"type": "draw", "player": players.find(p)})
|
||||
return ""
|
||||
@@ -359,9 +358,15 @@ func begin_turn(p: Player) -> void:
|
||||
c.ready = true # lag eine Runde: greift jetzt an, Verdecktes darf aufgedeckt werden
|
||||
c.attacks_done = 0
|
||||
p.turns += 1
|
||||
p.drew = false # das erste Ziehen im Zug ist wieder frei
|
||||
p.draws = 0 # das erste Ziehen im Zug ist wieder frei
|
||||
say("--- Zug %d: %s ---" % [turn, p.name])
|
||||
emit({"type": "turn", "player": players.find(p)})
|
||||
if p.deck.is_empty(): # Erschöpfung: ohne Stapel kostet jeder Zug mehr Leben, damit es ein Ende gibt
|
||||
p.fatigue += 1
|
||||
p.life -= p.fatigue
|
||||
say("%s hat keine Karten mehr und verliert %d Leben." % [p.name, p.fatigue])
|
||||
emit({"type": "damage", "target": ref_of(p), "amount": p.fatigue})
|
||||
check_end()
|
||||
|
||||
|
||||
## Zugende: Wesen mit Regeneration heilen ganz, dann ist der andere dran.
|
||||
@@ -417,7 +422,7 @@ func play_card(p: Player, card: Cards.Card, face_down: bool = false) -> String:
|
||||
cr.max_health += 1
|
||||
p.board.append(cr)
|
||||
for e in card.elements:
|
||||
p.played[e] = int(p.played.get(e, 0)) + card.cost
|
||||
p.played[e] = int(p.played.get(e, 0)) + 1
|
||||
if face_down:
|
||||
say("%s legt ein Wesen verdeckt." % p.name)
|
||||
else:
|
||||
@@ -492,9 +497,9 @@ static func look_of(p: Player, c: Cards.Card) -> int:
|
||||
return c.look if c.look >= 0 else int(p.looks.get(c.id, 0))
|
||||
|
||||
|
||||
## Kosten: 1 Mana, verdoppelt je Verschmelzung, die schon in einer der beiden steckt.
|
||||
## Kosten: die Hälfte der Manasumme beider Karten, aufgerundet.
|
||||
static func fusion_cost(a, b) -> int:
|
||||
return 1 << maxi(card_of(a).fusions, card_of(b).fusions)
|
||||
return ceili((card_of(a).cost + card_of(b).cost) / 2.0)
|
||||
|
||||
|
||||
## a und b: zwei eigene Wesen auf dem Brett oder zwei eigene Handkarten.
|
||||
@@ -539,11 +544,11 @@ func fuse(p: Player, a, b) -> String:
|
||||
## das Ergebnis ist nur angriffsbereit, wenn beide es waren.
|
||||
func _fuse_board(p: Player, a: Creature, b: Creature) -> String:
|
||||
var card := Cards.fused(a.card, b.card, Cards.fusion_value(a.attack, b.attack),
|
||||
Cards.fusion_value(a.max_health, b.max_health))
|
||||
Cards.fusion_value(a.max_health - a.aura, b.max_health - b.aura))
|
||||
card.look = maxi(look_of(p, a.card), look_of(p, b.card))
|
||||
var cr := Creature.new(card, p, next_uid)
|
||||
next_uid += 1
|
||||
cr.health = mini(card.health, Cards.fusion_value(a.health, b.health))
|
||||
cr.health = mini(card.health, Cards.fusion_value(mini(a.health, a.max_health - a.aura), mini(b.health, b.max_health - b.aura)))
|
||||
cr.ready = a.ready and b.ready
|
||||
cr.attacks_done = maxi(a.attacks_done, b.attacks_done)
|
||||
var at := p.board.find(a)
|
||||
@@ -560,11 +565,11 @@ func _fuse_board(p: Player, a: Creature, b: Creature) -> String:
|
||||
|
||||
# --- Elementboni ---------------------------------------------------------------
|
||||
|
||||
## +1 Angriff je volle ELEMENT_MANA an ausgespielten Karten eines Elements des Spielers; mehrere Elemente zählen alle.
|
||||
## +1/+1 je volle ELEMENT_COUNT ausgespielte Karten eines Elements des Spielers; mehrere Elemente zählen alle.
|
||||
static func aura_of(elements: Array, played: Dictionary) -> int:
|
||||
var a := 0
|
||||
for e in elements:
|
||||
a += int(played.get(e, 0)) / ELEMENT_MANA
|
||||
a += int(played.get(e, 0)) / ELEMENT_COUNT
|
||||
return a
|
||||
|
||||
|
||||
@@ -579,18 +584,24 @@ func aura_for(c: Creature) -> int:
|
||||
return a
|
||||
|
||||
|
||||
## Rechnet die Elementboni neu; sie wirken nur auf den Angriff (Creature.power).
|
||||
## Rechnet die Elementboni neu. Kommt ein Bonus, steigen Leben und Höchstleben; geht er
|
||||
## (beim Verdecken), sinkt nur das Höchstleben, das Leben wird gekappt.
|
||||
func apply_auras() -> void:
|
||||
for p in players:
|
||||
for c in p.board:
|
||||
c.aura = aura_for(c)
|
||||
var a := aura_for(c)
|
||||
if a != c.aura:
|
||||
var delta: int = a - c.aura
|
||||
c.max_health += delta
|
||||
c.health = mini(c.health + maxi(delta, 0), c.max_health)
|
||||
c.aura = a
|
||||
|
||||
|
||||
## Vorschau fürs Ausspielen: der Elementbonus, den die Karte offen gelegt bekäme (sie zählt selbst mit).
|
||||
func forecast(p: Player, card: Cards.Card) -> int:
|
||||
var played := p.played.duplicate()
|
||||
for e in card.elements:
|
||||
played[e] = int(played.get(e, 0)) + card.cost
|
||||
played[e] = int(played.get(e, 0)) + 1
|
||||
return aura_of(card.elements, played) * (2 if "element_doppelt" in p.perks else 1)
|
||||
|
||||
|
||||
@@ -685,20 +696,7 @@ func check_end() -> void:
|
||||
return
|
||||
winner_player = alive[0]
|
||||
winner = "human" if winner_player.is_human else "opponents"
|
||||
if humans.size() == 1:
|
||||
say("Der Gegner ist besiegt!" if winner == "human" else "Du wurdest besiegt.")
|
||||
else:
|
||||
say("%s gewinnt!" % winner_player.name)
|
||||
|
||||
|
||||
# --- Zustand für das Netzwerk (siehe NetState) ------------------------------------
|
||||
|
||||
func snapshot(viewer: int) -> Dictionary:
|
||||
return NetState.snapshot(self, viewer)
|
||||
|
||||
|
||||
static func from_snapshot(d: Dictionary) -> Game:
|
||||
return NetState.from_snapshot(d)
|
||||
say("Der Gegner ist besiegt!" if winner == "human" else "Du wurdest besiegt.")
|
||||
|
||||
|
||||
# --- Gegner und Kampfstart ---------------------------------------------------
|
||||
@@ -711,7 +709,7 @@ static func shuffle(arr: Array, r: RandomNumberGenerator) -> void:
|
||||
arr[j] = tmp
|
||||
|
||||
|
||||
## Duell gegen einen zufälligen Gegner. rules: {"human_perks": [...], "opponent_life": int, "looks": {...}}
|
||||
## Duell gegen einen zufälligen Gegner. rules: {"human_perks": [...], "opponent_life": int, "looks": {...}, "deck": [ids]}
|
||||
static func new_battle(r: RandomNumberGenerator = null, rules: Dictionary = {}) -> Game:
|
||||
if r == null:
|
||||
r = make_rng()
|
||||
@@ -724,6 +722,7 @@ static func new_battle_with(opp: Player, r: RandomNumberGenerator = null, fixed_
|
||||
var human := Player.new("Spieler", true, START_LIFE)
|
||||
human.perks = Array(rules.get("human_perks", []))
|
||||
human.looks = rules.get("looks", {})
|
||||
human.deck = rules.get("deck", []).duplicate()
|
||||
var g := Game.new(human, opp, r)
|
||||
for p in g.players:
|
||||
if "leben_35" in p.perks:
|
||||
|
||||
Reference in New Issue
Block a user