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