diff options
| author | historia <[not public]> | 2026-09-18 18:38:35 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-18 18:38:35 -0400 |
| commit | 1f118d79e319da4e765502e9ca5b661772de94f1 (patch) | |
| tree | bca8a3fd1e538f1c302480511e6ba872c92e10b8 /internal/casino/blackjack.go | |
| parent | 5ad42a51df4dcc5ccc3aa6a6d902085ce2ee4239 (diff) | |
| download | thehouseoficarus-1f118d79e319da4e765502e9ca5b661772de94f1.tar.gz | |
refactor: casino table core; fix: stake/migration/validation gaps
Diffstat (limited to 'internal/casino/blackjack.go')
| -rw-r--r-- | internal/casino/blackjack.go | 102 |
1 files changed, 21 insertions, 81 deletions
diff --git a/internal/casino/blackjack.go b/internal/casino/blackjack.go index fd0572b..fe212f7 100644 --- a/internal/casino/blackjack.go +++ b/internal/casino/blackjack.go @@ -6,16 +6,15 @@ import ( "fmt" "math/rand" "strings" - "sync" ) -type blackjackPhase string - +// Blackjack uses the shared table phases; these aliases keep the rule +// vocabulary readable at the call sites. const ( - blackjackWaiting blackjackPhase = "waiting" - blackjackBetting blackjackPhase = "betting" - blackjackInsurance blackjackPhase = "insurance" - blackjackPlaying blackjackPhase = "playing" + blackjackWaiting = phaseWaiting + blackjackBetting = phaseBetting + blackjackInsurance = phaseInsurance + blackjackPlaying = phasePlaying ) type blackjackHand struct { @@ -50,7 +49,8 @@ func (h *blackjackHand) total() (int, bool) { func (h *blackjackHand) blackjack(rules BlackjackConfig) bool { total, _ := h.total() - return len(h.cards) == 2 && !h.fromSplit && total == 21 || rules.CountsDoubleSplitBlackjack() && h.fromSplit && h.doubled && total == 21 + return (len(h.cards) == 2 && !h.fromSplit && total == 21) || + (rules.CountsDoubleSplitBlackjack() && h.fromSplit && h.doubled && total == 21) } type blackjackPlayer struct { @@ -65,12 +65,8 @@ type blackjackPlayer struct { func (p *blackjackPlayer) participantPtr() *Participant { return &p.Participant } type blackjackTable struct { - mu sync.Mutex - tableBase + tableCore[*blackjackPlayer] Rules BlackjackConfig - players seatSet[*blackjackPlayer] - phase blackjackPhase - window bettingWindow timer countdown ceiling countdown activePlayer int @@ -98,21 +94,19 @@ func newBlackjackTable(roomID int, cfg TableConfig, rng *rand.Rand) *blackjackTa cfg = cfg.Normalize() rules := cfg.Blackjack.Normalize() return &blackjackTable{ - tableBase: tableBase{RoomID: roomID, Config: cfg}, Rules: rules, - players: newSeatSet[*blackjackPlayer](), phase: blackjackWaiting, - shoe: newShoe(rules.Decks, rules.ShuffleAt, rng), turnHand: -1, + tableCore: newTableCore[*blackjackPlayer](&tableBase{RoomID: roomID, Config: cfg}), + Rules: rules, + shoe: newShoe(rules.Decks, rules.ShuffleAt, rng), + turnHand: -1, } } func (t *blackjackTable) menuEvent(name string) Event { - e := t.private(name, t.menu()) - e.Color = "casino_menu" - e.Menu = &MenuView{Kind: MenuBlackjackBet, Commands: []MenuCommand{ + return t.betMenuEvent(name, MenuBlackjackBet, []MenuCommand{ {Command: "bet", Desc: "Place a wager (min/max accepted)"}, {Command: "bet <amount>", Desc: "Place a specific wager"}, {Command: "stop", Desc: "Leave the table"}, - }} - return e + }) } func (t *blackjackTable) join(name string) []Event { @@ -124,8 +118,7 @@ func (t *blackjackTable) join(name string) []Event { if t.players.count() >= t.Config.MaxPlayers { return []Event{t.private(name, "That blackjack table is full.")} } - t.players.add(name, &blackjackPlayer{Participant: Participant{Name: name, Connected: true}}) - events := []Event{t.playerAction(name, fmt.Sprintf("%s sits down.", name), "You sit down.")} + events := []Event{t.addSeatLocked(name, &blackjackPlayer{Participant: Participant{Name: name, Connected: true}})} events = append(events, t.menuEvent(name)) return events } @@ -175,11 +168,7 @@ func (t *blackjackTable) bet(name, spot string, amount int, wallet Wallet) []Eve } p.HasBet, p.Wager, p.Wallet, p.lastBet = true, wager, wallet, amount events := []Event{t.playerAction(name, fmt.Sprintf("%s bets %d credits.", name, amount), fmt.Sprintf("You bet %d credits.", amount))} - if t.players.count() > 1 && t.phase == blackjackWaiting { - t.phase = blackjackBetting - t.window.open(t.Config.BettingTicks) - events = append(events, t.event(fmt.Sprintf("Blackjack betting is open for %d ticks.", t.Config.BettingTicks), true)) - } + events = t.openBettingLocked(events) if allBet(t.players.participants()) { events = append(events, t.startRound()...) } @@ -191,20 +180,7 @@ func (t *blackjackTable) tick() []Event { defer t.mu.Unlock() switch t.phase { case blackjackBetting: - sitOuts, resolve, pending := t.window.advance(t.players.participants()) - if pending { - return nil - } - var events []Event - for _, name := range sitOuts { - events = append(events, t.event(fmt.Sprintf("%s sits out this blackjack round.", name), true)) - } - if resolve { - events = append(events, t.startRound()...) - } else { - t.resetRound() - } - return events + return t.advanceBettingLocked(t.startRound, t.resetRound) case blackjackInsurance: if t.insuranceSettled() { return t.finishInsurance() @@ -807,10 +783,6 @@ func (t *blackjackTable) snapshot(revealDealer bool) *BlackjackSnapshot { return snapshot } -func (t *blackjackTable) menu() string { - return "New blackjack hand! Place your bet." -} - func handScore(hand *blackjackHand) string { total, soft := hand.total() if !soft || total == 21 { @@ -859,11 +831,9 @@ func (t *blackjackTable) markDisconnectedLocked(name string) []Event { p.Wallet.RefundOutstanding() } } - t.players.remove(name) + t.removeSeatLocked(name) events := []Event{t.event(fmt.Sprintf("%s stands up.", name), true)} - if t.players.count() == 0 { - events = append(events, t.event("The blackjack table closes.", true)) - } + events = t.closeIfEmptyLocked(events) if t.phase != blackjackPlaying { // Betting and insurance resolve against the remaining seats on the // next tick; waiting has no round in flight. @@ -882,12 +852,6 @@ func (t *blackjackTable) markDisconnectedLocked(name string) []Event { return append(events, t.finishHand()...) } -func (t *blackjackTable) hasParticipant(name string) bool { - t.mu.Lock() - defer t.mu.Unlock() - return t.players.has(name) -} - // refundAll refunds every committed wager at this table (graceful // shutdown/eviction): the wallet returns every outstanding stake — opening // bet, doubles, splits, and insurance — exactly once. It then RESETS the @@ -897,31 +861,7 @@ func (t *blackjackTable) hasParticipant(name string) bool { func (t *blackjackTable) refundAll() []Event { t.mu.Lock() defer t.mu.Unlock() - var events []Event - for _, p := range t.players.ordered() { - if !p.HasBet || p.Wallet == nil { - continue - } - name := p.Name - chips, credits, err := p.Wallet.RefundOutstanding() - refund := chips + credits - if err != nil || refund <= 0 { - if err != nil { - events = append(events, t.private(name, "Shutdown refund could not be persisted; the stake is recorded for recovery.")) - } - continue - } - events = append(events, t.playerAction(name, - fmt.Sprintf("%s is refunded %d as the casino closes.", name, refund), - fmt.Sprintf("The house refunds your %d stake for the shutdown.", refund))) - } + events := t.refundAllLocked() t.resetRound() return events } - -// setRoom updates the event-routing room after a room-ID rekey. Locked. -func (t *blackjackTable) setRoom(roomID int) { - t.mu.Lock() - defer t.mu.Unlock() - t.tableBase.setRoom(roomID) -} |
