diff options
Diffstat (limited to 'internal/casino/table.go')
| -rw-r--r-- | internal/casino/table.go | 211 |
1 files changed, 3 insertions, 208 deletions
diff --git a/internal/casino/table.go b/internal/casino/table.go index be2ea18..80ae451 100644 --- a/internal/casino/table.go +++ b/internal/casino/table.go @@ -1,9 +1,7 @@ package casino import ( - "fmt" "math/rand" - "sync" ) // tableGame is a multiplayer table game implementation. Table routes every @@ -17,7 +15,6 @@ type tableGame interface { action(name, action string) []Event rebet(name string) (spot string, amount int) markDisconnected(name string) []Event - markConnected(name string) hasParticipant(name string) bool } @@ -52,6 +49,8 @@ type Table struct { game tableGame } +// newTable builds the table game for cfg.Game, or nil when the game has no +// multiplayer table implementation. func newTable(roomID int, cfg TableConfig, rng *rand.Rand) *Table { cfg = cfg.Normalize() t := &Table{RoomID: roomID, Config: cfg} @@ -61,7 +60,7 @@ func newTable(roomID int, cfg TableConfig, rng *rand.Rand) *Table { case GameBaccarat: t.game = newBaccaratTable(roomID, cfg, rng) default: - t.game = newGenericTable(roomID, cfg, rng) + return nil } return t } @@ -78,208 +77,4 @@ func (t *Table) action(name, action string) []Event { return t.game.action(name, 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 ( - phaseWaiting tablePhase = "waiting" - phaseBetting tablePhase = "betting" -) - -// 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 - - participants map[string]*Participant - order []string - phase tablePhase - timer countdown - rng *rand.Rand -} - -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, - } -} - -func (t *genericTable) seats() []*Participant { - seats := make([]*Participant, 0, len(t.order)) - for _, name := range t.order { - if p := t.participants[name]; p != nil { - seats = append(seats, p) - } - } - return seats -} - -func (t *genericTable) join(name string) []Event { - t.mu.Lock() - defer t.mu.Unlock() - if p := t.participants[name]; p != nil { - p.Connected = true - return []Event{{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventJoined, Player: name, PrivateTo: name, Message: fmt.Sprintf("You resume your place at the %s table.", t.Config.ID)}} - } - if len(t.participants) >= t.Config.MaxPlayers { - return []Event{{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventResult, Player: name, PrivateTo: name, Message: "That table is full."}} - } - t.participants[name] = &Participant{Name: name, Connected: true} - t.order = append(t.order, name) - 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 *genericTable) leave(name string) []Event { - t.mu.Lock() - defer t.mu.Unlock() - p := t.participants[name] - if p == nil { - return nil - } - if t.phase != phaseWaiting { - return []Event{{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventResult, Player: name, PrivateTo: name, Message: "You're in the middle of a game!"}} - } - delete(t.participants, name) - for i, n := range t.order { - if n == name { - t.order = append(t.order[:i], t.order[i+1:]...) - break - } - } - events := []Event{{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventLeft, Player: name, Message: fmt.Sprintf("%s stops playing at the %s table.", name, t.Config.ID), Public: true}} - if len(t.participants) == 0 { - events = append(events, Event{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventClosed, Message: fmt.Sprintf("The %s table closes.", t.Config.ID), Public: true}) - } - return events -} - -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."}} - } - if p.HasBet { - return []Event{{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventResult, Player: name, PrivateTo: name, Message: "You have already bet this round."}} - } - if amount < t.Config.MinBet || amount > t.Config.MaxBet { - return []Event{{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventResult, Player: name, PrivateTo: name, Message: fmt.Sprintf("Bets must be between %d and %d.", t.Config.MinBet, t.Config.MaxBet)}} - } - wager, ok := wallet.Wager(amount) - if !ok { - return []Event{{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventResult, Player: name, PrivateTo: name, Message: "You don't have enough chips and credits for that bet."}} - } - p.HasBet = true - p.Wager = wager - p.Wallet = wallet - if t.phase == phaseWaiting && len(t.participants) > 1 { - t.phase = phaseBetting - t.timer.start(t.Config.BettingTicks) - } - events := []Event{{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventBetPlaced, Player: name, Message: fmt.Sprintf("%s bets %d credits at the %s table.", name, amount, t.Config.ID), Public: true}} - if t.phase == phaseBetting && t.timer.clock == 0 { - events = append(events, Event{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventBetting, Message: fmt.Sprintf("Betting is open at the %s table for %d ticks.", t.Config.ID, t.Config.BettingTicks), Public: true}) - } - if len(t.participants) == 1 || allBet(t.seats()) { - events = append(events, t.resolveRound()...) - } - return events -} - -func (t *genericTable) tick() []Event { - t.mu.Lock() - defer t.mu.Unlock() - if t.phase != phaseBetting { - return nil - } - t.timer.tick() - if allBet(t.seats()) { - return t.resolveRound() - } - if !t.timer.expired() { - return nil - } - var events []Event - for _, name := range sitOutNames(t.seats()) { - events = append(events, Event{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventTimedOut, Player: name, Message: fmt.Sprintf("%s sits out this round.", name), Public: true}) - } - if anyBet(t.seats()) { - events = append(events, t.resolveRound()...) - } else { - t.resetRound() - } - return events -} - -func (t *genericTable) resolveRound() []Event { - var events []Event - for _, name := range t.order { - p := t.participants[name] - if p == nil || !p.HasBet { - continue - } - result := slotResult(t.rng, p.Wager.Total) - message := fmt.Sprintf("%s spins: %s", name, result.Display) - events = append(events, Event{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventRoundStarted, Player: name, Message: message, Public: true}) - if result.Payout > 0 { - if p.Wallet != nil { - p.Wallet.PayCredits(result.Payout) - } - events = append(events, Event{RoomID: t.RoomID, TableID: t.Config.ID, Type: EventPayout, Player: name, PrivateTo: name, Message: fmt.Sprintf("You win %d credits!", result.Payout)}) - } - } - t.resetRound() - return events -} - -func (t *genericTable) resetRound() { - t.phase = phaseWaiting - t.timer.stop() - clearBets(t.seats()) -} - -func (t *genericTable) markDisconnected(name string) []Event { - t.mu.Lock() - defer t.mu.Unlock() - if p := t.participants[name]; p != nil { - p.Connected = false - } - return nil -} - -func (t *genericTable) markConnected(name string) { - t.mu.Lock() - defer t.mu.Unlock() - if p := t.participants[name]; p != nil { - p.Connected = true - } -} - -func (t *genericTable) hasParticipant(name string) bool { - t.mu.Lock() - defer t.mu.Unlock() - return t.participants[name] != nil -} - -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 *genericTable) rebet(name string) (string, int) { - return "", t.Config.MinBet -} |
