From 624626b9a10ae47cf7d5f6e91765570ed759bd74 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Tue, 29 Sep 2026 18:58:28 -0400 Subject: feat: better roulette racetrack graphics and payout table --- internal/game/render_roulette.go | 136 ++++++++++++++++++++++++++++----------- 1 file changed, 100 insertions(+), 36 deletions(-) (limited to 'internal/game/render_roulette.go') 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 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 { -- cgit v1.2.3