aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/casino.go7
-rw-r--r--internal/game/casino_ux.go12
-rw-r--r--internal/game/casino_ux_test.go15
-rw-r--r--internal/game/craps_test.go2
-rw-r--r--internal/game/craps_variants_test.go6
-rw-r--r--internal/game/render_craps.go203
-rw-r--r--internal/game/render_craps_dice_test.go82
-rw-r--r--internal/game/render_craps_test.go58
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{}