aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/blackjack.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/casino/blackjack.go')
-rw-r--r--internal/casino/blackjack.go35
1 files changed, 27 insertions, 8 deletions
diff --git a/internal/casino/blackjack.go b/internal/casino/blackjack.go
index 7b30699..16a480c 100644
--- a/internal/casino/blackjack.go
+++ b/internal/casino/blackjack.go
@@ -75,8 +75,14 @@ type blackjackTable struct {
ceiling countdown
activePlayer int
activeHand int
- dealer blackjackHand
- shoe *shoe
+ // turnName/turnHand identify the current turn so the absolute turn
+ // ceiling is restarted only when the turn actually changes. A third party
+ // joining or leaving mid-round re-prompts the same turn and must not
+ // extend a hung bettor's deadline.
+ turnName string
+ turnHand int
+ dealer blackjackHand
+ shoe *shoe
}
// loneTurnGrace multiplies the action window into an absolute per-turn
@@ -94,7 +100,7 @@ func newBlackjackTable(roomID int, cfg TableConfig, rng *rand.Rand) *blackjackTa
return &blackjackTable{
tableBase: tableBase{RoomID: roomID, Config: cfg}, Rules: rules,
players: newSeatSet[*blackjackPlayer](), phase: blackjackWaiting,
- shoe: newShoe(rules.Decks, rules.ShuffleAt, rng),
+ shoe: newShoe(rules.Decks, rules.ShuffleAt, rng), turnHand: -1,
}
}
@@ -465,6 +471,7 @@ func (t *blackjackTable) beginPlay() {
t.phase = blackjackPlaying
t.timer.start(t.Rules.ActionTicks)
t.activePlayer, t.activeHand = 0, 0
+ t.turnName, t.turnHand = "", -1
}
// roundCardCeiling is the heuristic number of cards this table's round is
@@ -475,19 +482,29 @@ func (t *blackjackTable) beginPlay() {
// guaranteed to hold it and draw() may still rebuild an exhausted shoe
// mid-round (a deliberate shuffle-through).
func (t *blackjackTable) roundCardCeiling() int {
- return blackjackRoundCards(t.Config.MaxPlayers, t.Rules.MaxSplitHands)
+ // Size the boundary reshuffle from the seats actually in the round, not
+ // the configured maximum: using MaxPlayers made ensureForRound dominate
+ // the cut card, so shuffle_at never fired on a table whose maximum seats
+ // implied more cards than the cut card allowed.
+ return blackjackRoundCards(t.bettorCount(), t.Rules.MaxSplitHands)
}
func (t *blackjackTable) prompt() []Event {
// Every freshly-prompted turn gets a full action window; rejected inputs
- // and timeouts can never extend the deadline. The turn ceiling restarts
- // with the window for the same reason.
+ // and timeouts can never extend the deadline.
t.timer.start(t.Rules.ActionTicks)
- t.ceiling.start(t.Rules.ActionTicks * loneTurnGrace)
name := t.activeName()
if name == "" {
return t.settle()
}
+ // The absolute turn ceiling belongs to the turn, not the prompt: restart
+ // it only when the active (player, hand) changes. A seat event that
+ // re-prompts the same turn must not reset a hung bettor's deadline.
+ if name != t.turnName || t.activeHand != t.turnHand {
+ t.ceiling.start(t.Rules.ActionTicks * loneTurnGrace)
+ t.turnName = name
+ t.turnHand = t.activeHand
+ }
p := t.players.get(name)
h := p.hands[t.activeHand]
ranks := make([]string, len(h.cards))
@@ -710,6 +727,7 @@ func (t *blackjackTable) resetRound() {
t.window.close()
t.timer.stop()
t.ceiling.stop()
+ t.turnName, t.turnHand = "", -1
for _, p := range t.players.ordered() {
p.HasBet, p.Wager, p.hands, p.insurance, p.insuranceStakeID, p.declinedInsurance = false, Wager{}, nil, 0, "", false
}
@@ -837,7 +855,8 @@ func (t *blackjackTable) markDisconnectedLocked(name string) []Event {
}
switch {
case index < t.activePlayer:
- // The same player stays active, one seat earlier.
+ // The same player stays active, one seat earlier. prompt() sees the
+ // unchanged turn and keeps the existing ceiling.
t.activePlayer--
case index == t.activePlayer:
// The next bettor slides into the active seat.