diff options
| author | historia <[not public]> | 2026-08-03 16:18:17 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-08-03 16:19:25 -0400 |
| commit | f2c4a58727169a03b8102704888d1579d26b28c8 (patch) | |
| tree | a334f9cd37b3c7521c11d266d797b208dac4f3be /internal/casino/table.go | |
| parent | 612e429de438821ae8e099d6b54f87c11ff6c1b5 (diff) | |
| download | thehouseoficarus-f2c4a58727169a03b8102704888d1579d26b28c8.tar.gz | |
feat: multiplayer baccarat game
Diffstat (limited to 'internal/casino/table.go')
| -rw-r--r-- | internal/casino/table.go | 147 |
1 files changed, 97 insertions, 50 deletions
diff --git a/internal/casino/table.go b/internal/casino/table.go index e993cb8..be2ea18 100644 --- a/internal/casino/table.go +++ b/internal/casino/table.go @@ -6,6 +6,81 @@ import ( "sync" ) +// tableGame is a multiplayer table game implementation. Table routes every +// player interaction through this interface so new games plug in without +// changing the dispatch layer. +type tableGame interface { + join(name string) []Event + leave(name string) []Event + bet(name, spot string, amount int, wallet Wallet) []Event + tick() []Event + action(name, action string) []Event + rebet(name string) (spot string, amount int) + markDisconnected(name string) []Event + markConnected(name string) + hasParticipant(name string) bool +} + +// tableBase carries the identity every table game needs to build events. +type tableBase struct { + RoomID int + Config TableConfig +} + +func (b tableBase) event(message string, public bool) Event { + return Event{RoomID: b.RoomID, TableID: b.Config.ID, Type: EventResult, Message: message, Public: public, Color: "casino_action"} +} + +func (b tableBase) private(name, message string) Event { + e := b.event(message, false) + e.PrivateTo = name + return e +} + +func (b tableBase) playerAction(name, publicMessage, privateMessage string) Event { + e := b.event(publicMessage, true) + e.PrivateTo = name + e.PrivateMessage = privateMessage + return e +} + +// Table is the manager-facing handle for a multiplayer table. It only holds +// routing state; all game logic lives in the tableGame implementation. +type Table struct { + RoomID int + Config TableConfig + game tableGame +} + +func newTable(roomID int, cfg TableConfig, rng *rand.Rand) *Table { + cfg = cfg.Normalize() + t := &Table{RoomID: roomID, Config: cfg} + switch cfg.Game { + case GameBlackjack: + t.game = newBlackjackTable(roomID, cfg, rng) + case GameBaccarat: + t.game = newBaccaratTable(roomID, cfg, rng) + default: + t.game = newGenericTable(roomID, cfg, rng) + } + return t +} + +func (t *Table) join(name string) []Event { return t.game.join(name) } +func (t *Table) leave(name string) []Event { return t.game.leave(name) } + +func (t *Table) bet(name, spot string, amount int, wallet Wallet) []Event { + return t.game.bet(name, spot, amount, wallet) +} + +func (t *Table) tick() []Event { return t.game.tick() } +func (t *Table) action(name, action string) []Event { return t.game.action(name, action) } +func (t *Table) rebet(name string) (string, int) { return t.game.rebet(name) } + +func (t *Table) markDisconnected(name string) []Event { return t.game.markDisconnected(name) } +func (t *Table) markConnected(name string) { t.game.markConnected(name) } +func (t *Table) hasParticipant(name string) bool { return t.game.hasParticipant(name) } + type tablePhase string const ( @@ -13,7 +88,9 @@ const ( phaseBetting tablePhase = "betting" ) -type Table struct { +// genericTable is the fallback table game: every bettor's wager resolves +// immediately against a simple spin once all connected players have bet. +type genericTable struct { mu sync.Mutex RoomID int Config TableConfig @@ -23,24 +100,19 @@ type Table struct { phase tablePhase timer countdown rng *rand.Rand - blackjack *blackjackTable } -func newTable(roomID int, cfg TableConfig, rng *rand.Rand) *Table { - t := &Table{ +func newGenericTable(roomID int, cfg TableConfig, rng *rand.Rand) *genericTable { + return &genericTable{ RoomID: roomID, Config: cfg, phase: phaseWaiting, participants: make(map[string]*Participant), rng: rng, } - if cfg.Game == GameBlackjack { - t.blackjack = newBlackjackTable(roomID, cfg, rng) - } - return t } -func (t *Table) seats() []*Participant { +func (t *genericTable) seats() []*Participant { seats := make([]*Participant, 0, len(t.order)) for _, name := range t.order { if p := t.participants[name]; p != nil { @@ -50,10 +122,7 @@ func (t *Table) seats() []*Participant { return seats } -func (t *Table) join(name string) []Event { - if t.blackjack != nil { - return t.blackjack.join(name) - } +func (t *genericTable) join(name string) []Event { t.mu.Lock() defer t.mu.Unlock() if p := t.participants[name]; p != nil { @@ -68,10 +137,7 @@ func (t *Table) join(name string) []Event { return []Event{{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventJoined, Player: name, Message: fmt.Sprintf("%s joins the %s table.", name, t.Config.ID), Public: true}} } -func (t *Table) leave(name string) []Event { - if t.blackjack != nil { - return t.blackjack.leave(name) - } +func (t *genericTable) leave(name string) []Event { t.mu.Lock() defer t.mu.Unlock() p := t.participants[name] @@ -95,16 +161,16 @@ func (t *Table) leave(name string) []Event { return events } -func (t *Table) bet(name string, amount int, wallet Wallet) []Event { - if t.blackjack != nil { - return t.blackjack.bet(name, amount, wallet) - } +func (t *genericTable) bet(name, spot string, amount int, wallet Wallet) []Event { t.mu.Lock() defer t.mu.Unlock() p := t.participants[name] if p == nil { return []Event{{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventResult, Player: name, PrivateTo: name, Message: "You aren't playing at that table."}} } + if spot != "" { + return []Event{{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventResult, Player: name, PrivateTo: name, Message: "That table doesn't take a bet target."}} + } if t.phase != phaseWaiting && t.phase != phaseBetting { return []Event{{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventResult, Player: name, PrivateTo: name, Message: "That table isn't accepting bets."}} } @@ -135,10 +201,7 @@ func (t *Table) bet(name string, amount int, wallet Wallet) []Event { return events } -func (t *Table) tick() []Event { - if t.blackjack != nil { - return t.blackjack.tick() - } +func (t *genericTable) tick() []Event { t.mu.Lock() defer t.mu.Unlock() if t.phase != phaseBetting { @@ -163,7 +226,7 @@ func (t *Table) tick() []Event { return events } -func (t *Table) resolveRound() []Event { +func (t *genericTable) resolveRound() []Event { var events []Event for _, name := range t.order { p := t.participants[name] @@ -184,16 +247,13 @@ func (t *Table) resolveRound() []Event { return events } -func (t *Table) resetRound() { +func (t *genericTable) resetRound() { t.phase = phaseWaiting t.timer.stop() clearBets(t.seats()) } -func (t *Table) markDisconnected(name string) []Event { - if t.blackjack != nil { - return t.blackjack.markDisconnected(name) - } +func (t *genericTable) markDisconnected(name string) []Event { t.mu.Lock() defer t.mu.Unlock() if p := t.participants[name]; p != nil { @@ -202,11 +262,7 @@ func (t *Table) markDisconnected(name string) []Event { return nil } -func (t *Table) markConnected(name string) { - if t.blackjack != nil { - t.blackjack.markConnected(name) - return - } +func (t *genericTable) markConnected(name string) { t.mu.Lock() defer t.mu.Unlock() if p := t.participants[name]; p != nil { @@ -214,25 +270,16 @@ func (t *Table) markConnected(name string) { } } -func (t *Table) hasParticipant(name string) bool { - if t.blackjack != nil { - return t.blackjack.hasParticipant(name) - } +func (t *genericTable) hasParticipant(name string) bool { t.mu.Lock() defer t.mu.Unlock() return t.participants[name] != nil } -func (t *Table) action(name, action string) []Event { - if t.blackjack == nil { - return []Event{{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventResult, PrivateTo: name, Message: "That game has no player actions."}} - } - return t.blackjack.action(name, action) +func (t *genericTable) action(name, action string) []Event { + return []Event{{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventResult, PrivateTo: name, Message: "That game has no player actions."}} } -func (t *Table) rebetAmount(name string) int { - if t.blackjack != nil { - return t.blackjack.rebetAmount(name) - } - return t.Config.MinBet +func (t *genericTable) rebet(name string) (string, int) { + return "", t.Config.MinBet } |
