aboutsummaryrefslogtreecommitdiff
path: root/internal/game/render_roulette.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-09-29 18:58:28 -0400
committerhistoria <[not public]>2026-09-29 18:58:28 -0400
commit624626b9a10ae47cf7d5f6e91765570ed759bd74 (patch)
tree9812f5d254b7d9cfe1d2fcb6da53362e974541b0 /internal/game/render_roulette.go
parentff9002b96e35ad0808945e7c35791460c9ca8776 (diff)
downloadthehouseoficarus-624626b9a10ae47cf7d5f6e91765570ed759bd74.tar.gz
feat: better roulette racetrack graphics and payout table
Diffstat (limited to 'internal/game/render_roulette.go')
-rw-r--r--internal/game/render_roulette.go136
1 files changed, 100 insertions, 36 deletions
diff --git a/internal/game/render_roulette.go b/internal/game/render_roulette.go
index 159fadc..f6c6bb7 100644
--- a/internal/game/render_roulette.go
+++ b/internal/game/render_roulette.go
@@ -76,33 +76,111 @@ func repeatRoulette(s string, n int) []string {
return parts
}
-// renderRouletteRacetrack draws the French call-bet track above the board. The
-// numbers are shown in wheel order for reference; the named bets are what the
-// player places. The Neighbors line explains the one bet that takes a number.
+// renderRouletteRacetrack draws the European wheel clockwise with 26 at
+// the right-hand tip. Interior boundaries meet the pockets covered by each
+// call bet; the bowed Zero boundary denotes its overlap with Voisins.
func renderRouletteRacetrack(mode string, unicode bool) string {
b := rouletteBorders(unicode)
- tl, tr, bl, br := b.tl, b.tr, b.bl, b.br
+ tl, tr, bl, br := "/", "\\", "\\", "/"
if unicode {
tl, tr, bl, br = "╭", "╮", "╰", "╯"
}
- // The panel matches the betting board's 74-column footprint.
- const width = 72
- bets := casino.RacetrackBets()
- rows := make([]string, 0, len(bets)+1)
- for _, bet := range bets {
- label := fmt.Sprintf("%s (%d)", bet.Name, bet.Chips)
- rows = append(rows, fmt.Sprintf("%-22s %s", label, strings.Join(bet.Series, " ")))
- }
- rows = append(rows, fmt.Sprintf("%-22s %s", "Neighbors (9)", "neighbors <n>: n plus four each side"))
- lines := []string{rouletteInk(mode, tl+strings.Repeat(b.h, width)+tr)}
- for _, row := range rows {
- row = " " + row
- if pad := width - color.VisibleLen(row); pad > 0 {
- row += strings.Repeat(" ", pad)
+ const width, height = 73, 11
+ grid := make([][]rouletteCell, height)
+ for y := range grid {
+ grid[y] = make([]rouletteCell, width)
+ for x := range grid[y] {
+ grid[y][x] = rouletteCell{' ', -1}
+ }
+ }
+ put := func(x, y int, text string, bg int) {
+ for _, r := range text {
+ grid[y][x] = rouletteCell{r, bg}
+ x++
+ }
+ }
+ ink := func(x, y int, text string) { put(x, y, text, -1) }
+ wheel := casino.WheelOrder(false)
+ pocket := func(x, y, index, width int) {
+ n := wheel[index%len(wheel)]
+ bg, name := roulettePocketColor(n)
+ if mode == "none" || mode == "" {
+ n += name[:1]
+ }
+ put(x, y, rouletteCenter(n, width), bg)
+ }
+ // Equal-width bands, but slightly wider Orphelins cells below: five
+ // pockets (1,20,14,31,9) above face three (6,34,17) below.
+ band := func(y, start, step int, widths []int) {
+ x := 4
+ ink(x, y, b.v)
+ for i, w := range widths {
+ ink(x, y-1, b.teeTop+strings.Repeat(b.h, w-1))
+ ink(x, y+1, b.teeBottom+strings.Repeat(b.h, w-1))
+ pocket(x+1, y, start+i*step, w-1)
+ x += w
+ ink(x, y, b.v)
}
- lines = append(lines, rouletteInk(mode, b.v+row+b.v))
}
- lines = append(lines, rouletteInk(mode, bl+strings.Repeat(b.h, width)+br))
+ band(1, 19, 1, []int{4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4})
+ band(9, 15, -1, []int{4, 4, 4, 4, 4, 5, 5, 6, 4, 4, 4, 4, 4, 4, 4})
+ ink(4, 0, tl)
+ ink(68, 0, tr)
+ ink(4, 10, bl)
+ ink(68, 10, br)
+ // Connected, stepped curves join the long bands to three end pockets.
+ ink(1, 1, tl+strings.Repeat(b.h, 2)+b.teeRight)
+ ink(68, 1, b.teeLeft+strings.Repeat(b.h, 2)+tr)
+ ink(0, 2, tl+b.teeBottom+strings.Repeat(b.h, 2)+b.cross)
+ ink(68, 2, b.cross+strings.Repeat(b.h, 2)+b.teeBottom+tr)
+ ink(0, 8, bl+b.teeTop+strings.Repeat(b.h, 2)+b.cross)
+ ink(68, 8, b.cross+strings.Repeat(b.h, 2)+b.teeTop+br)
+ ink(1, 9, bl+strings.Repeat(b.h, 2)+b.teeRight)
+ ink(68, 9, b.teeLeft+strings.Repeat(b.h, 2)+br)
+ for i := 0; i < 3; i++ {
+ y := 3 + 2*i
+ for _, x := range []int{0, 4, 68, 72} {
+ ink(x, y, b.v)
+ }
+ pocket(1, y, 18-i, 3) // 10,23,8
+ pocket(69, y, 35+i, 3) // 3,26,0
+ if i < 2 {
+ ink(0, y+1, b.teeLeft+strings.Repeat(b.h, 3)+b.teeRight)
+ ink(68, y+1, b.teeLeft+strings.Repeat(b.h, 3)+b.teeRight)
+ }
+ }
+ // Connected steps meet 33/1 above and 27/6 below. Diagonal glyphs
+ // only touch at cell corners and leave visible gaps in many fonts.
+ ink(20, 3, b.v)
+ ink(20, 4, b.bl+b.h+b.tr)
+ ink(22, 5, b.v)
+ ink(22, 6, b.bl+b.h+b.tr)
+ ink(24, 7, b.v)
+ for y := 3; y <= 7; y++ {
+ ink(40, y, b.v)
+ }
+ for _, p := range [][2]int{{20, 2}, {24, 8}, {40, 2}, {40, 8}, {60, 2}, {60, 8}} {
+ ink(p[0], p[1], b.cross)
+ }
+ // Zero includes 12,35,3,26,0,32,15, entirely within Voisins.
+ ink(59, 3, tl+br)
+ ink(58, 4, tl+br)
+ ink(58, 5, b.v)
+ ink(58, 6, bl+tr)
+ ink(59, 7, bl+tr)
+ for _, label := range []struct {
+ left, right int
+ text string
+ }{
+ {4, 22, "Tiers du Cylindre"}, {22, 40, "Orphelins"},
+ {40, 58, "Voisins du Zero"}, {58, 68, "Jeu Zero"},
+ } {
+ ink(label.left+1, 5, rouletteCenter(label.text, label.right-label.left-1))
+ }
+ lines := make([]string, height)
+ for y, row := range grid {
+ lines[y] = renderRouletteRow(row, mode)
+ }
return strings.Join(lines, "\n")
}
@@ -157,9 +235,9 @@ func renderRouletteLayout(double, racetrack bool, mode string, unicode bool) str
zero := ""
switch {
case double && row == 3:
- zero = "0"
- case double && row == 1:
zero = "00"
+ case double && row == 1:
+ zero = "0"
case !double && row == 2:
zero = "0"
}
@@ -464,20 +542,6 @@ func renderRouletteResult(pockets [3]string, mode string, unicode bool, size int
return strings.Join(lines, "\n")
}
-// rouletteReminder is the odds/cheat-sheet block, shown only by "table" (and
-// "table rules"), never with the ambient betting board. Racetrack tables also
-// list their call-bet syntax.
-func (g *Game) rouletteReminder(sess *net.Session, cfg casino.TableConfig) string {
- text := "Numbers 35:1 | Split 17:1 | Street/trio 11:1 | Corner 8:1 | Line 5:1 | Dozens & rows 2:1 | Outside 1:1\n" +
- "Combos: split 1 2 | street 1 2 3 | corner 1 2 4 5 | line 1 2 3 4 5 6 | trio 0 1 2 | basket (double zero)\n"
- if cfg.Roulette.Racetrack {
- text += "Racetrack: bet 10 on voisins | tiers | orphelins | zero spiel | neighbors 17\n"
- }
- text += "Low: 1-18 | High: 19-36 | Zero loses all outside bets\n" +
- "bet 10 on 17 / bet 10 on red / bet 10 on split 1 2"
- return g.colorize(sess, "casino_action", text)
-}
-
func (g *Game) renderCasinoRules(sess *net.Session, cfg casino.TableConfig) string {
rules := g.colorize(sess, "casino_action", cfg.RulesText())
if cfg.Game == casino.GameRoulette {