diff options
| author | historia <[not public]> | 2026-09-18 05:31:09 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-18 05:31:09 -0400 |
| commit | 153acb7f644d755c45533b7c5311e3ffa47e8973 (patch) | |
| tree | 7645e6f7b65bc3830b078e3b9336d2e6b199d584 /internal/casino/cards.go | |
| parent | 87d08f0151e643951769e323995f1c5ed89ee200 (diff) | |
| download | thehouseoficarus-153acb7f644d755c45533b7c5311e3ffa47e8973.tar.gz | |
fix: harden engine across casino, economy, concurrency, security, and content
Diffstat (limited to 'internal/casino/cards.go')
| -rw-r--r-- | internal/casino/cards.go | 31 |
1 files changed, 27 insertions, 4 deletions
diff --git a/internal/casino/cards.go b/internal/casino/cards.go index b1c2cbb..27da8aa 100644 --- a/internal/casino/cards.go +++ b/internal/casino/cards.go @@ -68,12 +68,17 @@ func (s *shoe) shuffle() { s.rng.Shuffle(len(s.cards), func(i, j int) { s.cards[i], s.cards[j] = s.cards[j], s.cards[i] }) } +// remaining reports the cards left in the shoe. +func (s *shoe) remaining() int { return len(s.cards) } + func (s *shoe) draw() card { if len(s.cards) == 0 { - // The shoe ran dry mid-round (only possible when the configured - // deck count is very small relative to the table): rebuild it - // rather than panic. The cut-card reshuffle itself happens - // between rounds via shuffleIfDue, never per draw. + // Shuffle-through: if the shoe runs out mid-round it is rebuilt from + // all decks, so a card already dealt this round can reappear. This + // matches real blackjack, where an exhausted shoe is shuffled through + // mid-round; ensureForRound makes it rare by reshuffling at round + // boundaries, and this keeps a directly-constructed table from + // panicking. s.shuffle() } card := s.cards[len(s.cards)-1] @@ -81,6 +86,24 @@ func (s *shoe) draw() card { return card } +// ensureForRound reshuffles when the shoe is unlikely to cover the round that +// is about to be dealt. It MUST be called at a round boundary, before any card +// of that round is dealt: a rebuild there cannot redeal a card the current +// round already holds, which is exactly what draw()'s exhausted-shoe +// shuffle-through does. need is a heuristic estimate of the cards one round +// can consume (see blackjackRoundCards/baccaratRoundCards), not a certified +// maximum, so coverage is best-effort. +func (s *shoe) ensureForRound(need int) { + if s.decks <= 0 { + // No deck composition to refill from: this is a synthetic shoe with + // an explicit card script (tests), which must not be replaced. + return + } + if len(s.cards) < need { + s.shuffle() + } +} + // shuffleIfDue reshuffles the shoe once its remaining cards have fallen // to the shuffleAt fraction (the cut card). It must be called at round // boundaries only — shuffling between individual draws would let cards |
