From ff9002b96e35ad0808945e7c35791460c9ca8776 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Tue, 29 Sep 2026 17:50:33 -0400 Subject: feat: roulette racetrack betting, rename casino to domita fortuna --- internal/game/render_roulette.go | 59 ++++++++++++++++++++++++++++++++-------- 1 file changed, 47 insertions(+), 12 deletions(-) (limited to 'internal/game/render_roulette.go') diff --git a/internal/game/render_roulette.go b/internal/game/render_roulette.go index 47ba5c6..159fadc 100644 --- a/internal/game/render_roulette.go +++ b/internal/game/render_roulette.go @@ -21,7 +21,7 @@ func renderRouletteSnapshot(s *casino.RouletteSnapshot, mode string, unicode boo case "result": return renderRouletteResult(s.Pockets, mode, unicode, size) default: - return renderRouletteLayout(s.DoubleZero, mode, unicode) + return renderRouletteLayout(s.DoubleZero, s.Racetrack, mode, unicode) } } @@ -76,18 +76,49 @@ 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. +func renderRouletteRacetrack(mode string, unicode bool) string { + b := rouletteBorders(unicode) + tl, tr, bl, br := b.tl, b.tr, b.bl, b.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) + } + lines = append(lines, rouletteInk(mode, b.v+row+b.v)) + } + lines = append(lines, rouletteInk(mode, bl+strings.Repeat(b.h, width)+br)) + return strings.Join(lines, "\n") +} + // renderRouletteLayout draws the betting board. Green is reserved for the // 0/00 lane: every other cell is red, black, or plain. The odds reminder is // not part of the board (it is shown by the "table" command), so the board // stays compact enough to sit in a betting menu. -func renderRouletteLayout(double bool, mode string, unicode bool) string { +func renderRouletteLayout(double, racetrack bool, mode string, unicode bool) string { + // The call bets are a single-zero layout; never draw the track on an + // American board even if a malformed snapshot asks for it. + racetrack = racetrack && !double b := rouletteBorders(unicode) var lines []string - title := "ROULETTE / SINGLE ZERO" - if double { - title = "ROULETTE / DOUBLE ZERO" + if racetrack { + lines = append(lines, renderRouletteRacetrack(mode, unicode)) } - lines = append(lines, rouletteInk(mode, title)) // Five columns for the zero lane, twelve four-column pockets, and a // six-column 2:1 lane on the right holding the row bets. Four adjacent // pockets line up exactly over each 19-column dozen. @@ -434,11 +465,15 @@ func renderRouletteResult(pockets [3]string, mode string, unicode bool, size int } // rouletteReminder is the odds/cheat-sheet block, shown only by "table" (and -// "table rules"), never with the ambient betting board. -func (g *Game) rouletteReminder(sess *net.Session) string { +// "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" + - "Low: 1-18 | High: 19-36 | Zero loses all outside bets\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) } @@ -446,8 +481,8 @@ func (g *Game) rouletteReminder(sess *net.Session) string { func (g *Game) renderCasinoRules(sess *net.Session, cfg casino.TableConfig) string { rules := g.colorize(sess, "casino_action", cfg.RulesText()) if cfg.Game == casino.GameRoulette { - snap := &casino.RouletteSnapshot{DoubleZero: cfg.Roulette.DoubleZero, Phase: "betting", Wheel: casino.WheelOrder(cfg.Roulette.DoubleZero)} - rules += "\n" + g.renderCasinoSnapshot(sess, snap) + "\n" + g.rouletteReminder(sess) + snap := &casino.RouletteSnapshot{DoubleZero: cfg.Roulette.DoubleZero, Racetrack: cfg.Roulette.Racetrack, Phase: "betting", Wheel: casino.WheelOrder(cfg.Roulette.DoubleZero)} + rules += "\n" + g.renderCasinoSnapshot(sess, snap) + "\n" + g.rouletteReminder(sess, cfg) } return rules } -- cgit v1.2.3