diff options
| author | historia <[not public]> | 2026-09-29 18:58:28 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-29 18:58:28 -0400 |
| commit | 624626b9a10ae47cf7d5f6e91765570ed759bd74 (patch) | |
| tree | 9812f5d254b7d9cfe1d2fcb6da53362e974541b0 /internal/game/render_roulette_test.go | |
| parent | ff9002b96e35ad0808945e7c35791460c9ca8776 (diff) | |
| download | thehouseoficarus-624626b9a10ae47cf7d5f6e91765570ed759bd74.tar.gz | |
feat: better roulette racetrack graphics and payout table
Diffstat (limited to 'internal/game/render_roulette_test.go')
| -rw-r--r-- | internal/game/render_roulette_test.go | 143 |
1 files changed, 138 insertions, 5 deletions
diff --git a/internal/game/render_roulette_test.go b/internal/game/render_roulette_test.go index d992abf..8d9c141 100644 --- a/internal/game/render_roulette_test.go +++ b/internal/game/render_roulette_test.go @@ -68,7 +68,7 @@ func TestRouletteArtModes(t *testing.T) { layout := plainRoulette(layoutArt) rows := strings.Split(layout, "\n") if double { - if !strings.Contains(string([]rune(rows[1])[:7]), "0") || !strings.Contains(string([]rune(rows[5])[:7]), "00") { + if !strings.Contains(string([]rune(rows[1])[:7]), "00") || !strings.Contains(string([]rune(rows[5])[:7]), "0") { t.Fatal("double zero not left of grid", layout) } } else if !strings.Contains(string([]rune(rows[3])[:7]), "0") { @@ -227,14 +227,14 @@ func TestRouletteZeroPanel(t *testing.T) { } // TestRouletteRacetrackPanel checks that an enabled racetrack adds the call-bet -// panel above the board, names all four call bets and the neighbours bet, and +// panel above the board, names the four call-bet areas (not Neighbors), and // keeps the same width and ASCII/Unicode guarantees as the board. func TestRouletteRacetrackPanel(t *testing.T) { for _, mode := range []string{"none", "ansi", "xterm256"} { for _, unicode := range []bool{false, true} { art := renderRouletteLayout(false, true, mode, unicode) plain := plainRoulette(art) - for _, want := range []string{"Voisins du Zero", "Tiers du Cylindre", "Orphelins", "Zero Spiel", "Neighbors (9)"} { + for _, want := range []string{"Tiers du Cylindre", "Orphelins", "Voisins du Zero", "Zero"} { if !strings.Contains(plain, want) { t.Fatalf("mode=%s unicode=%v: missing %q:\n%s", mode, unicode, want, plain) } @@ -255,11 +255,11 @@ func TestRouletteRacetrackPanel(t *testing.T) { } // The panel comes from the snapshot flag, and a plain board omits it. snap := &casino.RouletteSnapshot{Phase: "betting", Wheel: casino.WheelOrder(false), Racetrack: true} - if !strings.Contains(plainRoulette(renderRouletteSnapshot(snap, "none", true, 7)), "Voisins du Zero") { + if !strings.Contains(plainRoulette(renderRouletteSnapshot(snap, "none", true, 7)), "Voisins") { t.Fatal("racetrack snapshot did not draw the panel") } snap.Racetrack = false - if strings.Contains(plainRoulette(renderRouletteSnapshot(snap, "none", true, 7)), "Voisins du Zero") { + if strings.Contains(plainRoulette(renderRouletteSnapshot(snap, "none", true, 7)), "Voisins") { t.Fatal("plain snapshot drew the racetrack panel") } } @@ -404,3 +404,136 @@ func TestRouletteSettlementShowsBalanceOnce(t *testing.T) { } } } + +// Read the perimeter clockwise, not in terminal row order. Every displayed +// pocket must match the actual European wheel, including the wraparound. +func TestRouletteRacetrackPhysicalOrder(t *testing.T) { + for _, unicode := range []bool{false, true} { + art := renderRouletteRacetrack("none", unicode) + rows := strings.Split(art, "\n") + top, bottom := racetrackNumbers(rows[1]), racetrackNumbers(rows[9]) + if len(top) != 16 || len(bottom) != 15 { + t.Fatalf("missing track pockets:\n%s", art) + } + order := append([]string(nil), top...) + for _, i := range []int{3, 5, 7} { + side := racetrackNumbers(rows[i]) + if len(side) != 2 { + t.Fatalf("missing curved-end pockets:\n%s", art) + } + order = append(order, side[1]) + } + for i := len(bottom) - 1; i >= 0; i-- { + order = append(order, bottom[i]) + } + for _, i := range []int{7, 5, 3} { + order = append(order, racetrackNumbers(rows[i])[0]) + } + wheel := casino.WheelOrder(false) + if len(order) != len(wheel) { + t.Fatalf("got %d pockets", len(order)) + } + for i, p := range order { + if p != wheel[(19+i)%len(wheel)] { + t.Errorf("pocket %d: %s", i, p) + } + } + if got := racetrackNumbers(rows[5]); got[1] != "26" { + t.Fatalf("right tip is %s, want 26", got[1]) + } + } +} + +func racetrackNumbers(row string) []string { + var out []string + for _, match := range regexp.MustCompile(`([0-9]+)[RBG]`).FindAllStringSubmatch(row, -1) { + out = append(out, match[1]) + } + return out +} + +// The sections must touch their actual bet coverage, not merely contain +// labels. Zero is a nested part of Voisins, not a fourth disjoint sector. +func TestRouletteRacetrackSections(t *testing.T) { + for _, unicode := range []bool{false, true} { + rows := strings.Split(renderRouletteRacetrack("none", unicode), "\n") + segment := func(y, left, right int) string { return string([]rune(rows[y])[left:right]) } + nums := func(y, left, right int) []string { return racetrackNumbers(segment(y, left, right)) } + sections := map[string][]string{ + "tiers": append(nums(1, 4, 20), nums(9, 4, 24)...), + "orphelins": append(nums(1, 20, 40), nums(9, 24, 40)...), + "voisins": append(nums(1, 40, 60), nums(9, 40, 60)...), + "jeuzero": append(nums(1, 60, 68), nums(9, 60, 68)...), + } + for _, y := range []int{3, 5, 7} { + sections["tiers"] = append(sections["tiers"], nums(y, 0, 4)...) + sections["jeuzero"] = append(sections["jeuzero"], nums(y, 68, 73)...) + } + sections["voisins"] = append(sections["voisins"], sections["jeuzero"]...) + for _, bet := range casino.RacetrackBets() { + got := sections[bet.Syntax] + if len(got) != len(bet.Series) { + t.Fatalf("%s: coverage %v, want %v", bet.Syntax, got, bet.Series) + } + seen := map[string]bool{} + for _, n := range got { + seen[n] = true + } + for _, n := range bet.Series { + if !seen[n] { + t.Errorf("%s does not touch %s", bet.Syntax, n) + } + } + } + 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"}, + } { + if strings.TrimSpace(segment(5, label.left+1, label.right)) != label.text { + t.Errorf("misplaced %s label", label.text) + } + } + // Every step joins orthogonally; no font-dependent diagonal gaps. + b := rouletteBorders(unicode) + for _, step := range []struct { + x, y int + text string + }{ + {20, 3, b.v}, {20, 4, b.bl + b.h + b.tr}, {22, 5, b.v}, + {22, 6, b.bl + b.h + b.tr}, {24, 7, b.v}, + } { + if got := segment(step.y, step.x, step.x+len([]rune(step.text))); got != step.text { + t.Errorf("disconnected step at %d,%d: %q", step.x, step.y, got) + } + } + // The Zero boundary bows into Voisins then returns to the same column. + for _, p := range [][2]int{{60, 2}, {59, 3}, {58, 4}, {58, 5}, {58, 6}, {59, 7}, {60, 8}} { + if []rune(rows[p[1]])[p[0]] == ' ' { + t.Errorf("gap in Zero curve at %v", p) + } + } + } + for _, mode := range []string{"none", "ansi", "xterm256"} { + for _, unicode := range []bool{false, true} { + art := renderRouletteRacetrack(mode, unicode) + if strings.Contains(strings.ToLower(art), "neighbor") { + t.Fatal("Neighbors belongs in help, not on the racetrack") + } + rows := strings.Split(art, "\n") + for y, row := range rows { + wantWidth := 73 + if y == 0 || y == 10 { + wantWidth = 69 + } + if y == 1 || y == 9 { + wantWidth = 72 + } + if color.VisibleLen(row) != wantWidth { + t.Errorf("mode=%s unicode=%v: row %d width=%d, want %d", mode, unicode, y, color.VisibleLen(row), wantWidth) + } + } + } + } +} |
