diff options
| author | historia <[not public]> | 2026-09-24 20:06:41 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-24 20:07:31 -0400 |
| commit | 13db3e50cb6e29eedbf25416e5284bf08b0adefb (patch) | |
| tree | d5e9b99030b25cc7fc102288b694c133f8832f84 /internal/casino/table_ux.go | |
| parent | d114c747a7672c7e4daab3a5b5f1b2f066ebc912 (diff) | |
| download | thehouseoficarus-13db3e50cb6e29eedbf25416e5284bf08b0adefb.tar.gz | |
feat: casino table ux: aliases, rebet hints, deadline notifications, spectator mode
Diffstat (limited to 'internal/casino/table_ux.go')
| -rw-r--r-- | internal/casino/table_ux.go | 180 |
1 files changed, 180 insertions, 0 deletions
diff --git a/internal/casino/table_ux.go b/internal/casino/table_ux.go new file mode 100644 index 0000000..9543d91 --- /dev/null +++ b/internal/casino/table_ux.go @@ -0,0 +1,180 @@ +package casino + +import ( + "fmt" + "strings" +) + +func (c TableConfig) Label() string { + if c.Game != GameBaccarat { + return gameTitle(c.Game) + } + switch c.Baccarat.Normalize().Variant { + case BaccaratSuper6: + return "Baccarat / Super 6" + case BaccaratEZ: + return "Baccarat / EZ" + default: + return "Baccarat / Commission" + } +} + +func (c *tableCore[P]) overview() string { + c.mu.Lock() + defer c.mu.Unlock() + return c.overviewLocked() +} + +func (c *tableCore[P]) overviewLocked() string { + state := "Betting open" + switch c.phase { + case phaseBetting: + state = fmt.Sprintf("Betting closes in %d ticks", c.window.timer.remaining()) + case phasePlaying: + state = "Round in progress" + case phaseInsurance: + state = "Insurance decisions" + } + return fmt.Sprintf("%s | Limits %s | %d/%d seated | %s", c.Config.Label(), betRangeText(c.Config.MinBet, c.Config.MaxBet), c.players.count(), c.Config.MaxPlayers, state) +} + +func (c *tableCore[P]) warning(name, text string) Event { + e := c.private(name, text) + e.Color = "casino_warning" + return e +} + +// controlLocked queues departures without changing committed hands or stakes. +func (c *tableCore[P]) controlLocked(name, action string) ([]Event, bool) { + if action != "leave_after" && action != "sitout" { + return nil, false + } + for _, p := range c.players.participants() { + if p.Name != name { + continue + } + if action == "leave_after" { + p.LeaveAfter = true + return []Event{c.private(name, "You’ll leave after this round. Your current hand still needs your actions.")}, true + } + if p.HasBet { + p.SitOutAfter = true + return []Event{c.private(name, "You’ll sit out after this round. Your current wager and actions are unchanged.")}, true + } + p.SittingOut = true + return []Event{c.private(name, "You’re sitting out. Place a bet to rejoin; stop to leave.")}, true + } + return []Event{c.private(name, "You aren’t seated at this table.")}, true +} + +// Called only after resetRound, when seat removal cannot disturb active indices. +func (c *tableCore[P]) finishControlsLocked() []Event { + var events []Event + for _, p := range c.players.participants() { + if p.LeaveAfter { + c.removeSeatLocked(p.Name) + events = append(events, c.playerAction(p.Name, p.Name+" stands up.", "Round complete. You stand up.")) + } else if p.SitOutAfter { + p.SitOutAfter = false + p.SittingOut = true + events = append(events, c.private(p.Name, "You’re now sitting out. Place a bet to rejoin.")) + } + } + return c.closeIfEmptyLocked(events) +} + +func (c *tableCore[P]) seatStatusLocked(name string) string { + for _, p := range c.players.participants() { + if p.Name != name { + continue + } + if p.LeaveAfter { + return "Leaving after this round" + } + if p.SitOutAfter { + return "Sitting out after this round" + } + if p.SittingOut { + return "Sitting out | Bet to rejoin" + } + if p.HasBet { + return fmt.Sprintf("Wager committed: %d", p.Wager.Total) + } + } + var waiting []string + for _, p := range c.players.participants() { + if p.Connected && !p.SittingOut && !p.HasBet { + waiting = append(waiting, p.Name) + } + } + if len(waiting) > 0 { + return "Waiting for bets: " + strings.Join(waiting, ", ") + } + return "" +} + +func (t *blackjackTable) status(name string) []Event { + t.mu.Lock() + defer t.mu.Unlock() + e := t.private(name, t.overviewLocked()) + if text := t.seatStatusLocked(name); text != "" { + e.Message += "\n" + text + } + events := []Event{e} + switch t.phase { + case blackjackPlaying: + events[0].Snapshot = t.snapshot(false) + if t.activeName() == name { + events = append(events, t.turnMenuLocked(name)) + } else { + events[0].Message += "\n" + t.activeName() + "’s turn | You are waiting" + } + case blackjackInsurance: + events[0].Snapshot = t.snapshot(false) + if p := t.players.get(name); p != nil && p.HasBet && !p.declinedInsurance && p.insurance == 0 { + events = append(events, t.insuranceMenuLocked(p)) + } + default: + if p := t.players.get(name); p != nil && !p.HasBet { + events = append(events, t.menuEvent(name)) + } + } + return events +} + +func (t *baccaratTable) status(name string) []Event { + t.mu.Lock() + defer t.mu.Unlock() + e := t.private(name, t.overviewLocked()) + if text := t.seatStatusLocked(name); text != "" { + e.Message += "\n" + text + } + events := []Event{e} + if p := t.players.get(name); p != nil && !p.HasBet { + events = append(events, t.menuEvent(name)) + } + return events +} + +func (t *blackjackTable) insuranceMenuLocked(p *blackjackPlayer) Event { + cost := p.hands[0].bet / 2 + e := t.private(p.Name, "Insurance decision") + e.Color = "casino_menu" + e.Menu = &MenuView{Heading: fmt.Sprintf("Insurance | Costs %d | Pays %g:1 | %d ticks remaining", cost, t.Rules.InsurancePayout, t.timer.remaining()), Commands: []MenuCommand{{Command: "insurance", Desc: "Buy insurance", Cost: cost}, {Command: "decline", Desc: "Continue without insurance (also the timeout default)"}}} + if cost == 0 { + e.Menu.Commands = e.Menu.Commands[1:] + e.Menu.Heading = "Bet too small to insure | decline to continue" + } + return e +} + +func resultText(result string, stake, returned int) string { + label := strings.ToUpper(result) + switch result { + case "blackjack": + label = "BLACKJACK" + case "bust", "loss", "dealer blackjack": + label = "LOSS" + } + return fmt.Sprintf("%s | Net %+d credits | %d returned (stake included)", label, returned-stake, returned) +} |
