aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/baccarat.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-18 18:38:35 -0400
committerhistoria <[not public]>2026-09-18 18:38:35 -0400
commit1f118d79e319da4e765502e9ca5b661772de94f1 (patch)
treebca8a3fd1e538f1c302480511e6ba872c92e10b8 /internal/casino/baccarat.go
parent5ad42a51df4dcc5ccc3aa6a6d902085ce2ee4239 (diff)
downloadthehouseoficarus-1f118d79e319da4e765502e9ca5b661772de94f1.tar.gz
refactor: casino table core; fix: stake/migration/validation gaps
Diffstat (limited to 'internal/casino/baccarat.go')
-rw-r--r--internal/casino/baccarat.go106
1 files changed, 20 insertions, 86 deletions
diff --git a/internal/casino/baccarat.go b/internal/casino/baccarat.go
index 164d0ff..b762b6f 100644
--- a/internal/casino/baccarat.go
+++ b/internal/casino/baccarat.go
@@ -3,14 +3,13 @@ package casino
import (
"fmt"
"math/rand"
- "sync"
)
-type baccaratPhase string
-
+// Baccarat uses the shared table phases; these aliases keep the call sites
+// readable.
const (
- baccaratWaiting baccaratPhase = "waiting"
- baccaratBetting baccaratPhase = "betting"
+ baccaratWaiting = phaseWaiting
+ baccaratBetting = phaseBetting
)
// baccaratSpots are the outcomes a participant may wager on.
@@ -36,38 +35,27 @@ type baccaratPlayer struct {
func (p *baccaratPlayer) participantPtr() *Participant { return &p.Participant }
type baccaratTable struct {
- mu sync.Mutex
- tableBase
- Rules BaccaratConfig
- players seatSet[*baccaratPlayer]
- phase baccaratPhase
- window bettingWindow
- shoe *shoe
+ tableCore[*baccaratPlayer]
+ Rules BaccaratConfig
+ shoe *shoe
}
func newBaccaratTable(roomID int, cfg TableConfig, rng *rand.Rand) *baccaratTable {
cfg = cfg.Normalize()
rules := cfg.Baccarat.Normalize()
return &baccaratTable{
- tableBase: tableBase{RoomID: roomID, Config: cfg}, Rules: rules,
- players: newSeatSet[*baccaratPlayer](), phase: baccaratWaiting,
- shoe: newShoe(rules.Decks, rules.ShuffleAt, rng),
+ tableCore: newTableCore[*baccaratPlayer](&tableBase{RoomID: roomID, Config: cfg}),
+ Rules: rules,
+ shoe: newShoe(rules.Decks, rules.ShuffleAt, rng),
}
}
func (t *baccaratTable) menuEvent(name string) Event {
- e := t.private(name, t.menu())
- e.Color = "casino_menu"
- e.Menu = &MenuView{Kind: MenuBaccaratBet, Commands: []MenuCommand{
+ return t.betMenuEvent(name, MenuBaccaratBet, []MenuCommand{
{Command: "bet <amount>", Desc: "Place a wager (min/max accepted)"},
{Command: "bet <amount> on player|banker|tie", Desc: "Wager on a specific outcome"},
{Command: "stop", Desc: "Leave the table"},
- }}
- return e
-}
-
-func (t *baccaratTable) menu() string {
- return "New baccarat hand! Place your bet."
+ })
}
func (t *baccaratTable) join(name string) []Event {
@@ -79,8 +67,7 @@ func (t *baccaratTable) join(name string) []Event {
if t.players.count() >= t.Config.MaxPlayers {
return []Event{t.private(name, "That baccarat table is full.")}
}
- t.players.add(name, &baccaratPlayer{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, &baccaratPlayer{Participant: Participant{Name: name, Connected: true}})}
events = append(events, t.menuEvent(name))
return events
}
@@ -97,12 +84,9 @@ func (t *baccaratTable) leave(name string) []Event {
if p.HasBet {
return []Event{t.private(name, "You're in the middle of a baccarat round!")}
}
- t.players.remove(name)
+ t.removeSeatLocked(name)
events := []Event{t.playerAction(name, fmt.Sprintf("%s stands up.", name), "You stand up.")}
- if t.players.count() == 0 {
- events = append(events, t.event("The baccarat table closes.", true))
- }
- return events
+ return t.closeIfEmptyLocked(events)
}
func (t *baccaratTable) bet(name, spot string, amount int, wallet Wallet) []Event {
@@ -135,11 +119,7 @@ func (t *baccaratTable) bet(name, spot string, amount int, wallet Wallet) []Even
events := []Event{t.playerAction(name,
fmt.Sprintf("%s bets %d credits on %s.", name, amount, spot),
fmt.Sprintf("You bet %d credits on %s.", amount, spot))}
- if t.players.count() > 1 && t.phase == baccaratWaiting {
- t.phase = baccaratBetting
- t.window.open(t.Config.BettingTicks)
- events = append(events, t.event(fmt.Sprintf("Baccarat betting is open for %d ticks.", t.Config.BettingTicks), true))
- }
+ events = t.openBettingLocked(events)
if allBet(t.players.participants()) {
events = append(events, t.deal()...)
}
@@ -152,20 +132,7 @@ func (t *baccaratTable) tick() []Event {
if t.phase != baccaratBetting {
return nil
}
- 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 baccarat round.", name), true))
- }
- if resolve {
- events = append(events, t.deal()...)
- } else {
- t.resetRound()
- }
- return events
+ return t.advanceBettingLocked(t.deal, t.resetRound)
}
func (t *baccaratTable) action(name, action string) []Event {
@@ -291,18 +258,9 @@ func (t *baccaratTable) markDisconnected(name string) []Event {
if p := t.players.get(name); p != nil && p.HasBet && p.Wallet != nil {
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 baccarat table closes.", true))
- }
- return events
-}
-
-func (t *baccaratTable) hasParticipant(name string) bool {
- t.mu.Lock()
- defer t.mu.Unlock()
- return t.players.has(name)
+ return t.closeIfEmptyLocked(events)
}
// baccaratTotal scores a hand modulo 10.
@@ -431,31 +389,7 @@ func baccaratResultMessages(name, result string, payout int) (string, string) {
func (t *baccaratTable) 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 *baccaratTable) setRoom(roomID int) {
- t.mu.Lock()
- defer t.mu.Unlock()
- t.tableBase.setRoom(roomID)
-}