diff options
Diffstat (limited to 'internal/casino/baccarat.go')
| -rw-r--r-- | internal/casino/baccarat.go | 123 |
1 files changed, 51 insertions, 72 deletions
diff --git a/internal/casino/baccarat.go b/internal/casino/baccarat.go index 4261270..6a72caa 100644 --- a/internal/casino/baccarat.go +++ b/internal/casino/baccarat.go @@ -31,15 +31,16 @@ type baccaratPlayer struct { lastSpot string } +func (p *baccaratPlayer) participantPtr() *Participant { return &p.Participant } + type baccaratTable struct { mu sync.Mutex tableBase - Rules BaccaratConfig - participants map[string]*baccaratPlayer - order []string - phase baccaratPhase - timer countdown - shoe *shoe + Rules BaccaratConfig + players seatSet[*baccaratPlayer] + phase baccaratPhase + window bettingWindow + shoe *shoe } func newBaccaratTable(roomID int, cfg TableConfig, rng *rand.Rand) *baccaratTable { @@ -47,21 +48,11 @@ func newBaccaratTable(roomID int, cfg TableConfig, rng *rand.Rand) *baccaratTabl rules := cfg.Baccarat.Normalize() return &baccaratTable{ tableBase: tableBase{RoomID: roomID, Config: cfg}, Rules: rules, - participants: make(map[string]*baccaratPlayer), phase: baccaratWaiting, + players: newSeatSet[*baccaratPlayer](), phase: baccaratWaiting, shoe: newShoe(rules.Decks, rules.ShuffleAt, rng), } } -func (t *baccaratTable) 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.Participant) - } - } - return seats -} - func (t *baccaratTable) menuEvent(name string) Event { e := t.private(name, t.menu()) e.Color = "casino_menu" @@ -80,15 +71,13 @@ func (t *baccaratTable) menu() string { func (t *baccaratTable) join(name string) []Event { t.mu.Lock() defer t.mu.Unlock() - if p := t.participants[name]; p != nil { - p.Connected = true + if t.players.has(name) { return []Event{t.menuEvent(name)} } - if len(t.participants) >= t.Config.MaxPlayers { + if t.players.count() >= t.Config.MaxPlayers { return []Event{t.private(name, "That baccarat table is full.")} } - t.participants[name] = &baccaratPlayer{Participant: Participant{Name: name, Connected: true}} - t.order = append(t.order, name) + 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 = append(events, t.menuEvent(name)) return events @@ -97,22 +86,15 @@ func (t *baccaratTable) join(name string) []Event { func (t *baccaratTable) leave(name string) []Event { t.mu.Lock() defer t.mu.Unlock() - p := t.participants[name] - if p == nil { + if !t.players.has(name) { return nil } if t.phase != baccaratWaiting { return []Event{t.private(name, "You're in the middle of a baccarat round!")} } - delete(t.participants, name) - for i, n := range t.order { - if n == name { - t.order = append(t.order[:i], t.order[i+1:]...) - break - } - } + t.players.remove(name) events := []Event{t.playerAction(name, fmt.Sprintf("%s stands up.", name), "You stand up.")} - if len(t.participants) == 0 { + if t.players.count() == 0 { events = append(events, t.event("The baccarat table closes.", true)) } return events @@ -121,7 +103,7 @@ func (t *baccaratTable) leave(name string) []Event { func (t *baccaratTable) bet(name, spot string, amount int, wallet Wallet) []Event { t.mu.Lock() defer t.mu.Unlock() - p := t.participants[name] + p := t.players.get(name) if p == nil { return []Event{t.private(name, "You aren't playing baccarat.")} } @@ -148,12 +130,12 @@ 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 len(t.participants) > 1 && t.phase == baccaratWaiting { + if t.players.count() > 1 && t.phase == baccaratWaiting { t.phase = baccaratBetting - t.timer.start(t.Config.BettingTicks) + t.window.open(t.Config.BettingTicks) events = append(events, t.event(fmt.Sprintf("Baccarat betting is open for %d ticks.", t.Config.BettingTicks), true)) } - if allBet(t.seats()) { + if allBet(t.players.participants()) { events = append(events, t.deal()...) } return events @@ -165,18 +147,15 @@ func (t *baccaratTable) tick() []Event { if t.phase != baccaratBetting { return nil } - t.timer.tick() - if allBet(t.seats()) { - return t.deal() - } - if !t.timer.expired() { + sitOuts, resolve, pending := t.window.advance(t.players.participants()) + if pending { return nil } var events []Event - for _, name := range sitOutNames(t.seats()) { + for _, name := range sitOuts { events = append(events, t.event(fmt.Sprintf("%s sits out this baccarat round.", name), true)) } - if anyBet(t.seats()) { + if resolve { events = append(events, t.deal()...) } else { t.resetRound() @@ -189,7 +168,7 @@ func (t *baccaratTable) action(name, action string) []Event { } func (t *baccaratTable) rebet(name string) (string, int) { - if p := t.participants[name]; p != nil && p.lastBet > 0 { + if p := t.players.get(name); p != nil && p.lastBet > 0 { return p.lastSpot, p.lastBet } return "", t.Config.MinBet @@ -203,17 +182,16 @@ func (t *baccaratTable) deal() []Event { round := t.event("New baccarat hand!", true) round.Baccarat = t.snapshot(player, banker) events := []Event{round, t.event(baccaratOutcomeMessage(playerTotal, bankerTotal), true)} - for _, name := range t.order { - p := t.participants[name] - if p == nil || !p.HasBet { + for _, p := range t.players.ordered() { + if !p.HasBet { continue } result, payout := settleBaccaratBet(p.lastSpot, p.Wager.Total, playerTotal, bankerTotal, t.Rules) if payout > 0 && p.Wallet != nil { p.Wallet.PayCredits(payout) } - publicResult, privateResult := baccaratResultMessages(name, result, payout) - settlement := t.playerAction(name, publicResult, privateResult) + publicResult, privateResult := baccaratResultMessages(p.Name, result, payout) + settlement := t.playerAction(p.Name, publicResult, privateResult) if result == "win" { if p.lastSpot == baccaratSpotTie { settlement.Color = "casino_jackpot_win" @@ -224,9 +202,9 @@ func (t *baccaratTable) deal() []Event { events = append(events, settlement) } t.resetRound() - for _, name := range t.order { - if p := t.participants[name]; p != nil && p.Connected { - events = append(events, t.menuEvent(name)) + for _, p := range t.players.ordered() { + if p.Connected { + events = append(events, t.menuEvent(p.Name)) } } return events @@ -243,43 +221,42 @@ func (t *baccaratTable) snapshot(player, banker []card) *BaccaratSnapshot { for _, c := range banker { snapshot.Banker = append(snapshot.Banker, CardView{Rank: c.rank, Suit: c.suit}) } - for _, name := range t.order { - p := t.participants[name] - if p == nil || !p.HasBet { + for _, p := range t.players.ordered() { + if !p.HasBet { continue } - snapshot.Bets = append(snapshot.Bets, BaccaratBetView{Name: name, Spot: p.lastSpot, Amount: p.Wager.Total}) + snapshot.Bets = append(snapshot.Bets, BaccaratBetView{Name: p.Name, Spot: p.lastSpot, Amount: p.Wager.Total}) } return snapshot } func (t *baccaratTable) resetRound() { t.phase = baccaratWaiting - t.timer.stop() - clearBets(t.seats()) + t.window.close() + clearBets(t.players.participants()) } +// markDisconnected removes the player from the table, forfeiting any wager +// they committed (deal only settles seated bettors). An open betting window +// resolves or resets on the next tick now that they no longer hold it open. func (t *baccaratTable) markDisconnected(name string) []Event { t.mu.Lock() defer t.mu.Unlock() - if p := t.participants[name]; p != nil { - p.Connected = false + if !t.players.has(name) { + return nil } - return nil -} - -func (t *baccaratTable) markConnected(name string) { - t.mu.Lock() - defer t.mu.Unlock() - if p := t.participants[name]; p != nil { - p.Connected = true + t.players.remove(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.participants[name] != nil + return t.players.has(name) } // baccaratTotal scores a hand modulo 10. @@ -338,7 +315,9 @@ func bankerDraws(bankerTotal, playerThird int) bool { } // settleBaccaratBet returns the result label and total payout (stake plus -// winnings) for one wager. Player and banker bets push on a tie. +// winnings) for one wager. Player and banker bets push on a tie. Winnings +// round down to whole credits, so the banker commission effectively rounds +// up: never bet less than one commission unit. func settleBaccaratBet(spot string, bet int, playerTotal, bankerTotal int, rules BaccaratConfig) (string, int) { switch { case playerTotal > bankerTotal: @@ -347,11 +326,11 @@ func settleBaccaratBet(spot string, bet int, playerTotal, bankerTotal int, rules } case bankerTotal > playerTotal: if spot == baccaratSpotBanker { - return "win", bet + int(math.Round(float64(bet)*rules.BankerPayout)) + return "win", bet + int(math.Floor(float64(bet)*rules.BankerPayout)) } default: if spot == baccaratSpotTie { - return "win", bet + int(math.Round(float64(bet)*rules.TiePayout)) + return "win", bet + int(math.Floor(float64(bet)*rules.TiePayout)) } return "push", bet } |
