diff options
Diffstat (limited to 'internal/game')
| -rw-r--r-- | internal/game/casino.go | 7 | ||||
| -rw-r--r-- | internal/game/casino_ux.go | 12 | ||||
| -rw-r--r-- | internal/game/casino_ux_test.go | 15 | ||||
| -rw-r--r-- | internal/game/craps_test.go | 2 | ||||
| -rw-r--r-- | internal/game/craps_variants_test.go | 6 | ||||
| -rw-r--r-- | internal/game/render_craps.go | 203 | ||||
| -rw-r--r-- | internal/game/render_craps_dice_test.go | 82 | ||||
| -rw-r--r-- | internal/game/render_craps_test.go | 58 |
8 files changed, 266 insertions, 119 deletions
diff --git a/internal/game/casino.go b/internal/game/casino.go index 21f9627..dc9f8f0 100644 --- a/internal/game/casino.go +++ b/internal/game/casino.go @@ -6,7 +6,6 @@ import ( "os" "strconv" "strings" - "time" "unicode/utf8" "thehouseoficarus/internal/cardart" @@ -542,11 +541,7 @@ func (g *Game) executeTableAction(sess *net.Session, action string) { func (g *Game) CasinoTick() { if g.Casino != nil { - ms := 600 - if g.Ticks != nil { - ms = g.Ticks.TickLengthMilliseconds() - } - g.emitCasinoEvents(g.Casino.TickElapsed(time.Duration(ms) * time.Millisecond)) + g.emitCasinoEvents(g.Casino.Tick()) } } diff --git a/internal/game/casino_ux.go b/internal/game/casino_ux.go index d65cfb9..2650d47 100644 --- a/internal/game/casino_ux.go +++ b/internal/game/casino_ux.go @@ -260,16 +260,10 @@ func (g *Game) seesCasinoDetails(sess *net.Session, event casino.Event) bool { var casinoTicksPattern = regexp.MustCompile(`([0-9]+) ticks`) -// Casino logic counts engine ticks; present deadlines in actual configured seconds. +// Casino logic counts engine ticks; deadlines are presented in ticks too, so +// the configured tick cadence never rewrites a deadline the player can count. func (g *Game) casinoTimeText(text string) string { - ms := 600 - if g.Ticks != nil { - ms = g.Ticks.TickLengthMilliseconds() - } - return casinoTicksPattern.ReplaceAllStringFunc(text, func(s string) string { - n, _ := strconv.Atoi(strings.Fields(s)[0]) - return fmt.Sprintf("%g seconds", float64(n)*float64(ms)/1000) - }) + return casinoTicksPattern.ReplaceAllString(text, "${1}t") } // betRangeWords renders the wager range in words for one-line hints. diff --git a/internal/game/casino_ux_test.go b/internal/game/casino_ux_test.go index 1a06f89..c83db27 100644 --- a/internal/game/casino_ux_test.go +++ b/internal/game/casino_ux_test.go @@ -228,3 +228,18 @@ func TestRebetRepeatsWager(t *testing.T) { t.Fatalf("rebet did not target banker: %q", stripANSI(string(conn.data))) } } + +// TestCasinoTimeTextUsesTicks pins the presentation unit: authored "N ticks" +// deadlines display as "Nt" rather than being rewritten into seconds. +func TestCasinoTimeTextUsesTicks(t *testing.T) { + g := &Game{} + for in, want := range map[string]string{ + "Your turn | 25 ticks remaining": "Your turn | 25t remaining", + "Betting closes in 5 ticks.": "Betting closes in 5t.", + "no deadline here": "no deadline here", + } { + if got := g.casinoTimeText(in); got != want { + t.Fatalf("casinoTimeText(%q) = %q, want %q", in, got, want) + } + } +} diff --git a/internal/game/craps_test.go b/internal/game/craps_test.go index 27610bd..6712455 100644 --- a/internal/game/craps_test.go +++ b/internal/game/craps_test.go @@ -43,7 +43,7 @@ func TestCrapsCommandsAndRendering(t *testing.T) { for _, unicode := range []bool{false, true} { art := renderCrapsSnapshot(snap, mode, unicode) plain := plainRoulette(art) - for _, word := range []string{"PASS LINE", "DON'T COME", "FIELD", "FIRE", "ALL SMALL", "Total: 7", "POINT 6"} { + for _, word := range []string{"PASS LINE", "DON'T COME", "FIELD", "FIRE", "SMALL", "Total: 7", "POINT 6"} { if !strings.Contains(plain, word) { t.Fatal("missing", word) } diff --git a/internal/game/craps_variants_test.go b/internal/game/craps_variants_test.go index 46f5be6..c538caa 100644 --- a/internal/game/craps_variants_test.go +++ b/internal/game/craps_variants_test.go @@ -34,7 +34,7 @@ func TestCrapsVariantRendering(t *testing.T) { t.Fatal("dice rendered instead of cards") } } else { - if strings.Contains(plain, "FIRE") || strings.Contains(plain, "ALL SMALL") { + if strings.Contains(plain, "FIRE") || strings.Contains(plain, "SMALL") { t.Fatal(v, "unsupported bonus display") } } @@ -59,8 +59,8 @@ func TestCrapsVariantRendering(t *testing.T) { } for n := 1; n <= 6; n++ { text := renderCrapsDice([2]int{n, 0}, "none", false) - if strings.Count(text, "+-------+") != 2 { - t.Fatal("single die needs a top and bottom outline", text) + if strings.Count(text, "\n") != 5 { + t.Fatal("single die needs three pip rows and two caps", text) } if got := strings.Count(strings.ReplaceAll(text, "Total:", ""), "o"); got != n { t.Fatalf("single die %d rendered %d pips", n, got) diff --git a/internal/game/render_craps.go b/internal/game/render_craps.go index 7ec015a..ce9825f 100644 --- a/internal/game/render_craps.go +++ b/internal/game/render_craps.go @@ -20,8 +20,7 @@ const ( crapsMarked crapsPuckOn crapsPuckOff - crapsFire - crapsAts + crapsBonus ) var crapsPaint = []color.ColorSpec{ @@ -32,8 +31,7 @@ var crapsPaint = []color.ColorSpec{ {Fg: 0, Bg: 220, Bold: true}, {Fg: 0, Bg: 15, Bold: true}, {Fg: 15, Bg: 0, Bold: true}, - {Fg: 229, Bg: 124, Bold: true}, // fire tracker: yellow on red - {Fg: 229, Bg: 24, Bold: true}, // ATS tracker: yellow on blue + {Fg: 229, Bg: 24, Bold: true}, // Fire/ATS tracker: yellow on blue } type crapsCell struct { @@ -94,8 +92,39 @@ func (c *crapsCanvas) text(x, y int, s string, ink int) { func (c *crapsCanvas) center(x1, x2, y int, s string, ink int) { c.text(x1+(x2-x1+1-color.VisibleLen(s))/2, y, s, ink) } + +// crapsJoints maps a union of box edges (N=1, E=2, S=4, W=8) to its glyph. +var crapsJoints = map[uint8]rune{3: '└', 6: '┌', 9: '┘', 12: '┐', 7: '├', 11: '┴', 13: '┤', 14: '┬', 15: '┼'} + +// crapsEdgeRune is the box glyph for a cell's edges, downgraded to ASCII when +// unicode is off. +func crapsEdgeRune(cell crapsCell, unicode bool) rune { + if cell.edges == 0 { + return ' ' + } + var r rune + switch { + case cell.edges&5 == 0: + r = '─' + case cell.edges&10 == 0: + r = '│' + default: + r = crapsJoints[cell.edges] + } + if !unicode { + switch r { + case '─': + r = '-' + case '│': + r = '|' + default: + r = '+' + } + } + return r +} + func (c *crapsCanvas) render(mode string, unicode bool) string { - joints := map[uint8]rune{3: '└', 6: '┌', 9: '┘', 12: '┐', 7: '├', 11: '┴', 13: '┤', 14: '┬', 15: '┼'} var out strings.Builder for _, row := range c.cells { var run strings.Builder @@ -113,27 +142,7 @@ func (c *crapsCanvas) render(mode string, unicode bool) string { } r := cell.glyph if r == 0 { - r = ' ' - if cell.edges != 0 { - switch { - case cell.edges&5 == 0: - r = '─' - case cell.edges&10 == 0: - r = '│' - default: - r = joints[cell.edges] - } - if !unicode { - switch r { - case '─': - r = '-' - case '│': - r = '|' - default: - r = '+' - } - } - } + r = crapsEdgeRune(cell, unicode) } run.WriteRune(r) } @@ -174,48 +183,38 @@ func crapsDrawPuck(c *crapsCanvas, x, y int, on, unicode bool) { c.text(x, y+2, d, ink) } func renderCrapsTrackers(s *casino.CrapsSnapshot, mode string, unicode bool) string { - c := newCrapsCanvas(12) - // Section backgrounds first: fire red, ATS blue, yellow lettering on top. - c.fill(0, 0, 76, 5, crapsFire) - c.fill(0, 5, 76, 11, crapsAts) + c := newCrapsCanvas(7) + // The whole Fire/ATS tracker sits on one blue field. + c.fill(0, 0, 76, 6, crapsBonus) c.box(0, 0, 76, 2) - c.box(0, 5, 38, 8) - c.box(38, 5, 76, 8) - c.text(3, 1, "FIRE", crapsFire) + c.box(0, 4, 38, 6) + c.box(38, 4, 76, 6) + c.text(3, 1, "FIRE", crapsBonus) for i, n := range []int{4, 5, 6, 8, 9, 10} { - ink, mark := crapsFire, " " + ink := crapsBonus if crapsHas(s.Made, n) { - ink, mark = crapsMarked, "*" + ink = crapsMarked } - c.text(15+i*9, 1, fmt.Sprintf("[%2d%s]", n, mark), ink) + c.text(15+i*9, 1, fmt.Sprintf("[%2d]", n), ink) } bounds := []int{0, 25, 50, 76} for i, payout := range []string{"24:1", "249:1", "999:1"} { lo, hi := bounds[i], bounds[i+1] - c.box(lo, 2, hi, 5) - c.center(lo+1, hi-1, 3, fmt.Sprintf("%d DISTINCT POINTS", i+4), crapsFire) - c.center(lo+1, hi-1, 4, payout, crapsFire) + c.box(lo, 2, hi, 4) + c.center(lo+1, hi-1, 3, fmt.Sprintf("%d POINTS %s", i+4, payout), crapsBonus) } - c.text(2, 6, "ALL SMALL", crapsAts) - c.text(40, 6, "TALL", crapsAts) + c.text(2, 5, "SMALL", crapsBonus) + c.text(40, 5, "TALL", crapsBonus) for i, n := range []int{2, 3, 4, 5, 6, 8, 9, 10, 11, 12} { x := 12 + i*5 if i >= 5 { x = 48 + (i-5)*5 } - ink, mark := crapsAts, " " + ink := crapsBonus if crapsHas(s.Seen, n) { - ink, mark = crapsMarked, "*" + ink = crapsMarked } - c.text(x, 6, fmt.Sprintf("%2d%s ", n, mark), ink) - } - c.center(1, 37, 7, "* = rolled", crapsAts) - c.center(39, 75, 7, "* = rolled", crapsAts) - for i, row := range [][2]string{{"SMALL", "34:1"}, {"ALL", "175:1"}, {"TALL", "34:1"}} { - lo, hi := bounds[i], bounds[i+1] - c.box(lo, 8, hi, 11) - c.center(lo+1, hi-1, 9, row[0], crapsAts) - c.center(lo+1, hi-1, 10, row[1], crapsAts) + c.text(x, 5, fmt.Sprintf("[%2d]", n), ink) } return c.render(mode, unicode) } @@ -261,14 +260,16 @@ func renderCrapsFelt(s *casino.CrapsSnapshot, mode string, unicode bool) string c.box(0, 22, 50, 25) c.center(1, 49, 24, "FREE ODDS / BEHIND THE LINE", crapsInk) // Centre action: four bonus spots, four hardways, six propositions, horn/hop. - bonus := []struct { - label, pays string - }{{"FIRE", "24x"}, {"SMALL", "34:1"}, {"ALL", "175:1"}, {"TALL", "34:1"}} + // The Fire and ATS spots share one blue field. + bonus := []struct{ label, pays string }{{"FIRE", "24x"}, {"SMALL", "34:1"}, {"ALL", "175:1"}, {"TALL", "34:1"}} xs := []int{50, 56, 63, 69, 76} - for i, spot := range bonus { + for i := range bonus { c.box(xs[i], 0, xs[i+1], 5) - c.center(xs[i]+1, xs[i+1]-1, 2, spot.label, crapsGold) - c.center(xs[i]+1, xs[i+1]-1, 4, spot.pays, crapsMuted) + } + c.fill(50, 0, 76, 5, crapsBonus) + for i, spot := range bonus { + c.center(xs[i]+1, xs[i+1]-1, 2, spot.label, crapsBonus) + c.center(xs[i]+1, xs[i+1]-1, 4, spot.pays, crapsBonus) } for i, n := range []int{4, 6, 8, 10} { x, y := 50+(i%2)*13, 5+(i/2)*4 @@ -315,12 +316,12 @@ func renderHighPointFelt(s *casino.CrapsSnapshot, mode string, unicode bool) str c.box(0, 0, 76, 15) c.center(1, 75, 1, "HIGH POINT CRAPS", crapsGold) c.center(1, 75, 2, "COME-OUT 2 OR 3: IGNORED | 11 OR 12: WINS", crapsInk) - nums := []int{4, 5, 6, 8, 9, 10} - labels := []string{"4", "5", "SIX", "8", "NINE", "10"} + nums := []int{4, 5, 6, 7, 8, 9, 10} + labels := []string{"4", "5", "SIX", "7", "8", "NINE", "10"} for i, n := range nums { - x := 2 + i*12 - c.box(x, 5, x+11, 10) - c.center(x+1, x+10, 9, labels[i], crapsGold) + x := 3 + i*10 + c.box(x, 5, x+10, 10) + c.center(x+1, x+9, 9, labels[i], crapsGold) if s.Point == n { crapsDrawPuck(c, x+3, 6, true, unicode) } @@ -392,47 +393,59 @@ func renderDieRichFelt(s *casino.CrapsSnapshot, mode string, unicode bool) strin return c.render(mode, unicode) } -// renderCrapsDice paints each face as a white tile with black pips. The tile -// is five interior rows tall (three pip rows plus white padding top and -// bottom) inside a box outline, so the face reads as a square die. +// renderCrapsDice uses half-block caps and white backgrounds to make a +// square tile in a terminal's roughly 1:2 character cells. Pips are inset; +// monochrome clients get an outlined face rather than disconnected dots. func renderCrapsDice(dice [2]int, mode string, unicode bool) string { if dice[0] < 1 || dice[0] > 6 || dice[1] < 0 || dice[1] > 6 { return "" } - patterns := [][3]string{{}, {" ", " o ", " "}, {"o ", " ", " o"}, {"o ", " o ", " o"}, {"o o", " ", "o o"}, {"o o", " o ", "o o"}, {"o o", "o o", "o o"}} - pip := "o" - if unicode { - pip = "●" - } - top, bottom, v := "+-------+", "+-------+", "|" - if unicode { - top, bottom, v = "╭───────╮", "╰───────╯", "│" - } - spec := color.ColorSpec{Fg: 0, Bg: 15, Bold: true} + patterns := [7][3]string{ + {}, + {" ", " ● ", " "}, + {" ● ", " ", " ● "}, + {" ● ", " ● ", " ● "}, + {" ● ● ", " ", " ● ● "}, + {" ● ● ", " ● ", " ● ● "}, + {" ● ● ", " ● ● ", " ● ● "}, + } + colored := mode != "" && mode != "none" + face := color.ColorSpec{Fg: 0, Bg: 15} + cap := color.ColorSpec{Fg: 15, Bg: -1} var out strings.Builder - for row := 0; row < 7; row++ { + for row := 0; row < 5; row++ { out.WriteString(" ") for i, n := range dice { if n == 0 { continue } - var line string - switch row { - case 0: - line = top - case 6: - line = bottom - case 1, 5: - line = v + " " + v - default: - line = v + " " + strings.ReplaceAll(patterns[n][row-2], "o", pip) + " " + v - } - out.WriteString(color.Render(mode, spec, line)) - if i == 0 { + if i > 0 { out.WriteString(" ") } + line := "" + spec := face + if row == 0 || row == 4 { + line = " " + if unicode && colored { + line, spec = "▄▄▄▄▄▄▄", cap + if row == 4 { + line = "▀▀▀▀▀▀▀" + } + } else if !colored { + line = "+-----+" + } + } else { + line = patterns[n][row-1] + if !colored { + line = "|" + strings.TrimPrefix(strings.TrimSuffix(line, " "), " ") + "|" + } + if !unicode { + line = strings.ReplaceAll(line, "●", "o") + } + } + out.WriteString(color.Render(mode, spec, line)) } - if row == 3 { + if row == 2 { out.WriteString(color.Render(mode, color.ColorSpec{Fg: 229, Bg: -1, Bold: true}, fmt.Sprintf(" Total: %d", dice[0]+dice[1]))) } out.WriteByte('\n') @@ -506,16 +519,16 @@ func renderCrapsSnapshot(s *casino.CrapsSnapshot, mode string, unicode bool, sty hint = "" if s.Variant == "simplified" { if s.ShootIn > 0 { - hint = fmt.Sprintf("The dice roll in %ds.", s.ShootIn) + hint = fmt.Sprintf("The dice roll in %dt.", s.ShootIn) } } else if s.ShootIn > 0 { - hint = fmt.Sprintf("Bets open. Shooter ready in %ds.", s.ShootIn) + hint = fmt.Sprintf("Bets open. Shooter ready in %dt.", s.ShootIn) } else if s.DealerIn > 0 { - hint = fmt.Sprintf("Shooter ready. Dealer rolls in %ds; bets still open.", s.DealerIn) + hint = fmt.Sprintf("Shooter ready. Dealer rolls in %dt; bets still open.", s.DealerIn) } } if s.AutoRoll { - hint = fmt.Sprintf("AUTOROLL / Shooter's next roll in %ds", s.AutoIn) + hint = fmt.Sprintf("AUTOROLL / Shooter's next roll in %dt", s.AutoIn) } if hint != "" { out.WriteString(subtle(" "+hint) + "\n") diff --git a/internal/game/render_craps_dice_test.go b/internal/game/render_craps_dice_test.go new file mode 100644 index 0000000..e691fd5 --- /dev/null +++ b/internal/game/render_craps_dice_test.go @@ -0,0 +1,82 @@ +package game + +import ( + "fmt" + "strings" + "testing" + + "thehouseoficarus/internal/casino" + "thehouseoficarus/internal/color" +) + +func TestCrapsDiceTilesAndFallbacks(t *testing.T) { + for _, mode := range []string{"xterm256", "ansi", "none", ""} { + for _, unicode := range []bool{true, false} { + for a := 1; a <= 6; a++ { + for b := 0; b <= 6; b++ { + art := renderCrapsDice([2]int{a, b}, mode, unicode) + plain := plainRoulette(art) + rows := strings.Split(strings.TrimSuffix(plain, "\n"), "\n") + if len(rows) != 5 { + t.Fatal("tile height", rows) + } + width := 9 // two-column indent and one seven-column die + if b != 0 { + width += 10 + } + for i, row := range rows { + if i == 2 { + row = strings.TrimSuffix(row, fmt.Sprintf(" Total: %d", a+b)) + } + if color.VisibleLen(row) != width { + t.Fatalf("uneven dice: %q", plain) + } + } + pip := "●" + if !unicode { + pip = "o" + for _, r := range plain { + if r > 127 { + t.Fatal("Unicode in ASCII fallback") + } + } + } + if strings.Count(strings.ReplaceAll(plain, "Total:", ""), pip) != a+b { + t.Fatal("incorrect pips", a, b, plain) + } + if mode == "none" || mode == "" { + if strings.Contains(art, "\x1b") || !strings.Contains(plain, "+-----+") { + t.Fatal("missing monochrome outline", art) + } + } else { + if !strings.Contains(art, color.BgCode(mode, 15)) || !strings.Contains(art, color.FgCode(mode, 0)) { + t.Fatal("missing black-on-white face", art) + } + if unicode && (!strings.Contains(plain, "▄▄▄▄▄▄▄") || !strings.Contains(plain, "▀▀▀▀▀▀▀")) { + t.Fatal("missing block caps", art) + } + } + } + } + } + } + for _, dice := range [][2]int{{0, 0}, {-1, 2}, {7, 1}, {1, -1}, {1, 7}} { + if renderCrapsDice(dice, "xterm256", true) != "" { + t.Fatal("rendered invalid dice", dice) + } + } +} + +func TestHighPointFeltIncludesSevenPoint(t *testing.T) { + for n := 4; n <= 10; n++ { + art := plainRoulette(renderHighPointFelt(&casino.CrapsSnapshot{Point: n}, "none", true)) + if strings.Count(art, "ON ") != 1 { + t.Fatalf("point %d missing its puck:\n%s", n, art) + } + for _, row := range strings.Split(strings.TrimSuffix(art, "\n"), "\n") { + if color.VisibleLen(row) != crapsCanvasWidth { + t.Fatal("felt width changed") + } + } + } +} diff --git a/internal/game/render_craps_test.go b/internal/game/render_craps_test.go index b3cc1d8..49bda4a 100644 --- a/internal/game/render_craps_test.go +++ b/internal/game/render_craps_test.go @@ -57,22 +57,70 @@ func TestCrapsHalfTableGeometryAndPuck(t *testing.T) { } t.Log("\n" + renderCrapsSnapshot(&casino.CrapsSnapshot{Point: 6, Field12: 3, Dice: [2]int{3, 3}, Queue: []string{"Alice", "Bob"}, Made: []int{4, 5}, Seen: []int{2, 4, 5, 6, 9}, Phase: "betting", ShootIn: 10, DealerIn: 70}, "none", true)) } + +// TestCrapsFireTrackerHighlightsMadePoints pins the Fire half of the tracker: +// made points highlight, un-made points do not, and no asterisk is involved. +func TestCrapsFireTrackerHighlightsMadePoints(t *testing.T) { + s := &casino.CrapsSnapshot{Point: 6, Made: []int{4, 9}} + art := renderCrapsTrackers(s, "xterm256", true) + fireRow := "" + for _, line := range strings.Split(art, "\n") { + if strings.Contains(line, "FIRE") { + fireRow = line + } + } + if got := strings.Count(fireRow, color.BgCode("xterm256", 220)); got != 2 { + t.Fatalf("expected 2 highlighted fire points, got %d: %q", got, plainRoulette(art)) + } + plain := plainRoulette(art) + for _, want := range []string{"[ 4]", "[ 9]"} { + if !strings.Contains(plain, want) { + t.Fatalf("missing fire point %s: %q", want, plain) + } + } + if strings.Contains(plain, "*") { + t.Fatalf("fire tracker still uses an asterisk: %q", plain) + } +} func TestCrapsTrackersAndDiceStyles(t *testing.T) { s := &casino.CrapsSnapshot{Point: 6, Made: []int{4, 6}, Seen: []int{2, 12}} art := renderCrapsTrackers(s, "xterm256", true) plain := plainRoulette(art) - for _, token := range []string{"[ 4*]", "[ 5 ]", "[ 6*]", " 2*", "12*"} { + // Both trackers bracket every number; made/rolled numbers are shown by the + // highlight alone, with no asterisk. + for _, token := range []string{"[ 4]", "[ 5]", "[ 6]", "[ 2]", "[12]"} { if !strings.Contains(plain, token) { t.Fatal("tracker missing", token) } } + if strings.Contains(plain, "*") || strings.Contains(plain, "= rolled") { + t.Fatalf("tracker still uses asterisks or the rolled legend: %q", plain) + } if strings.Count(art, color.BgCode("xterm256", 220)) != 4 { - t.Fatal("wrong number of filled tracker tokens") + t.Fatal("wrong number of highlighted tracker tokens") + } + if !strings.Contains(art, color.BgCode("xterm256", 24)) { + t.Fatal("tracker background is not blue") + } + if strings.Contains(art, color.BgCode("xterm256", 124)) { + t.Fatal("tracker still uses a red background") + } + // The ATS payout panel now lives only on the main felt, and the fire odds + // share a single row. + if strings.Contains(plain, "DISTINCT POINTS") || strings.Contains(plain, "34:1") || strings.Contains(plain, "175:1") { + t.Fatalf("tracker still shows a payout panel: %q", plain) + } + if !strings.Contains(plain, "4 POINTS 24:1") { + t.Fatalf("fire odds not collapsed to one row: %q", plain) + } + feltArt := renderCrapsFelt(s, "xterm256", true) + if !strings.Contains(feltArt, color.BgCode("xterm256", 24)) { + t.Fatal("felt Fire/ATS spots are not blue") } - if !strings.Contains(art, color.BgCode("xterm256", 124)) || !strings.Contains(art, color.BgCode("xterm256", 24)) { - t.Fatal("fire/ATS tracker backgrounds are not red/blue") + if strings.Contains(feltArt, color.BgCode("xterm256", 124)) { + t.Fatal("felt still uses a red bonus background") } - if !strings.Contains(renderCrapsFelt(s, "xterm256", true), color.BgCode("xterm256", 22)) { + if !strings.Contains(feltArt, color.BgCode("xterm256", 22)) { t.Fatal("felt not dark green") } g := &Game{} |
