update
This commit is contained in:
388
scripts/game.gd
388
scripts/game.gd
@@ -3,7 +3,11 @@ extends Node3D
|
||||
|
||||
const START_DICE := 5
|
||||
const BID_TIMEOUT := 30.0
|
||||
const REVEAL_PAUSE := 5.0 ## Anzeigedauer des Rundenergebnisses
|
||||
## Aufdecken in drei Etappen: Becher hoch, zählen, Urteil. So baut sich die
|
||||
## Spannung auf, statt dass alles auf einmal dasteht.
|
||||
const ZEIT_BECHER := 1.3
|
||||
const ZEIT_ZAEHLEN := 2.2 ## reicht, um bis zu 15 passende Würfel aufleuchten zu lassen
|
||||
const ZEIT_URTEIL := 2.8
|
||||
const WURF_ZEIT := 1.1 ## wie lange die geworfene Münze zu sehen ist,
|
||||
## bevor ihre Wirkung einsetzt
|
||||
|
||||
@@ -20,14 +24,16 @@ var my_values: Array = [] ## Client-Sicht (-1 = vernebelt)
|
||||
var bid: Dictionary = {}
|
||||
var turn := 0
|
||||
var starter := 0
|
||||
var fogged: Dictionary = {} ## seat -> Array[int] verdeckter Würfel
|
||||
var peeked: Dictionary = {} ## viewer -> {target_seat: Array[[idx, value]]}
|
||||
var min_raise: Dictionary = {} ## seat -> int
|
||||
var gezaehlt: Dictionary = {} ## seat -> {face: [von, bis]}, durch Zählen
|
||||
var verlauf: Array = [] ## alle Ansagen dieser Runde, {seat, count, face, erhoeht}
|
||||
var must_bid: Dictionary = {} ## seat -> bool
|
||||
var hand: Dictionary = {} ## seat -> Array[String], Münzen beim Spieler
|
||||
var mitte: Dictionary = {} ## seat -> zuletzt in die Mitte gelegte Münze
|
||||
var topf: Array = [] ## geworfene Münzen, {seat, id, stark, nr}; bleibt liegen, bis ihn einer nimmt
|
||||
var _muenze_laeuft := false ## eine Münze wird gerade aufgelöst
|
||||
var _wurf_nr := 0 ## zählt die Würfe, damit das HUD neue erkennt
|
||||
var _wurf_nr := 0 ## zählt die Würfe übers Spiel, damit das HUD neue erkennt
|
||||
var _klang_phase := "" ## letzte Phase, für die ein Klang lief
|
||||
var _gezaehlt_gezeigt := false ## Hervorhebung beim Aufdecken lief schon
|
||||
var last_bids: Dictionary = {} ## seat -> letzte Ansage dieser Runde
|
||||
var log_lines: Array = []
|
||||
var phase := "lobby"
|
||||
@@ -48,6 +54,7 @@ func _ready() -> void:
|
||||
|
||||
|
||||
func setup(cfg: Dictionary) -> void:
|
||||
DiceAI.stufe = int(cfg.get("stufe", 1))
|
||||
seats.clear()
|
||||
var peers: Array = cfg.peers
|
||||
for p in peers:
|
||||
@@ -76,6 +83,8 @@ func setup(cfg: Dictionary) -> void:
|
||||
add_child(hud)
|
||||
hud.bid_pressed.connect(_on_bid_pressed)
|
||||
hud.challenge_pressed.connect(_on_challenge_pressed)
|
||||
hud.exact_pressed.connect(_on_exact_pressed)
|
||||
hud.rematch_pressed.connect(_on_rematch)
|
||||
hud.booster_chosen.connect(_on_booster_chosen)
|
||||
hud.quit_pressed.connect(_on_quit)
|
||||
hud.pause_changed.connect(_on_pause_changed)
|
||||
@@ -200,13 +209,11 @@ func _phase_roll() -> void:
|
||||
deadline = 0.0
|
||||
reveal_total = 0
|
||||
bid = {}
|
||||
fogged.clear()
|
||||
last_bids.clear()
|
||||
peeked.clear()
|
||||
min_raise.clear()
|
||||
gezaehlt.clear()
|
||||
verlauf.clear()
|
||||
must_bid.clear()
|
||||
mitte.clear()
|
||||
_wurf_nr = 0
|
||||
banner = ""
|
||||
_log("--- Neue Runde ---")
|
||||
|
||||
@@ -275,14 +282,16 @@ func _zieh_muenze(seat: int) -> void:
|
||||
_log("%s bekommt %s." % [seats[seat].name, BoosterDB.get_booster(id).name])
|
||||
|
||||
|
||||
## Ausspielen heißt: Münze in die Mitte legen und werfen. Wappen verstärkt,
|
||||
## Zahl lässt es bei der Grundwirkung. Beides hilft - verschenkt ist nie ein Zug.
|
||||
## 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 phase != "bid" or not seats[seat].alive:
|
||||
if seat < 0 or phase != "bid" or seat != turn or _answered.has(seat):
|
||||
return
|
||||
# Zwei Münzen gleichzeitig würden sich beim Würfeln in die Quere kommen.
|
||||
if _muenze_laeuft:
|
||||
return
|
||||
if BoosterDB.needs_bid(id) and bid.is_empty():
|
||||
return
|
||||
var muenzen: Array = hand.get(seat, [])
|
||||
if not muenzen.has(id):
|
||||
return
|
||||
@@ -292,7 +301,10 @@ func _do_play(seat: int, id: String, target: int) -> void:
|
||||
|
||||
var stark := rng.randf() < BoosterDB.WAPPEN_CHANCE
|
||||
_wurf_nr += 1
|
||||
mitte[seat] = {"id": id, "stark": stark, "nr": _wurf_nr}
|
||||
topf.append({"seat": seat, "id": id, "stark": stark, "nr": _wurf_nr})
|
||||
while topf.size() > BoosterDB.TOPF_MAX:
|
||||
var weg: Dictionary = topf.pop_front()
|
||||
_log("%s fällt vom Tisch - der Topf ist voll." % BoosterDB.get_booster(String(weg.id)).name)
|
||||
_log("%s wirft %s - %s." % [seats[seat].name,
|
||||
BoosterDB.get_booster(id).name, "Wappen" if stark else "Zahl"])
|
||||
|
||||
@@ -305,12 +317,6 @@ func _do_play(seat: int, id: String, target: int) -> void:
|
||||
_muenze_laeuft = false
|
||||
if not _alive():
|
||||
return
|
||||
# Der Widerruf hat keine eigene Verstärkung; bei Wappen bleibt er liegen.
|
||||
if stark and BoosterDB.keeps_on_wappen(id):
|
||||
var zurueck: Array = hand.get(seat, [])
|
||||
if zurueck.size() < BoosterDB.HANDLIMIT:
|
||||
zurueck.append(id)
|
||||
hand[seat] = zurueck
|
||||
_snapshot_all()
|
||||
_push_state()
|
||||
|
||||
@@ -319,15 +325,21 @@ func _wirkung(seat: int, id: String, target: int, stark: bool) -> void:
|
||||
var vals: Array = host_values[seat]
|
||||
var ziel: int = clampi(target, 0, maxi(0, vals.size() - 1))
|
||||
match id:
|
||||
"reroll_all":
|
||||
await _neu_werfen(seat, range(vals.size()))
|
||||
if stark and not host_values[seat].is_empty():
|
||||
_set_die(seat, rng.randi_range(0, host_values[seat].size() - 1), 6)
|
||||
"reroll_one":
|
||||
await _neu_werfen(seat, _ziel_und_zufall(seat, ziel, stark))
|
||||
"set_six":
|
||||
"peek":
|
||||
var info := {}
|
||||
for o in _alive_seats():
|
||||
if o != seat:
|
||||
info[o] = _zufalls_paare(o, 2 if stark else 1)
|
||||
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 := _count_face(int(bid.face))
|
||||
var von: int = n if stark else maxi(0, n - rng.randi_range(0, 2))
|
||||
gezaehlt[seat] = {int(bid.face): [von, von if stark else von + 2]}
|
||||
"set_face":
|
||||
for k: int in _ziel_und_zufall(seat, ziel, stark):
|
||||
_set_die(seat, k, 6)
|
||||
_set_die(seat, k, int(bid.face))
|
||||
"gain_die":
|
||||
seats[seat].count += 1
|
||||
table.set_dice_count(seat, seats[seat].count)
|
||||
@@ -336,46 +348,26 @@ func _wirkung(seat: int, id: String, target: int, stark: bool) -> void:
|
||||
await _neu_werfen(seat, [neu])
|
||||
if stark:
|
||||
_set_die(seat, neu, 6)
|
||||
"cancel":
|
||||
fogged.erase(seat)
|
||||
min_raise.erase(seat)
|
||||
must_bid.erase(seat)
|
||||
"peek":
|
||||
var info := {}
|
||||
for o in _alive_seats():
|
||||
if o != seat:
|
||||
info[o] = _zufalls_paare(o, 2 if stark else 1)
|
||||
peeked[seat] = info
|
||||
"bid_up":
|
||||
if not bid.is_empty():
|
||||
bid.count = int(bid.count) + (2 if stark else 1)
|
||||
last_bids[int(bid.seat)] = {"count": bid.count, "face": bid.face}
|
||||
"enemy_lower":
|
||||
var minus := 2 if stark else 1
|
||||
for o in _alive_seats():
|
||||
if o != seat:
|
||||
for k: int in _zufalls_idx(o, 1):
|
||||
_set_die(o, k, maxi(1, int(host_values[o][k]) - minus))
|
||||
"fog":
|
||||
for o in _alive_seats():
|
||||
if o == seat:
|
||||
continue
|
||||
var liste: Array = fogged.get(o, [])
|
||||
for k: int in _zufalls_idx(o, 2 if stark else 1):
|
||||
if not liste.has(k):
|
||||
liste.append(k)
|
||||
fogged[o] = liste
|
||||
"raise_min":
|
||||
for o in _alive_seats():
|
||||
if o != seat:
|
||||
min_raise[o] = 3 if stark else 2
|
||||
bid.count = int(bid.count) + (2 if stark else 1)
|
||||
last_bids[int(bid.seat)] = {"count": bid.count, "face": bid.face}
|
||||
verlauf.append({"seat": int(bid.seat), "count": bid.count, "face": bid.face, "erhoeht": true})
|
||||
"gag":
|
||||
if stark:
|
||||
for o in _alive_seats():
|
||||
if o != seat:
|
||||
must_bid[o] = true
|
||||
else:
|
||||
must_bid[turn] = true
|
||||
must_bid[_next_alive(seat)] = true
|
||||
|
||||
|
||||
func _count_face(face: int) -> int:
|
||||
var n := 0
|
||||
for i in _alive_seats():
|
||||
for v in host_values[i]:
|
||||
if v == face:
|
||||
n += 1
|
||||
return n
|
||||
|
||||
|
||||
## Bis zu `n` verschiedene Würfelplätze eines Sitzes, zufällig gezogen.
|
||||
@@ -453,12 +445,15 @@ func _phase_bid() -> Dictionary:
|
||||
_push_state()
|
||||
if seats[turn].ai:
|
||||
await _sleep(1.1)
|
||||
# Bots werfen ihre Münzen, bevor sie ansagen.
|
||||
await _bot_muenze(turn)
|
||||
# Erst planen, dann passend dazu eine Münze werfen. Die kann die
|
||||
# Lage ändern - also danach noch einmal entscheiden.
|
||||
var a := DiceAI.choose_action(host_values[turn], _total_dice(), bid,
|
||||
must_bid.get(turn, false), rng)
|
||||
if await _bot_muenze(turn, a):
|
||||
a = DiceAI.choose_action(host_values[turn], _total_dice(), bid,
|
||||
must_bid.get(turn, false), rng)
|
||||
if not _alive():
|
||||
return {}
|
||||
var a := DiceAI.choose_action(host_values[turn], _total_dice(), bid,
|
||||
min_raise.get(turn, 1), must_bid.get(turn, false), rng)
|
||||
var res := _apply_action(turn, a)
|
||||
if not res.is_empty():
|
||||
return res
|
||||
@@ -471,7 +466,7 @@ func _phase_bid() -> Dictionary:
|
||||
else:
|
||||
# Zeit abgelaufen oder getrennt: automatisch entscheiden
|
||||
var a := DiceAI.choose_action(host_values[turn], _total_dice(), bid,
|
||||
min_raise.get(turn, 1), must_bid.get(turn, false), rng)
|
||||
must_bid.get(turn, false), rng)
|
||||
_log("%s wird automatisch gespielt." % seats[turn].name)
|
||||
var res := _apply_action(turn, a)
|
||||
if not res.is_empty():
|
||||
@@ -480,24 +475,66 @@ func _phase_bid() -> Dictionary:
|
||||
return {}
|
||||
|
||||
|
||||
## Ein Bot wirft manchmal eine Münze, statt sie bis zum Ausscheiden zu horten.
|
||||
func _bot_muenze(seat: int) -> void:
|
||||
## 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 = hand.get(seat, [])
|
||||
if muenzen.is_empty():
|
||||
return
|
||||
# Voller Vorrat: raus damit. Sonst nur ab und zu, sonst ist alles sofort weg.
|
||||
var lust := 1.0 if muenzen.size() >= BoosterDB.HANDLIMIT else 0.35
|
||||
if rng.randf() > lust:
|
||||
return
|
||||
var id: String = muenzen[rng.randi_range(0, muenzen.size() - 1)]
|
||||
if id == "bid_up" and bid.is_empty():
|
||||
return
|
||||
await _do_play(seat, id, DiceAI.target_for(id, host_values[seat]))
|
||||
return false
|
||||
var vals: Array = host_values[seat]
|
||||
var unknown: int = _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(bid.count), int(bid.face)) if not bid.is_empty() else 0.0
|
||||
|
||||
var wahl := ""
|
||||
var ziel := 0
|
||||
# Hinten liegen: Nachschub ist der einzige Weg zurück.
|
||||
if muenzen.has("gain_die") and vals.size() <= _kleinste_hand():
|
||||
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 bid.is_empty() and int(a.face) == int(bid.face) \
|
||||
and muenzen.has("set_face") and vals.count(int(bid.face)) < vals.size():
|
||||
wahl = "set_face"
|
||||
ziel = DiceAI.target_for(vals, int(bid.face))
|
||||
# Voller Vorrat: irgendwas Spielbares raus, sonst wird nie geworfen.
|
||||
elif muenzen.size() >= BoosterDB.HANDLIMIT:
|
||||
for id: String in muenzen:
|
||||
if not BoosterDB.needs_bid(id) or not bid.is_empty():
|
||||
wahl = id
|
||||
break
|
||||
if wahl == "":
|
||||
return false
|
||||
await _do_play(seat, wahl, ziel)
|
||||
await _sleep(0.6)
|
||||
return true
|
||||
|
||||
|
||||
## Die kleinste Würfelzahl am Tisch - wer da liegt, ist hinten.
|
||||
func _kleinste_hand() -> int:
|
||||
var k := 99
|
||||
for i in _alive_seats():
|
||||
k = mini(k, seats[i].count)
|
||||
return k
|
||||
|
||||
|
||||
## Gibt {} zurück wenn das Spiel weitergeht, sonst das Anfechtungs-Ergebnis.
|
||||
func _apply_action(seat: int, a: Dictionary) -> Dictionary:
|
||||
if a.type == "exact":
|
||||
_log("%s ruft: Genau!" % seats[seat].name)
|
||||
return {"challenger": seat, "bid": bid.duplicate(), "exact": true}
|
||||
if a.type == "bid" and not _valid_bid(seat, int(a.count), int(a.face)):
|
||||
# Wer einen Maulkorb hat, darf sich nicht über ein ungültiges Gebot in
|
||||
# die Anfechtung retten - dann wird auf das kleinste gültige korrigiert.
|
||||
@@ -513,22 +550,17 @@ func _apply_action(seat: int, a: Dictionary) -> Dictionary:
|
||||
return {"challenger": seat, "bid": bid.duplicate()}
|
||||
bid = {"seat": seat, "count": int(a.count), "face": int(a.face)}
|
||||
last_bids[seat] = {"count": bid.count, "face": bid.face}
|
||||
verlauf.append({"seat": seat, "count": bid.count, "face": bid.face, "erhoeht": false})
|
||||
must_bid.erase(seat)
|
||||
_log("%s sagt %d x %d an." % [seats[seat].name, bid.count, bid.face])
|
||||
return {}
|
||||
|
||||
|
||||
## Kleinstes Gebot, das dieser Platz noch ansagen darf ({} = gar keins mehr).
|
||||
func _lowest_legal_bid(seat: int) -> Dictionary:
|
||||
func _lowest_legal_bid(_seat: int) -> Dictionary:
|
||||
if bid.is_empty():
|
||||
return {"count": 1, "face": 1}
|
||||
var mr: int = min_raise.get(seat, 1)
|
||||
if mr == 1 and int(bid.face) < 6:
|
||||
return {"count": int(bid.count), "face": int(bid.face) + 1}
|
||||
var c: int = int(bid.count) + mr
|
||||
if c <= _total_dice():
|
||||
return {"count": c, "face": 1}
|
||||
return {}
|
||||
return DiceAI.lowest_raise(bid, _total_dice())
|
||||
|
||||
|
||||
func _valid_bid(seat: int, count: int, face: int) -> bool:
|
||||
@@ -536,11 +568,8 @@ func _valid_bid(seat: int, count: int, face: int) -> bool:
|
||||
return false
|
||||
if bid.is_empty():
|
||||
return true
|
||||
var mr: int = min_raise.get(seat, 1)
|
||||
if count >= bid.count + mr:
|
||||
if count > bid.count:
|
||||
return true
|
||||
if mr > 1:
|
||||
return false
|
||||
return count == bid.count and face > bid.face
|
||||
|
||||
|
||||
@@ -548,19 +577,13 @@ func _phase_reveal(result: Dictionary) -> void:
|
||||
phase = "reveal"
|
||||
deadline = 0.0
|
||||
var b: Dictionary = result.bid
|
||||
var challenger: int = result.challenger
|
||||
var total := 0
|
||||
for i in _alive_seats():
|
||||
for v in host_values[i]:
|
||||
if v == b.face:
|
||||
total += 1
|
||||
|
||||
reveal_total = total
|
||||
var loser := challenger if total >= b.count else int(b.seat)
|
||||
_log("%d x %d angesagt - tatsächlich %d. %s verliert einen Würfel." % [
|
||||
b.count, b.face, total, seats[loser].name])
|
||||
banner = "%s verliert" % seats[loser].name
|
||||
var rufer: int = result.challenger
|
||||
var genau: bool = result.get("exact", false)
|
||||
var total := _count_face(int(b.face))
|
||||
|
||||
# 1. Becher hoch. Noch kein Urteil - erst schauen.
|
||||
reveal_total = -1
|
||||
banner = "Genau?" if genau else "Aufdecken"
|
||||
for i in _alive_seats():
|
||||
table.arrange_and_show(i, host_values[i], rng)
|
||||
table.set_cup_down(i, false)
|
||||
@@ -572,20 +595,64 @@ func _phase_reveal(result: Dictionary) -> void:
|
||||
if _peer_live(peer):
|
||||
_reveal.rpc_id(peer, values, banner)
|
||||
_push_state()
|
||||
await _sleep(REVEAL_PAUSE)
|
||||
await _sleep(ZEIT_BECHER)
|
||||
if not _alive():
|
||||
return
|
||||
|
||||
seats[loser].count -= 1
|
||||
if seats[loser].count <= 0:
|
||||
seats[loser].alive = false
|
||||
seats[loser].count = 0
|
||||
host_values[loser] = []
|
||||
table.set_dice_count(loser, 0)
|
||||
_log("%s scheidet aus." % seats[loser].name)
|
||||
# 2. Zählen. Die Ansage in der Mitte färbt sich jetzt ein.
|
||||
reveal_total = total
|
||||
banner = "%d gefunden, %d angesagt" % [total, b.count]
|
||||
_push_state()
|
||||
await _sleep(ZEIT_ZAEHLEN)
|
||||
if not _alive():
|
||||
return
|
||||
|
||||
# 3. Urteil. Bei "Genau" trifft es alle anderen - oder nur den Rufer.
|
||||
var verlierer: Array = []
|
||||
var winner: int
|
||||
var bonus := -1
|
||||
if genau and total == int(b.count):
|
||||
# Fester Schwung von zwei, egal wie viele am Tisch sitzen: der Ansager
|
||||
# verliert einen, der Rufer bekommt einen. Mehr als eine Anfechtung
|
||||
# bringt, weniger als "alle anderen" - das kippte bei vielen Spielern.
|
||||
verlierer = [int(b.seat)]
|
||||
winner = rufer
|
||||
bonus = rufer
|
||||
banner = "Genau! %s verliert, %s bekommt einen Würfel" % [
|
||||
seats[int(b.seat)].name, seats[rufer].name]
|
||||
elif genau:
|
||||
verlierer = [rufer]
|
||||
winner = int(b.seat)
|
||||
banner = "Daneben - %s verliert" % seats[rufer].name
|
||||
else:
|
||||
_zieh_muenze(loser)
|
||||
starter = loser if seats[loser].alive else _next_alive(loser)
|
||||
var loser := rufer if total >= int(b.count) else int(b.seat)
|
||||
verlierer = [loser]
|
||||
winner = int(b.seat) if loser == rufer else rufer
|
||||
banner = "%s verliert" % seats[loser].name
|
||||
_log("%d x %d angesagt - tatsächlich %d. %s" % [b.count, b.face, total, banner])
|
||||
_topf_an(winner)
|
||||
_push_state()
|
||||
await _sleep(ZEIT_URTEIL)
|
||||
if not _alive():
|
||||
return
|
||||
|
||||
for l: int in verlierer:
|
||||
seats[l].count -= 1
|
||||
if seats[l].count <= 0:
|
||||
seats[l].alive = false
|
||||
seats[l].count = 0
|
||||
host_values[l] = []
|
||||
table.set_dice_count(l, 0)
|
||||
_log("%s scheidet aus." % seats[l].name)
|
||||
else:
|
||||
_zieh_muenze(l)
|
||||
if bonus >= 0 and seats[bonus].alive:
|
||||
seats[bonus].count += 1
|
||||
host_values[bonus].append(rng.randi_range(1, 6))
|
||||
table.set_dice_count(bonus, seats[bonus].count)
|
||||
# Nach einem Treffer beginnt der Rufer, sonst der Verlierer.
|
||||
var naechster: int = winner if genau and total == int(b.count) else verlierer[0]
|
||||
starter = naechster if seats[naechster].alive else _next_alive(naechster)
|
||||
# Nur wer noch spielt, bekommt den Becher wieder. Auf einer leeren Zone
|
||||
# sähe er aus, als lägen dort noch Würfel.
|
||||
for i in seats.size():
|
||||
@@ -595,6 +662,22 @@ func _phase_reveal(result: Dictionary) -> void:
|
||||
_push_state()
|
||||
|
||||
|
||||
## 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 topf.is_empty():
|
||||
return
|
||||
var h: Array = hand.get(winner, [])
|
||||
var genommen := 0
|
||||
while not topf.is_empty() and h.size() < BoosterDB.HANDLIMIT:
|
||||
h.append(String(topf.pop_front().id))
|
||||
genommen += 1
|
||||
hand[winner] = h
|
||||
_log("%s nimmt %d Münze(n) aus dem Topf, %d bleiben." % [
|
||||
seats[winner].name, genommen, topf.size()])
|
||||
|
||||
|
||||
func _all_values() -> Array:
|
||||
var out: Array = []
|
||||
for i in seats.size():
|
||||
@@ -715,15 +798,9 @@ func _push_state() -> void:
|
||||
_state.rpc_id(peer, _state_for(i))
|
||||
|
||||
|
||||
## Welche Münze liegt von wem in der Mitte? Beim Würfeln steht noch nichts
|
||||
## an, danach ist es offen.
|
||||
## Was liegt im Topf? Der bleibt auch übers Würfeln hinweg liegen.
|
||||
func _round_boosters() -> Array:
|
||||
var out: Array = []
|
||||
if phase == "roll":
|
||||
return out
|
||||
for i in seats.size():
|
||||
out.append(mitte.get(i, {}))
|
||||
return out
|
||||
return topf.duplicate(true)
|
||||
|
||||
|
||||
## Was jeder Platz in dieser Runde zuletzt angesagt hat.
|
||||
@@ -739,25 +816,22 @@ func _state_for(seat: int) -> Dictionary:
|
||||
if phase == "roll":
|
||||
for _k in seats[seat].count: # während des Wurfs steht der Wert noch nicht fest
|
||||
vis.append(-1)
|
||||
elif phase == "reveal":
|
||||
vis = host_values[seat].duplicate() # aufgedeckt wird ohne Nebel
|
||||
else:
|
||||
var fg: Array = fogged.get(seat, [])
|
||||
for k in host_values[seat].size():
|
||||
vis.append(-1 if fg.has(k) else host_values[seat][k])
|
||||
vis = host_values[seat].duplicate()
|
||||
var pub: Array = []
|
||||
for s in seats:
|
||||
pub.append({"name": s.name, "count": s.count, "alive": s.alive, "ai": s.ai})
|
||||
return {
|
||||
"phase": phase, "turn": turn, "bid": bid, "seats": pub, "values": vis,
|
||||
"banner": banner,
|
||||
"peek": peeked.get(seat, {}), "min_raise": min_raise.get(seat, 1),
|
||||
"peek": peeked.get(seat, {}), "zaehlung": gezaehlt.get(seat, {}),
|
||||
"must_bid": must_bid.get(seat, false), "total": _total_dice(),
|
||||
"time_left": 0.0 if deadline <= 0.0 else maxf(0.0, deadline - _now()),
|
||||
"open": _all_values() if phase == "reveal" else [],
|
||||
"boosts": _round_boosters(),
|
||||
"actual": reveal_total if phase == "reveal" else 0,
|
||||
"bids": _seat_bids(),
|
||||
"verlauf": verlauf.duplicate(true),
|
||||
"hand": (hand.get(seat, []) as Array).duplicate(),
|
||||
}
|
||||
|
||||
@@ -791,7 +865,23 @@ func _apply_state(st: Dictionary) -> void:
|
||||
table.set_cup_down(i, seats[i].alive)
|
||||
table.set_dice_visible(local_seat, true)
|
||||
_refresh_labels()
|
||||
_apply_fog_to_table()
|
||||
table.set_topf(st.get("boosts", []))
|
||||
if phase == "reveal" and int(st.get("actual", -1)) >= 0 and not _gezaehlt_gezeigt:
|
||||
_gezaehlt_gezeigt = true
|
||||
if not bid.is_empty():
|
||||
table.zaehlen_zeigen(int(bid.face), st.get("open", []))
|
||||
elif phase != "reveal" and _gezaehlt_gezeigt:
|
||||
_gezaehlt_gezeigt = false
|
||||
table.hervorhebung_weg()
|
||||
if phase != _klang_phase:
|
||||
_klang_phase = phase
|
||||
match phase:
|
||||
"roll":
|
||||
Klang.wuerfel()
|
||||
"reveal":
|
||||
Klang.becher()
|
||||
"over":
|
||||
Klang.chime()
|
||||
var bids: Array = st.get("bids", [])
|
||||
for i in mini(seats.size(), bids.size()):
|
||||
var b: Dictionary = bids[i]
|
||||
@@ -804,15 +894,6 @@ func _apply_state(st: Dictionary) -> void:
|
||||
|
||||
# ---------------------------------------------------------------- Aktionen
|
||||
|
||||
## Der Nebel-Debuff muss auch den Würfel auf dem Tisch verdecken.
|
||||
func _apply_fog_to_table() -> void:
|
||||
if phase == "roll" or local_seat < 0 or local_seat >= table.dice.size():
|
||||
return
|
||||
var dl: Array = table.dice_of(local_seat)
|
||||
for i in mini(dl.size(), my_values.size()):
|
||||
dl[i].visible = my_values[i] >= 0
|
||||
|
||||
|
||||
func _on_bid_pressed(count: int, face: int) -> void:
|
||||
if multiplayer.is_server():
|
||||
_do_bid(local_seat, count, face)
|
||||
@@ -827,6 +908,13 @@ func _on_challenge_pressed() -> void:
|
||||
_req_challenge.rpc_id(1)
|
||||
|
||||
|
||||
func _on_exact_pressed() -> void:
|
||||
if multiplayer.is_server():
|
||||
_do_exact(local_seat)
|
||||
else:
|
||||
_req_exact.rpc_id(1)
|
||||
|
||||
|
||||
func _on_booster_chosen(id: String, target: int) -> void:
|
||||
if multiplayer.is_server():
|
||||
_do_play(local_seat, id, target)
|
||||
@@ -849,6 +937,19 @@ func _on_pause_changed(on: bool) -> void:
|
||||
_push_state()
|
||||
|
||||
|
||||
## Noch eine Solopartie mit denselben Bots. Der Wunsch wandert über Net ins
|
||||
## neu geladene Menü, das dann sofort startet.
|
||||
func _on_rematch() -> void:
|
||||
if _quitting:
|
||||
return
|
||||
var bots := 0
|
||||
for s in seats:
|
||||
if s.ai:
|
||||
bots += 1
|
||||
Net.rematch = {"name": Net.player_name, "ai": bots, "stufe": DiceAI.stufe}
|
||||
_on_quit()
|
||||
|
||||
|
||||
func _on_quit() -> void:
|
||||
if _quitting:
|
||||
return
|
||||
@@ -871,6 +972,11 @@ func _req_challenge() -> void:
|
||||
_do_challenge(_seat_of(multiplayer.get_remote_sender_id()))
|
||||
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _req_exact() -> void:
|
||||
_do_exact(_seat_of(multiplayer.get_remote_sender_id()))
|
||||
|
||||
|
||||
@rpc("any_peer", "reliable")
|
||||
func _req_boost(id: String, target: int) -> void:
|
||||
_do_play(_seat_of(multiplayer.get_remote_sender_id()), id, target)
|
||||
@@ -904,3 +1010,15 @@ func _do_challenge(seat: int) -> void:
|
||||
return
|
||||
_answered[seat] = _apply_action(seat, {"type": "challenge"})
|
||||
|
||||
|
||||
## "Genau" unterliegt denselben Regeln wie die Anfechtung: nur am Zug, nur
|
||||
## mit Ansage, und der Maulkorb sperrt es genauso.
|
||||
func _do_exact(seat: int) -> void:
|
||||
if seat < 0 or phase != "bid" or seat != turn or _answered.has(seat):
|
||||
return
|
||||
if bid.is_empty():
|
||||
return
|
||||
if must_bid.get(seat, false) and not _lowest_legal_bid(seat).is_empty():
|
||||
return
|
||||
_answered[seat] = _apply_action(seat, {"type": "exact"})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user