extends RefCounted ## Münzen: ziehen, werfen, Wirkung, Bot-Absicht, Topf. Arbeitet auf dem Spiel ## `g` - Zustand und Ablauf bleiben dort, hier steht nur, was Münzen tun. ## Kein class_name: den Klassencache baut nur der Editor, headless fehlt er. var g: Node func _init(spiel: Node) -> void: g = spiel ## Wer einen Würfel abgibt, bekommt eine Münze. Das ist der einzige Nachschub. func _zieh_muenze(seat: int) -> void: var muenzen: Array = g.hand.get(seat, []) if muenzen.size() >= BoosterDB.HANDLIMIT: g._log("%s hat schon drei Münzen und bekommt keine." % g.seats[seat].name) return var id: String = BoosterDB.zieh(g.rng) muenzen.append(id) g.hand[seat] = muenzen g._log("%s bekommt %s." % [g.seats[seat].name, BoosterDB.get_booster(id).name]) ## Ausspielen heißt: Münze in den Topf werfen. Geht nur am eigenen Zug, bevor ## man ansagt - so ist der Zug eine Entscheidung und nichts kommt sich in die ## Quere. Wappen verstärkt, Zahl lässt es bei der Grundwirkung. func _do_play(seat: int, id: String, target: int) -> void: if seat < 0 or g.phase != "bid" or seat != g.turn or g._answered.has(seat): return if g._muenze_laeuft: return if BoosterDB.needs_bid(id) and g.bid.is_empty(): return if BoosterDB.needs_room(id) and not g._hinten(seat): return var muenzen: Array = g.hand.get(seat, []) if not muenzen.has(id): return muenzen.erase(id) g.hand[seat] = muenzen g._muenze_laeuft = true var stark: bool = g.rng.randf() < BoosterDB.WAPPEN_CHANCE g._wurf_nr += 1 g.topf.append({"seat": seat, "id": id, "stark": stark, "nr": g._wurf_nr}) while g.topf.size() > BoosterDB.TOPF_MAX: var weg: Dictionary = g.topf.pop_front() g._log("%s fällt vom Tisch - der Topf ist voll." % BoosterDB.get_booster(String(weg.id)).name) g._log("%s wirft %s - %s." % [g.seats[seat].name, BoosterDB.get_booster(id).name, "Wappen" if stark else "Zahl"]) # Erst die Münze zeigen, dann wirken lassen. Andersherum sähe man die # Folge vor der Ursache. g._push_state() await g._sleep(g.WURF_ZEIT) if g._alive(): await _wirkung(seat, id, target, stark) g._muenze_laeuft = false if not g._alive(): return g._snapshot_all() g._push_state() func _wirkung(seat: int, id: String, target: int, stark: bool) -> void: var vals: Array = g.host_values[seat] var ziel: int = clampi(target, 0, maxi(0, vals.size() - 1)) match id: "peek": var info := {} for o in g._alive_seats(): if o != seat: info[o] = _zufalls_paare(o, 2 if stark else 1) g.peeked[seat] = info "count": # Zahl: ein Fenster von drei, in dem die Wahrheit irgendwo liegt. # Genau wüsste man sonst, ob die Ansage hält - kein Bluff mehr. var n: int = _count_face(int(g.bid.face)) var von: int = n if stark else maxi(0, n - g.rng.randi_range(0, 2)) g.gezaehlt[seat] = {int(g.bid.face): [von, von if stark else von + 2]} "set_face": for k: int in _ziel_und_zufall(seat, ziel, stark): g._set_die(seat, k, int(g.bid.face)) "gain_die": g.seats[seat].count += 1 g.table.set_dice_count(seat, g.seats[seat].count) g.host_values[seat].append(1) var neu: int = g.host_values[seat].size() - 1 await g._neu_werfen(seat, [neu]) if stark: g._set_die(seat, neu, 6) "bid_up": g.bid.count = int(g.bid.count) + (2 if stark else 1) g.last_bids[int(g.bid.seat)] = {"count": g.bid.count, "face": g.bid.face} g.verlauf.append({"seat": int(g.bid.seat), "count": g.bid.count, "face": g.bid.face, "erhoeht": true}) "gag": if stark: for o in g._alive_seats(): if o != seat: g.must_bid[o] = true else: g.must_bid[g._next_alive(seat)] = true func _count_face(face: int) -> int: var n := 0 for i in g._alive_seats(): for v in g.host_values[i]: if v == face: n += 1 return n ## Bis zu `n` verschiedene Würfelplätze eines Sitzes, zufällig gezogen. func _zufalls_idx(seat: int, n: int) -> Array: var frei: Array = range(g.host_values[seat].size()) var out: Array = [] while out.size() < n and not frei.is_empty(): out.append(frei.pop_at(g.rng.randi_range(0, frei.size() - 1))) return out func _zufalls_paare(seat: int, n: int) -> Array: var out: Array = [] for k: int in _zufalls_idx(seat, n): out.append([k, g.host_values[seat][k]]) return out ## Der selbst gewählte Würfel, bei Wappen zusätzlich ein anderer zufälliger. func _ziel_und_zufall(seat: int, ziel: int, stark: bool) -> Array: var out: Array = [ziel] if not stark: return out var frei: Array = [] for k in g.host_values[seat].size(): if k != ziel: frei.append(k) if not frei.is_empty(): out.append(frei[g.rng.randi_range(0, frei.size() - 1)]) return out ## Ein Bot wirft eine Münze, wenn sie zu seinem Vorhaben passt. `a` ist die ## geplante Handlung. Gibt zurück, ob geworfen wurde. func _bot_muenze(seat: int, a: Dictionary) -> bool: var muenzen: Array = g.hand.get(seat, []) if muenzen.is_empty(): return false var vals: Array = g.host_values[seat] var unknown: int = g._total_dice() - vals.size() var will_bieten: bool = a.type == "bid" # Wie glaubwürdig ist das, was der Bot vorhat bzw. was auf dem Tisch liegt? var eigenes: float = DiceAI.chance(vals, unknown, int(a.count), int(a.face)) if will_bieten else 0.0 var liegt: float = DiceAI.chance(vals, unknown, int(g.bid.count), int(g.bid.face)) if not g.bid.is_empty() else 0.0 var wahl := "" var ziel := 0 # Wirklich hinten liegen: Nachschub ist der Weg zurück. Bei Gleichstand # nicht - sonst wirft ihn zu Beginn jeder, und der Tisch wird nie leerer. if muenzen.has("gain_die") and g._hinten(seat): wahl = "gain_die" # Vor dem Anfechten lieber noch nachsehen, wenn es knapp ist. elif not will_bieten and liegt > 0.2 and liegt < 0.55 and muenzen.has("count"): wahl = "count" elif not will_bieten and liegt > 0.2 and liegt < 0.55 and muenzen.has("peek"): wahl = "peek" # Zudrücken: die Ansage des Gegners höher schrauben, dann anfechten. elif not will_bieten and muenzen.has("bid_up"): wahl = "bid_up" # Ein Bluff steht an: dem Nächsten den Mund verbieten. elif will_bieten and eigenes < 0.35 and muenzen.has("gag"): wahl = "gag" # Auf derselben Augenzahl erhöhen: einen Würfel dazu drehen. elif will_bieten and not g.bid.is_empty() and int(a.face) == int(g.bid.face) \ and muenzen.has("set_face") and vals.count(int(g.bid.face)) < vals.size(): wahl = "set_face" ziel = DiceAI.target_for(vals, int(g.bid.face)) # Voller Vorrat: irgendwas Spielbares raus, sonst wird nie geworfen. elif muenzen.size() >= BoosterDB.HANDLIMIT: for id: String in muenzen: if BoosterDB.needs_bid(id) and g.bid.is_empty(): continue if BoosterDB.needs_room(id) and not g._hinten(seat): continue wahl = id break if wahl == "": return false await _do_play(seat, wahl, ziel) await g._sleep(0.6) return true ## Die grösste Würfelzahl am Tisch - wer weniger hat, liegt hinten. func _groesste_hand() -> int: var k := 0 for i in g._alive_seats(): k = maxi(k, g.seats[i].count) return k ## Wer das Aufdecken gewinnt, nimmt aus dem Topf, so viel in den Vorrat ## passt. Der Rest bleibt liegen und wächst in die nächste Runde - so ist ## ein voller Vorrat kein Grund, den Topf zu verschenken. func _topf_an(winner: int) -> void: if g.topf.is_empty(): return var h: Array = g.hand.get(winner, []) var genommen := 0 g._topf_ziel = winner while not g.topf.is_empty() and h.size() < BoosterDB.HANDLIMIT: h.append(String(g.topf.pop_front().id)) genommen += 1 g.hand[winner] = h g._log("%s nimmt %d Münze(n) aus dem Topf, %d bleiben." % [ g.seats[winner].name, genommen, g.topf.size()])