aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-01 19:30:38 -0400
committerhistoria <[not public]>2026-10-01 19:30:38 -0400
commit6d9908b29c8187196d23b126b1165c7e0bd0fc76 (patch)
treeddbc02357ed92646b801687b90b1a06c71a15d71 /internal/game
parent8a94f21bd47c0a00bc415395232dba22fa958837 (diff)
downloadthehouseoficarus-6d9908b29c8187196d23b126b1165c7e0bd0fc76.tar.gz
fix: casino pai gow sorting, universal unbet, table/rules split, remove solo timers
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/casino_commands_test.go6
-rw-r--r--internal/game/casino_ux.go77
-rw-r--r--internal/game/casino_ux_test.go8
-rw-r--r--internal/game/cmd_registry.go4
-rw-r--r--internal/game/craps_test.go8
-rw-r--r--internal/game/look_casino.go2
-rw-r--r--internal/game/pai_gow_test.go13
-rw-r--r--internal/game/render_casino_help.go4
-rw-r--r--internal/game/render_craps.go2
-rw-r--r--internal/game/render_help.go2
-rw-r--r--internal/game/render_pai_gow.go11
-rw-r--r--internal/game/render_roulette.go46
-rw-r--r--internal/game/render_roulette_test.go2
-rw-r--r--internal/game/render_roulette_tracker_test.go18
-rw-r--r--internal/game/roulette_test.go10
15 files changed, 98 insertions, 115 deletions
diff --git a/internal/game/casino_commands_test.go b/internal/game/casino_commands_test.go
index 5e05d4e..cd558ea 100644
--- a/internal/game/casino_commands_test.go
+++ b/internal/game/casino_commands_test.go
@@ -12,9 +12,9 @@ import (
func TestCasinoCommandsWrapBeforeSessionWithoutLosingIndent(t *testing.T) {
commands := []casino.MenuCommand{
- {Command: "bet <amount> on <target>", Desc: "Place bets until the dice roll; table rules lists targets"},
+ {Command: "bet <amount> on <target>", Desc: "Place bets until the dice roll; list gives targets"},
{Command: "roll / autoroll / stop", Desc: "Shoot / repeat on your turn / return to manual play"},
- {Command: "unbet <target> / unbet all", Desc: "Remove one bet / every optional bet"},
+ {Command: "unbet", Desc: "Remove all your bets"},
{Command: "table", Desc: "View the felt"},
{Command: "insurance", Desc: "Buy insurance; a deliberately long description which requires a hanging continuation line"},
{Command: "decline", Desc: "Continue without insurance (also the timeout default)"},
@@ -58,7 +58,7 @@ func TestCasinoCommandsHangingIndentAndShortDescription(t *testing.T) {
sess := &net.Session{Player: player.New("a")}
g := &Game{}
commands := []casino.MenuCommand{
- {Command: "bet <amount> on <target>", Desc: "Place a wager; targets: table rules"},
+ {Command: "bet <amount> on <target>", Desc: "Place a wager; targets: list"},
{Command: "table", Desc: "View the felt"},
}
text := plainRoulette(g.casinoMenuCommands(sess, commands))
diff --git a/internal/game/casino_ux.go b/internal/game/casino_ux.go
index 1d66f43..6d666f7 100644
--- a/internal/game/casino_ux.go
+++ b/internal/game/casino_ux.go
@@ -109,14 +109,26 @@ func (g *Game) showCasinoChoices(sess *net.Session, choices []casinoChoice) {
sess.WriteLine(g.renderCasinoCommands(sess, "", "Choose a game (use an unambiguous name or abbreviation):", commands))
}
+// renderCasinoFelt draws a table's felt (layout, payouts, board) without the
+// prose rule text. The table command shows this; rules opens the help page.
+// Craps and roulette already render their felt through the live status
+// snapshot, so they return nothing here.
+func (g *Game) renderCasinoFelt(sess *net.Session, cfg casino.TableConfig) string {
+ switch cfg.Game {
+ case casino.GameBlackjack:
+ return g.renderCasinoSnapshot(sess, &casino.BlackjackRulesView{Config: cfg})
+ case casino.GameBaccarat:
+ return g.renderCasinoSnapshot(sess, &casino.BaccaratRulesView{Config: cfg})
+ case casino.GamePaiGow:
+ return renderPaiGowFelt(cfg, g.colorMode(sess), sess.Player.OptionBool("unicode"))
+ }
+ return ""
+}
+
func (g *Game) executeTableStatus(sess *net.Session, args []string, _ string) {
if sess.Player == nil {
return
}
- if len(args) > 0 && args[0] != "rules" && args[0] != "help" {
- sess.WriteLine("Use table, table rules, or table help.")
- return
- }
t, playing := g.Casino.Participation(sess.Player.Name)
if !playing || t == nil {
if len(args) > 0 && args[0] == "help" {
@@ -132,41 +144,36 @@ func (g *Game) executeTableStatus(sess *net.Session, args []string, _ string) {
}
return
}
- if len(args) > 0 && args[0] == "rules" {
- // The felt and the exact, per-table wording stay under table rules;
- // bare rules opens the game's own help page.
- if t.Config.Game == casino.GameVideoPoker {
- g.executeTableAction(sess, "paytable")
- } else {
- sess.WriteLine(g.renderCasinoRules(sess, t.Config))
- }
- return
- }
+ // table help remains a shortcut to the seated game's help page; the old
+ // "table rules" subcommand is gone, so bare table always shows the felt.
if len(args) > 0 && args[0] == "help" {
g.doHelp(sess, t.HelpTopic())
- } else {
- g.emitCasinoEvents(t.Status(sess.Player.Name))
- if t.Config.Game == casino.GameRoulette {
- sess.WriteLine(g.rouletteReminder(sess, t.Config))
- } else {
- sess.WriteLine(g.colorize(sess, "command", "Learn to play: rules. Exact payouts: table rules."))
+ if t.Config.Game != casino.GameVideoPoker {
+ commands := []casino.MenuCommand{
+ {Command: "rules", Desc: "Help page for this game variant"},
+ {Command: "list", Desc: "Every bet available right now, with payouts"},
+ {Command: "option casino_display compact", Desc: "Compact action hints (detailed for full command menus)"},
+ {Command: "help casino", Desc: "Quick guides to all casino games"},
+ }
+ sess.WriteLine(g.renderCasinoCommands(sess, "", "Table controls", commands))
}
+ return
}
- if len(args) > 0 {
- // Reuse the game's current-state menu instead of advertising actions
- // (insurance, split, rebet, etc.) that may not be legal right now.
- g.emitCasinoEvents(t.Status(sess.Player.Name))
- if t.Config.Game == casino.GameVideoPoker {
- return
- }
- commands := []casino.MenuCommand{
- {Command: "rules", Desc: "Help page for this game variant"},
- {Command: "list", Desc: "Every bet available right now, with payouts"},
- {Command: "table rules", Desc: "This table’s exact rules and payouts"},
- {Command: "option casino_display compact", Desc: "Compact action hints (detailed for full command menus)"},
- {Command: "help casino", Desc: "Quick guides to all casino games"},
- }
- sess.WriteLine(g.renderCasinoCommands(sess, "", "Table controls", commands))
+ // The table command always shows the live felt. The rules command opens
+ // the game's help page; there is no separate "table rules" command.
+ if t.Config.Game == casino.GameVideoPoker {
+ // A video poker cabinet's felt is its paytable.
+ g.executeTableAction(sess, "paytable")
+ return
+ }
+ g.emitCasinoEvents(t.Status(sess.Player.Name))
+ if felt := g.renderCasinoFelt(sess, t.Config); felt != "" {
+ sess.WriteLine(felt)
+ }
+ if t.Config.Game == casino.GameRoulette {
+ sess.WriteLine(g.rouletteReminder(sess, t.Config))
+ } else {
+ sess.WriteLine(g.colorize(sess, "command", "Learn to play: rules."))
}
}
diff --git a/internal/game/casino_ux_test.go b/internal/game/casino_ux_test.go
index 5313484..415df63 100644
--- a/internal/game/casino_ux_test.go
+++ b/internal/game/casino_ux_test.go
@@ -168,10 +168,10 @@ func TestTableStatusShowsStateAndRules(t *testing.T) {
if !strings.Contains(text, "LIMITS") || !strings.Contains(text, "Waiting for bets") {
t.Fatalf("status missing overview: %q", text)
}
- conn.data = nil
- g.executeTableStatus(sess, []string{"rules"}, "table rules")
- if !strings.Contains(stripANSI(string(conn.data)), "BLACKJACK PAYS") {
- t.Fatalf("table rules missing: %q", stripANSI(string(conn.data)))
+ // The table command carries the blackjack felt; the rules command (not a
+ // "table rules" subcommand) opens the help page.
+ if !strings.Contains(text, "BLACKJACK PAYS") {
+ t.Fatalf("table felt missing: %q", text)
}
}
diff --git a/internal/game/cmd_registry.go b/internal/game/cmd_registry.go
index acac68b..8aad30b 100644
--- a/internal/game/cmd_registry.go
+++ b/internal/game/cmd_registry.go
@@ -56,8 +56,8 @@ var commandRegistry = map[string]commandDef{
}, ClassInstant},
"roll": {func(g *Game, s *net.Session, _ []string, _ string) { g.executeTableAction(s, "roll") }, ClassActive},
"autoroll": {func(g *Game, s *net.Session, _ []string, _ string) { g.executeTableAction(s, "autoroll") }, ClassInstant},
- "unbet": {func(g *Game, s *net.Session, args []string, _ string) {
- g.executeTableAction(s, "unbet "+strings.Join(args, " "))
+ "unbet": {func(g *Game, s *net.Session, _ []string, _ string) {
+ g.executeTableAction(s, "unbet")
}, ClassActive},
"table": {(*Game).executeTableStatus, ClassInstant},
"status": {(*Game).executeTableStatus, ClassInstant},
diff --git a/internal/game/craps_test.go b/internal/game/craps_test.go
index 569b4db..fd2e4b9 100644
--- a/internal/game/craps_test.go
+++ b/internal/game/craps_test.go
@@ -5,12 +5,14 @@ import (
"testing"
"thehouseoficarus/internal/casino"
"thehouseoficarus/internal/color"
+ "thehouseoficarus/internal/combat"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
)
func TestCrapsCommandsAndRendering(t *testing.T) {
- g := &Game{Casino: casino.NewManager(1)}
+ g := &Game{Casino: casino.NewManager(1), Combat: combat.NewTracker(), Deps: Deps{World: world.New(t.TempDir())}}
cfg := casino.TableConfig{ID: "craps", Game: casino.GameCraps}
g.Casino.Join(1, cfg, "alice")
conn := &casinoTestConn{}
@@ -24,8 +26,8 @@ func TestCrapsCommandsAndRendering(t *testing.T) {
}
}
conn.data = nil
- g.executeTableStatus(sess, []string{"rules"}, "table rules")
- if !strings.Contains(string(conn.data), "Bank Craps") || !strings.Contains(string(conn.data), "PASS LINE") {
+ g.executeTableStatus(sess, nil, "table")
+ if !strings.Contains(string(conn.data), "PASS LINE") {
t.Fatal(string(conn.data))
}
for _, cmd := range []string{"roll", "autoroll", "unbet"} {
diff --git a/internal/game/look_casino.go b/internal/game/look_casino.go
index 6fa2959..1e91ced 100644
--- a/internal/game/look_casino.go
+++ b/internal/game/look_casino.go
@@ -93,7 +93,7 @@ func (g *Game) renderCasinoTableDetails(sess *net.Session, cfg casino.TableConfi
}
// casinoTableSummary is the compact per-table rule description used by the
-// look details block. The full wording stays in RulesText.
+// look details block. The full wording lives in the rules help page.
func casinoTableSummary(cfg casino.TableConfig) string {
switch cfg.Game {
case casino.GameBlackjack:
diff --git a/internal/game/pai_gow_test.go b/internal/game/pai_gow_test.go
index 9b37a68..b8a9c22 100644
--- a/internal/game/pai_gow_test.go
+++ b/internal/game/pai_gow_test.go
@@ -18,7 +18,7 @@ func TestPaiGowCommandsLookAndSettlement(t *testing.T) {
t.Fatal("alias failed")
}
conn.data = nil
- g.executeTableStatus(sess, []string{"rules"}, "table rules")
+ g.executeTableStatus(sess, nil, "table")
text := stripANSI(string(conn.data))
for _, want := range []string{"FORTUNE", "ENVY", "750:1", "Three pair"} {
if !strings.Contains(text, want) {
@@ -147,6 +147,15 @@ func TestPaiGowShowdownOrderNamesAndColors(t *testing.T) {
t.Fatalf("showdown missing %q:\n%s", want, clean)
}
}
+ // The hand labels are left-justified normal text, never centered over the
+ // card art.
+ for _, line := range strings.Split(clean, "\n") {
+ if strings.Contains(line, "Your high") || strings.Contains(line, "Dealer high") {
+ if strings.HasPrefix(line, " ") {
+ t.Fatalf("showdown label is indented, not left justified: %q", line)
+ }
+ }
+ }
// Winning hand strengths are bold yellow; losing ones are bold white.
if !strings.Contains(raw, "38;5;220m") {
t.Fatalf("winning strength not yellow:\n%q", raw)
@@ -296,7 +305,7 @@ func TestPaiGowContextHintsFollowState(t *testing.T) {
conn.data = nil
g.executeTableStatus(sess, nil, "table")
text := stripANSI(string(conn.data))
- for _, want := range []string{"bet <amount> fortune", "bet <amount>", "table rules", "rules", "stop"} {
+ for _, want := range []string{"bet <amount> fortune", "bet <amount>", "table", "rules", "stop"} {
if !strings.Contains(text, want) {
t.Fatalf("missing %q at a fresh table: %s", want, text)
}
diff --git a/internal/game/render_casino_help.go b/internal/game/render_casino_help.go
index 26927ae..248a6dd 100644
--- a/internal/game/render_casino_help.go
+++ b/internal/game/render_casino_help.go
@@ -40,7 +40,7 @@ func (g *Game) casinoBetTable(sess *net.Session, title string, columns []string,
return strings.Join(t.Render(unicode, width), "\n")
}
-// rouletteReminder is shown by table/table rules, not on ambient boards.
+// rouletteReminder is shown by the table command, not on ambient boards.
// Hit chances count equally likely pockets; all ratios are NET profit odds.
func (g *Game) rouletteReminder(sess *net.Session, cfg casino.TableConfig) string {
pockets, basketCount, basketOdds := 37, 4, "8:1"
@@ -83,6 +83,6 @@ func (g *Game) rouletteReminder(sess *net.Session, cfg casino.TableConfig) strin
"Profit excludes your returned stake: 1:1 on 10 returns 20.\n"+
"Low: 1-18; high: 19-36. Zero pockets lose outside bets.\n"+
"Use bet <amount> on <target>, e.g. bet 10 on split 1 2.\n"+
- "Learn more: help roulette / help casino; table rules for this table.")
+ "Learn more: help roulette / help casino; table for the felt.")
return strings.Join(blocks, "\n")
}
diff --git a/internal/game/render_craps.go b/internal/game/render_craps.go
index 12714dc..80f05ee 100644
--- a/internal/game/render_craps.go
+++ b/internal/game/render_craps.go
@@ -359,7 +359,7 @@ func renderCrapsSnapshot(s *casino.CrapsSnapshot, mode string, unicode bool, sty
}
c := newCrapsCanvas(height)
c.box(0, 0, 76, height-1)
- c.center(1, 75, 1, "HOUSE EDITION — table rules for payouts", crapsGold)
+ c.center(1, 75, 1, "HOUSE EDITION — rules teaches this edition", crapsGold)
switch variant {
case "crapless":
c.center(1, 75, 3, "POINTS: 2 3 4 5 6 8 9 10 11 12 | PASS / COME / ODDS", crapsInk)
diff --git a/internal/game/render_help.go b/internal/game/render_help.go
index 36f73f7..6887e3e 100644
--- a/internal/game/render_help.go
+++ b/internal/game/render_help.go
@@ -64,7 +64,7 @@ var commandList = []cmdEntry{
{"look / l", "Instant", "Look around or examine things"},
{"map", "Instant", "Display an ASCII map of the area"},
{"play", "Active", "Join a game; play bj / play bac; optional amount and target"},
- {"table / status", "Instant", "Redisplay table state; table rules / table help for details"},
+ {"table / status", "Instant", "Redisplay the table felt; rules for the help page"},
{"rules", "Instant", "Help page for the casino game you're playing (one per house edition)"},
{"watch", "Instant", "Watch detailed table action; watch off to stop"},
{"sitout", "Instant", "Sit out future table rounds; bet to rejoin"},
diff --git a/internal/game/render_pai_gow.go b/internal/game/render_pai_gow.go
index 4b31373..b125a68 100644
--- a/internal/game/render_pai_gow.go
+++ b/internal/game/render_pai_gow.go
@@ -72,8 +72,9 @@ func paiGowNumberLabels(nums []int, mode string) []string {
return labels
}
-// paiGowCardBlock renders one labeled run of cards: an optional centered
-// header over the card art and, when nums is set, position numbers below.
+// paiGowCardBlock renders one labeled run of cards: an optional header over
+// the card art and, when nums is set, position numbers below. The header is
+// plain left-justified text, not centered, so the hand labels read normally.
func paiGowCardBlock(header string, cards []casino.CardView, nums []int, mode string, unicode bool, size int, style string, store *cardart.Store) []string {
if len(cards) == 0 {
return nil
@@ -86,11 +87,7 @@ func paiGowCardBlock(header string, cards []casino.CardView, nums []int, mode st
if header == "" {
return lines
}
- width := 0
- for _, line := range lines {
- width = max(width, color.VisibleLen(line))
- }
- return append([]string{rouletteCenter(header, width)}, lines...)
+ return append([]string{header}, lines...)
}
// paiGowJoinBlocks sets two card blocks side by side with a gutter between
diff --git a/internal/game/render_roulette.go b/internal/game/render_roulette.go
index 9ffd355..218a91f 100644
--- a/internal/game/render_roulette.go
+++ b/internal/game/render_roulette.go
@@ -9,7 +9,6 @@ import (
"thehouseoficarus/internal/casino"
"thehouseoficarus/internal/color"
- "thehouseoficarus/internal/net"
)
// Roulette art is rendered per recipient, just like cards. Never embed ANSI
@@ -595,48 +594,3 @@ func renderRouletteResult(pockets [5]string, mode string, unicode bool, size int
}
return strings.Join(lines, "\n")
}
-
-func (g *Game) renderCasinoRules(sess *net.Session, cfg casino.TableConfig) string {
- if cfg.Game == casino.GameBlackjack {
- // The whole rule card is a rendered felt.
- return g.renderCasinoSnapshot(sess, &casino.BlackjackRulesView{Config: cfg})
- }
- if cfg.Game == casino.GamePaiGow {
- return cfg.RulesText() + "\n" + renderPaiGowFelt(cfg, g.colorMode(sess), sess.Player.OptionBool("unicode"))
- }
- if cfg.Game == casino.GameBaccarat {
- return g.renderCasinoSnapshot(sess, &casino.BaccaratRulesView{Config: cfg})
- }
- rules := g.colorize(sess, "casino_action", cfg.RulesText())
- if cfg.Game == casino.GameCraps {
- snap := &casino.CrapsSnapshot{Phase: "waiting"}
- if g.Casino != nil && sess.Player != nil {
- if table := g.Casino.Table(sess.Player.RoomID, cfg.ID); table != nil {
- for _, event := range table.Status(sess.Player.Name) {
- if view, ok := event.Snapshot.(*casino.CrapsSnapshot); ok {
- snap = view
- break
- }
- }
- }
- }
- return rules + "\n" + g.renderCasinoSnapshot(sess, snap)
- }
- if cfg.Game == casino.GameRoulette {
- snap := &casino.RouletteSnapshot{DoubleZero: cfg.Roulette.DoubleZero, Racetrack: cfg.Roulette.Racetrack, Phase: "betting", Wheel: casino.WheelOrder(cfg.Roulette.DoubleZero)}
- // Rules views draw a betting board too; use the live table's history
- // rather than presenting an empty tracker after completed spins.
- if g.Casino != nil && sess.Player != nil {
- if table := g.Casino.Table(sess.Player.RoomID, cfg.ID); table != nil {
- for _, event := range table.Status(sess.Player.Name) {
- if view, ok := event.Snapshot.(*casino.RouletteSnapshot); ok {
- snap.History = view.History
- break
- }
- }
- }
- }
- rules += "\n" + g.renderCasinoSnapshot(sess, snap) + "\n" + g.rouletteReminder(sess, cfg)
- }
- return rules
-}
diff --git a/internal/game/render_roulette_test.go b/internal/game/render_roulette_test.go
index fcfd9c7..72da225 100644
--- a/internal/game/render_roulette_test.go
+++ b/internal/game/render_roulette_test.go
@@ -406,7 +406,7 @@ func TestRouletteSettlementShowsBalanceOnce(t *testing.T) {
if action != "" {
g.Casino.Action(1, "r", p.Name, action)
}
- g.Casino.Tick()
+ // A lone player's spin starts on the bet, so the first tick settles.
g.emitCasinoEvents(g.Casino.Tick())
if got := strings.Count(string(conn.data), "Available "); got != 1 {
t.Fatalf("action=%q: balance printed %d times\n%s", action, got, conn.data)
diff --git a/internal/game/render_roulette_tracker_test.go b/internal/game/render_roulette_tracker_test.go
index a629b57..b05a2be 100644
--- a/internal/game/render_roulette_tracker_test.go
+++ b/internal/game/render_roulette_tracker_test.go
@@ -105,7 +105,7 @@ func TestRouletteTrackerAboveBettingBoardsOnly(t *testing.T) {
}
}
-func TestRouletteRulesUseLiveHistory(t *testing.T) {
+func TestRouletteTableShowsLiveHistory(t *testing.T) {
g := &Game{Casino: casino.NewManager(7)}
p := player.New("alice")
p.RoomID, p.Credits = 1, 100
@@ -116,8 +116,20 @@ func TestRouletteRulesUseLiveHistory(t *testing.T) {
g.Casino.Bet(1, "r", p.Name, "red", 1, casinoWallet{g: g, p: p, saveCharacter: func(*player.Player) error { return nil }})
g.Casino.Tick()
g.Casino.Tick()
- art := g.renderCasinoRules(sess, cfg)
+ // The table command renders the live status snapshot, which carries the
+ // tracker's completed-spin history.
+ var snap *casino.RouletteSnapshot
+ for _, event := range g.Casino.Table(1, "r").Status(p.Name) {
+ if view, ok := event.Snapshot.(*casino.RouletteSnapshot); ok && view.Phase != "result" {
+ snap = view
+ break
+ }
+ }
+ if snap == nil {
+ t.Fatal("no live roulette snapshot")
+ }
+ art := g.renderCasinoSnapshot(sess, snap)
if !strings.Contains(art, "History: last 1 spin") || strings.Contains(art, "awaiting the first spin") {
- t.Fatalf("rules did not show live history:\n%s", art)
+ t.Fatalf("table did not show live history:\n%s", art)
}
}
diff --git a/internal/game/roulette_test.go b/internal/game/roulette_test.go
index 0c5b62e..9eaea8e 100644
--- a/internal/game/roulette_test.go
+++ b/internal/game/roulette_test.go
@@ -5,13 +5,15 @@ import (
"testing"
"thehouseoficarus/internal/casino"
+ "thehouseoficarus/internal/combat"
"thehouseoficarus/internal/net"
"thehouseoficarus/internal/player"
+ "thehouseoficarus/internal/world"
)
func TestRouletteNumericTargetsAndRendering(t *testing.T) {
for _, double := range []bool{false, true} {
- g := &Game{Casino: casino.NewManager(1)}
+ g := &Game{Casino: casino.NewManager(1), Combat: combat.NewTracker(), Deps: Deps{World: world.New(t.TempDir())}}
cfg := casino.TableConfig{ID: "roulette", Game: casino.GameRoulette, Roulette: casino.RouletteConfig{DoubleZero: double}}
g.Casino.Join(1, cfg, "alice")
conn := &casinoTestConn{}
@@ -29,9 +31,9 @@ func TestRouletteNumericTargetsAndRendering(t *testing.T) {
}
}
conn.data = nil
- g.executeTableStatus(sess, []string{"rules"}, "table rules")
- if !strings.Contains(string(conn.data), "Straight numbers pay 35:1") || strings.Contains(string(conn.data), "Blackjack") {
- t.Fatal("wrong rules", string(conn.data))
+ g.executeTableStatus(sess, nil, "table")
+ if !strings.Contains(string(conn.data), "35:1") || strings.Contains(string(conn.data), "Blackjack") {
+ t.Fatal("wrong felt", string(conn.data))
}
snapshot := &casino.RouletteSnapshot{DoubleZero: double, Phase: "playing", Players: []string{"alice"}}
if got := g.renderCasinoSnapshot(sess, snapshot); got != renderRouletteSnapshot(snapshot, g.colorMode(sess), sess.Player.OptionBool("unicode"), sess.Player.OptionInt("card_size")) {