aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/table.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/casino/table.go')
-rw-r--r--internal/casino/table.go211
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
-}