aboutsummaryrefslogtreecommitdiff
path: root/internal/game/plinko_test.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-04 17:44:27 -0400
committerhistoria <[not public]>2026-10-04 17:44:27 -0400
commit93dcb67ffee05ebf1de0c9a78a8b906eaf161007 (patch)
tree88cdd3d732f11eee616698a9ca32f699d2b41d77 /internal/game/plinko_test.go
parent3a39a020e4449bc4d6f728ef837cffb9f7bd0c56 (diff)
downloadthehouseoficarus-93dcb67ffee05ebf1de0c9a78a8b906eaf161007.tar.gz
feat: new casino game: cash hunt, fixed verb issue with pai gow
Diffstat (limited to 'internal/game/plinko_test.go')
-rw-r--r--internal/game/plinko_test.go130
1 files changed, 85 insertions, 45 deletions
diff --git a/internal/game/plinko_test.go b/internal/game/plinko_test.go
index 711525f..dd3e205 100644
--- a/internal/game/plinko_test.go
+++ b/internal/game/plinko_test.go
@@ -24,6 +24,11 @@ func TestPlinkoCommands(t *testing.T) {
t.Fatalf("commands failed: %+v", snap)
}
g.executeBet(s, []string{"1"}, "bet 1")
+ for _, removed := range []string{"Welcome to PLINKO", "*** PLINKO", "=== P L I N K O", "White = one ball", "Matching letters share", "(base "} {
+ if strings.Contains(string(conn.data), removed) {
+ t.Fatalf("obsolete prose still printed: %q", removed)
+ }
+ }
if !table.Busy(s.Player.Name) || len(s.Player.PendingWagers) != 1 {
t.Fatal("drop did not start")
}
@@ -35,12 +40,15 @@ func TestPlinkoCommands(t *testing.T) {
t.Fatal("drop did not settle")
}
text := stripANSI(string(conn.data))
- if !strings.Contains(text, "Ball 10 ->") || strings.Count(text, "Returned:") != 1 || strings.Contains(text, "credits returned") || strings.Contains(text, "net +") {
+ if strings.Contains(text, "->") || strings.Count(text, "Returned:") != 1 || strings.Contains(text, "credits returned") || strings.Contains(text, "net +") {
t.Fatal(text)
}
+ if !strings.Contains(text, "Wager") || !strings.Contains(text, "Staked") {
+ t.Fatalf("settlement receipt missing: %s", text)
+ }
conn.data = nil
g.doLookTarget(s, "plinko")
- if !strings.Contains(stripANSI(string(conn.data)), "P L I N K O") {
+ if !strings.Contains(stripANSI(string(conn.data)), "% RTP:") {
t.Fatal("look missing paytable")
}
}
@@ -75,16 +83,27 @@ func TestPlinkoRenderRows(t *testing.T) {
}
}
wantLines := 1
- if row == plinkoRows/2 || row == plinkoRows {
- wantLines = 2 // midpoint hint ruler, base rail
+ if row == plinkoRows {
+ wantLines = 2 // base rail
}
if got := len(strings.Split(out, "\n")); got != wantLines {
t.Fatalf("row %d printed %d lines, want %d", row, got, wantLines)
}
- if strings.Contains(out, "\x1b[48;") {
- t.Fatal("the board must not paint a background")
+ switch mode {
+ case "none":
+ if strings.Contains(out, "\x1b") {
+ t.Fatal("plain board must not contain escapes")
+ }
+ case "ansi", "xterm256":
+ // The board is background-free: the player's terminal theme
+ // shows through instead of a fixed gradient.
+ for bg := 0; bg < 256; bg++ {
+ if strings.Contains(out, color.BgCode(mode, bg)) {
+ t.Fatalf("%s board must not paint a background", mode)
+ }
+ }
}
- if mode == "none" && (!strings.Contains(out, "*") || strings.Contains(out, "\x1b")) {
+ if mode == "none" && !strings.Contains(out, "*") {
t.Fatal("plain overlap fallback missing")
}
}
@@ -102,7 +121,7 @@ func TestPlinkoUsesSharedHints(t *testing.T) {
if strings.Contains(text, "difficulty easy|medium|hard|expert") != (mode != "off") {
t.Fatalf("join hints (%s): %s", mode, text)
}
- if mode != "off" && strings.Index(text, "P L I N K O") > strings.Index(text, "bet N") {
+ if mode != "off" && strings.Index(text, "% RTP:") > strings.Index(text, "bet N") {
t.Fatal("hints must follow the paytable")
}
conn.data = nil
@@ -134,15 +153,17 @@ func plinkoTestBoardRow(s *casino.PlinkoSnapshot) []rune {
}
func TestPlinkoPegsNeverMove(t *testing.T) {
+ pockets := [17]float64{16, 7.8, 5.9, 4.4, 3.1, 2, 1.1, 0.5, 0.2, 0.5, 1.1, 2, 3.1, 4.4, 5.9, 7.8, 16}
for row := 0; row <= 16; row++ {
- s := &casino.PlinkoSnapshot{Row: row, Total: 1}
+ s := &casino.PlinkoSnapshot{Row: row, Total: 1, Pockets: pockets}
empty := plinkoTestBoardRow(s)
left, right := 32-2*row, 36+2*row
pegs := map[int]bool{}
for k := 1; k <= row; k++ {
- pegs[left+4*k] = true
- if empty[left+4*k] != '·' {
- t.Fatalf("row %d: peg missing at its true position %d", row, left+4*k)
+ x := left + 4*k
+ pegs[x] = true
+ if empty[x] != '·' {
+ t.Fatalf("row %d: peg missing at its true position %d", row, x)
}
}
if right < plinkoWidth && empty[right] != '\\' || empty[left] != '/' {
@@ -165,35 +186,28 @@ func TestPlinkoPegsNeverMove(t *testing.T) {
}
}
-func TestPlinkoHintsPrintOnceAtMidpoint(t *testing.T) {
+func TestPlinkoHintsOverlayReminderRow(t *testing.T) {
pockets := [17]float64{16, 7.8, 5.9, 4.4, 3.1, 2, 1.1, 0.5, 0.2, 0.5, 1.1, 2, 3.1, 4.4, 5.9, 7.8, 16}
labels := casino.PlinkoPocketLabels(pockets)
- isRuler := func(line string) bool {
- r := []rune(line)
- if len(r) != plinkoWidth || r[1] != ' ' {
- return false
- }
- for k, label := range labels {
- if r[2+4*k] != label {
- return false
- }
- }
- return true
- }
for row := 0; row <= 16; row++ {
s := &casino.PlinkoSnapshot{Row: row, Total: 1, Pockets: pockets}
- hasRuler := false
- for _, line := range strings.Split(stripANSI(renderPlinko(s, "none", true)), "\n") {
- hasRuler = hasRuler || isRuler(line)
+ lines := strings.Split(stripANSI(renderPlinko(s, "none", true)), "\n")
+ wantLines := 1
+ if row == plinkoRows {
+ wantLines = 2
}
- if hasRuler != (row == plinkoRows/2) {
- t.Fatalf("row %d: hint ruler present=%v", row, hasRuler)
+ if len(lines) != wantLines {
+ t.Fatalf("row %d printed %d lines, want %d", row, len(lines), wantLines)
}
- // The pyramid row itself never carries pocket letters any more.
- board := plinkoTestBoardRow(s)
+ board := []rune(lines[0])
for k, label := range labels {
- if board[2+4*k] == label {
- t.Fatalf("row %d: pocket hint leaked into the board", row)
+ x := 2 + 4*k
+ if row == plinkoHintRow {
+ if board[x] != label {
+ t.Fatalf("row %d: reminder %c missing at pocket column %d (got %q)", row, label, x, board[x])
+ }
+ } else if board[x] == label {
+ t.Fatalf("row %d: reminder %c leaked into the board at %d", row, label, x)
}
}
}
@@ -225,11 +239,11 @@ func TestPlinkoLegendDownTheLeft(t *testing.T) {
if left := 32 - 2*s.Row; left <= 10 {
t.Fatalf("legend would collide with the pyramid: left border %d", left)
}
- // The paytable prints the same entries, one per line, not a wide block.
+ // The standalone paytable prints the same entries on one line.
s.Row = 0
paytable := stripANSI(renderPlinko(&casino.PlinkoSnapshot{Pockets: s.Pockets, Risk: "easy", Paytable: true}, "none", true))
for _, e := range entries {
- if strings.Count(paytable, fmt.Sprintf("%c %s", e.label, e.text)) != 1 {
+ if strings.Count(paytable, fmt.Sprintf("%c%s", e.label, e.text)) != 1 {
t.Fatalf("paytable legend missing or duplicated %c", e.label)
}
}
@@ -246,16 +260,42 @@ func TestPlinkoLegendDownTheLeft(t *testing.T) {
}
}
-func TestPlinkoHintsAreDimWhite(t *testing.T) {
- r, g, b := color.Xterm256ToRGB(plinkoHintColor())
- if r != g || g != b {
- t.Fatalf("hint is not white: %d,%d,%d", r, g, b)
+func TestPlinkoPaytableIsCompact(t *testing.T) {
+ s := &casino.PlinkoSnapshot{
+ Paytable: true, Risk: "easy", Total: 1, RTP: 97.9998779296875,
+ Pockets: [17]float64{16, 7.8, 5.8, 4.3, 3, 2, 1.1, 0.4, 0.2, 0.4, 1.1, 2, 3, 4.3, 5.8, 7.8, 16},
}
- if r > 95 {
- t.Fatalf("hint is too bright: %d", r)
+ want := "easy 1 ball 98.00% RTP: A16x B7.8x C5.8x D4.3x E3x F2x G1.1x H0.4x I0.2x"
+ for _, mode := range []string{"none", "ansi", "xterm256"} {
+ out := renderPlinko(s, mode, true)
+ if plain := stripANSI(out); plain != want {
+ t.Fatalf("%s paytable: %q", mode, plain)
+ }
+ if mode != "none" {
+ for bg := 0; bg < 256; bg++ {
+ if strings.Contains(out, color.BgCode(mode, bg)) {
+ t.Fatalf("%s paytable must not paint a background", mode)
+ }
+ }
+ }
}
- br, bg, bb := color.Xterm256ToRGB(plinkoBallColor(1, 1))
- if r >= br || g >= bg || b >= bb {
- t.Fatal("hint must be dimmer than a ball")
+}
+
+func TestPlinkoLandingPockets(t *testing.T) {
+ s := &casino.PlinkoSnapshot{Row: plinkoRows, Total: 3}
+ s.Pockets[0], s.Pockets[16] = 16, 16
+ s.Counts[0], s.Counts[8] = 1, 2
+ for _, mode := range []string{"none", "ansi", "xterm256"} {
+ rail := strings.Split(renderPlinko(s, mode, true), "\n")[1]
+ plain := []rune(stripANSI(rail))
+ for k := range s.Counts {
+ left, right := '[', ']'
+ if s.Counts[k] > 0 {
+ left, right = '>', '<'
+ }
+ if plain[1+4*k] != left || plain[3+4*k] != right {
+ t.Fatalf("%s: incorrect landing marker at pocket %d: %s", mode, k, string(plain))
+ }
+ }
}
}