diff options
| author | historia <[not public]> | 2026-09-29 14:35:57 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-29 14:35:57 -0400 |
| commit | 61fb8e950e0b30ef0ee65aa5882545e5292d9112 (patch) | |
| tree | 06319b38231d1b86236b8d3944a1accf9824eb08 /internal/game | |
| parent | 004dcd3826856870b267a55dacf84aa7742b7e3f (diff) | |
| download | thehouseoficarus-61fb8e950e0b30ef0ee65aa5882545e5292d9112.tar.gz | |
fix: roulette table box drawing characters
Diffstat (limited to 'internal/game')
| -rw-r--r-- | internal/game/render_roulette.go | 88 | ||||
| -rw-r--r-- | internal/game/render_roulette_test.go | 57 | ||||
| -rw-r--r-- | internal/game/roulette_test.go | 2 |
3 files changed, 122 insertions, 25 deletions
diff --git a/internal/game/render_roulette.go b/internal/game/render_roulette.go index 95e91ff..4861939 100644 --- a/internal/game/render_roulette.go +++ b/internal/game/render_roulette.go @@ -80,26 +80,56 @@ func renderRouletteLayout(double bool, mode string, unicode bool) string { title = "ROULETTE / DOUBLE ZERO" } lines = append(lines, rouletteInk(mode, title)) - // Five columns for the zero lane, twelve four-column pockets. Four - // adjacent pockets line up exactly over each 19-column dozen. + // 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. + const rowCell = 6 gridRule := func(left, joint, right string) string { - return left + strings.Repeat(b.h, 5) + joint + strings.Join(repeatRoulette(strings.Repeat(b.h, 4), 12), joint) + right + return left + strings.Repeat(b.h, 5) + joint + + strings.Join(repeatRoulette(strings.Repeat(b.h, 4), 12), joint) + + joint + strings.Repeat(b.h, rowCell) + right + } + // The numbered grid's bottom edge closes the row-bet cells and carries the + // dozen-row dividers as downward stubs: crosses at the inner dozen + // boundaries and where the grid meets the row lane, an upward tee + // everywhere else. + gridBottom := func() string { + var sb strings.Builder + sb.WriteString(b.bl) + for col := 6; col <= 66; col += 5 { + if col == 6 { + sb.WriteString(strings.Repeat(b.h, 5)) + } else { + sb.WriteString(strings.Repeat(b.h, 4)) + } + switch col { + case 6, 26, 46, 66: + sb.WriteString(b.cross) + default: + sb.WriteString(b.teeBottom) + } + } + sb.WriteString(strings.Repeat(b.h, rowCell)) + sb.WriteString(b.br) + return sb.String() } lines = append(lines, rouletteInk(mode, gridRule(b.tl, b.teeTop, b.tr))) for row := 3; row >= 1; row-- { - zero, zeroBg := "", -1 + zero := "" switch { case double && row == 3: - zero, zeroBg = "0", 2 + zero = "0" case double && row == 1: - zero, zeroBg = "00", 2 + zero = "00" case !double && row == 2: - zero, zeroBg = "0", 2 + zero = "0" } - if zeroBg == 2 && (mode == "none" || mode == "") { + if mode == "none" || mode == "" { zero += "G" } - line := rouletteInk(mode, b.v) + roulettePaint(mode, rouletteCenter(zero, 5), zeroBg) + rouletteInk(mode, b.v) + // The 0/00 lane is one green rectangle, so its blank cells above and + // below the zeros are painted to match the pockets beside them. + line := rouletteInk(mode, b.v) + roulettePaint(mode, rouletteCenter(zero, 5), 2) + rouletteInk(mode, b.v) for n := row; n <= 36; n += 3 { pocket := strconv.Itoa(n) bg, name := roulettePocketColor(pocket) @@ -108,22 +138,32 @@ func renderRouletteLayout(double bool, mode string, unicode bool) string { } line += roulettePaint(mode, rouletteCenter(pocket, 4), bg) + rouletteInk(mode, b.v) } - line += rouletteInk(mode, fmt.Sprintf(" column%d", row)) + // row1 is the top numbered line (3,6,...36) and row3 the bottom. + line += rouletteInk(mode, rouletteCenter(fmt.Sprintf("row%d", 4-row), rowCell)+b.v) lines = append(lines, line) if row > 1 { - // The zero lane spans the grid height; only its labeled cell is green. - line = rouletteInk(mode, b.v+" "+b.teeLeft+strings.Join(repeatRoulette(strings.Repeat(b.h, 4), 12), b.cross)+b.teeRight) + line = rouletteInk(mode, b.v+" "+b.teeLeft+ + strings.Join(repeatRoulette(strings.Repeat(b.h, 4), 12), b.cross)+ + b.cross+strings.Repeat(b.h, rowCell)+b.teeRight) lines = append(lines, line) } } - lines = append(lines, rouletteInk(mode, gridRule(b.bl, b.teeBottom, b.br))) - // Outside bets share the exact 60-column numbered-grid footprint. + lines = append(lines, rouletteInk(mode, gridBottom())) + // Outside bets share the exact 60-column numbered-grid footprint. The + // dozen row gets its own bottom rule so the 1to12/13to24/25to36 boxes are + // closed and do not run into the even-money spaces below them. indent := " " dozen := indent + rouletteInk(mode, b.v) - for i := 1; i <= 3; i++ { - dozen += rouletteInk(mode, rouletteCenter(fmt.Sprintf("dozen%d", i), 19)) + rouletteInk(mode, b.v) + for _, label := range []string{"1to12", "13to24", "25to36"} { + dozen += rouletteInk(mode, rouletteCenter(label, 19)) + rouletteInk(mode, b.v) } lines = append(lines, dozen) + dozenBottom := indent + b.teeLeft + dozenJoints := []string{b.teeTop, b.cross, b.teeTop, b.cross, b.teeTop, b.teeRight} + for i := 0; i < 6; i++ { + dozenBottom += strings.Repeat(b.h, 9) + dozenJoints[i] + } + lines = append(lines, rouletteInk(mode, dozenBottom)) line := indent + rouletteInk(mode, b.v) for _, bet := range []string{"low", "even", "red", "black", "odd", "high"} { bg := -1 @@ -135,7 +175,17 @@ func renderRouletteLayout(double bool, mode string, unicode bool) string { } line += roulettePaint(mode, rouletteCenter(bet, 9), bg) + rouletteInk(mode, b.v) } - lines = append(lines, line, rouletteInk(mode, indent+b.bl+strings.Repeat(b.h, 59)+b.br)) + // The bottom rule tees upward under each outside-bet divider so the + // vertical lines below the dozens meet the table's bottom edge. + bottom := indent + b.bl + for i := 0; i < 6; i++ { + bottom += strings.Repeat(b.h, 9) + if i < 5 { + bottom += b.teeBottom + } + } + bottom += b.br + lines = append(lines, line, rouletteInk(mode, bottom)) if mode == "none" || mode == "" { lines = append(lines, "R = red, B = black, G = green") } @@ -317,9 +367,9 @@ 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 { - text := "Numbers 35:1 | Dozens / columns 2:1 | Other outside bets 1:1\n" + + text := "Numbers 35:1 | 1to12 / 13to24 / 25to36 and rows 2:1 | Other outside bets 1:1\n" + "Low: 1-18 | High: 19-36 | Zero loses all outside bets\n" + - "bet 10 on 17 / bet 10 on red / bet 10 on dozen2" + "bet 10 on 17 / bet 10 on red / bet 10 on 13to24" return g.colorize(sess, "casino_action", text) } diff --git a/internal/game/render_roulette_test.go b/internal/game/render_roulette_test.go index 1dde5ad..c1065e5 100644 --- a/internal/game/render_roulette_test.go +++ b/internal/game/render_roulette_test.go @@ -74,7 +74,7 @@ func TestRouletteArtModes(t *testing.T) { } else if !strings.Contains(string([]rune(rows[4])[:7]), "0") { t.Fatal("zero not left of grid", layout) } - for _, target := range []string{"column1", "column2", "column3", "dozen1", "dozen2", "dozen3", "red", "black", "odd", "even", "low", "high"} { + for _, target := range []string{"1to12", "13to24", "25to36", "row1", "row2", "row3", "red", "black", "odd", "even", "low", "high"} { if !strings.Contains(layout, target) { t.Fatal("missing bet", target) } @@ -86,10 +86,9 @@ func TestRouletteArtModes(t *testing.T) { } } if mode != "none" { - wantGreen := 1 - if double { - wantGreen = 2 - } + // The whole 0/00 lane is green: blank cells above and below + // the zeros fill the rectangle. + wantGreen := 3 if got := strings.Count(layoutArt, color.BgCode(mode, 2)); got != wantGreen { t.Fatalf("green cells on board = %d, want %d", got, wantGreen) } @@ -135,6 +134,54 @@ func TestRouletteArtModes(t *testing.T) { } } +// TestRouletteLayoutConnections guards the box-drawing joints: the dozen-row +// boxes must connect to the numbered grid above and be closed under, the +// row-bet cells must be boxed on the right, and the bottom rule must tee under +// each outside-bet divider. +func TestRouletteLayoutConnections(t *testing.T) { + for _, double := range []bool{false, true} { + art := plainRoulette(renderRouletteLayout(double, "xterm256", true)) + rows := strings.Split(art, "\n") + // rows[7] is the numbered grid's bottom edge, directly above the dozen + // labels. The dozen boundaries and the grid/row-lane joint cross; the + // row-bet lane closes with a bottom-right corner. + gridBottom := []rune(rows[7]) + for _, col := range []int{6, 26, 46, 66} { + if gridBottom[col] != '┼' { + t.Fatalf("double=%v: grid bottom col %d = %q, want ┼:\n%s", double, col, gridBottom[col], art) + } + } + if gridBottom[73] != '┘' { + t.Fatalf("double=%v: grid bottom row-lane corner = %q, want ┘:\n%s", double, gridBottom[73], art) + } + // The row-bet lane is boxed: its separators cross at the grid edge and + // close on the right. + for _, idx := range []int{3, 5} { + sep := []rune(rows[idx]) + if sep[66] != '┼' || sep[73] != '┤' { + t.Fatalf("double=%v: row lane separator %q lacks box joints:\n%s", double, rows[idx], art) + } + } + // rows[9] is the dozen boxes' bottom edge: tees point down where the + // even-money dividers sit and crosses pass the dozen dividers through. + dozenBottom := []rune(rows[9]) + wantDozen := map[int]rune{6: '├', 16: '┬', 26: '┼', 36: '┬', 46: '┼', 56: '┬', 66: '┤'} + for col, want := range wantDozen { + if dozenBottom[col] != want { + t.Fatalf("double=%v: dozen bottom col %d = %q, want %q:\n%s", double, col, dozenBottom[col], want, art) + } + } + // The last line is the table's bottom rule; it tees upward under each + // outside-bet divider. + bottom := []rune(rows[len(rows)-1]) + for _, col := range []int{16, 26, 36, 46, 56} { + if bottom[col] != '┴' { + t.Fatalf("double=%v: table bottom col %d = %q, want ┴:\n%s", double, col, bottom[col], art) + } + } + } +} + func TestRouletteResultSizesAndNeighbors(t *testing.T) { for _, unicode := range []bool{false, true} { for _, size := range []int{1, 5, 7, 9} { diff --git a/internal/game/roulette_test.go b/internal/game/roulette_test.go index 9537710..a963913 100644 --- a/internal/game/roulette_test.go +++ b/internal/game/roulette_test.go @@ -17,7 +17,7 @@ func TestRouletteNumericTargetsAndRendering(t *testing.T) { conn := &casinoTestConn{} sess := &net.Session{Player: player.New("alice"), Conn: conn} g.loggedInChars = map[string]*net.Session{"alice": sess} - for _, target := range []string{"0", "17", "00", "red", "column2"} { + for _, target := range []string{"0", "17", "00", "red", "row2"} { conn.data = nil g.executeBet(sess, []string{"10", "on", target}, "bet 10 on "+target) expected := "You don't have enough chips and credits" |
