From 82e571d227feeafd0af4011e701ce26b99c5e3d9 Mon Sep 17 00:00:00 2001 From: historia <[not public]> Date: Sat, 3 Oct 2026 00:51:41 -0400 Subject: fix: craps felt and wager description cleanup --- internal/game/render_craps.go | 113 ++++++++++++++++++++++++++++-------------- 1 file changed, 76 insertions(+), 37 deletions(-) (limited to 'internal/game/render_craps.go') diff --git a/internal/game/render_craps.go b/internal/game/render_craps.go index ce9825f..e4e6618 100644 --- a/internal/game/render_craps.go +++ b/internal/game/render_craps.go @@ -21,17 +21,23 @@ const ( crapsPuckOn crapsPuckOff crapsBonus + crapsGoldInk + crapsBonusInk + crapsBonusDim ) var crapsPaint = []color.ColorSpec{ {Fg: 15, Bg: crapsFelt}, {Fg: 229, Bg: crapsFelt, Bold: true}, - {Fg: 217, Bg: crapsFelt, Bold: true}, + {Fg: 160, Bg: crapsFelt, Bold: true}, // deep red for come/pass/props {Fg: 108, Bg: crapsFelt}, {Fg: 0, Bg: 220, Bold: true}, {Fg: 0, Bg: 15, Bold: true}, {Fg: 15, Bg: 0, Bold: true}, {Fg: 229, Bg: 24, Bold: true}, // Fire/ATS tracker: yellow on blue + {Fg: 229, Bg: crapsFelt}, // regular-weight gold, used for field notes + {Fg: 229, Bg: 24}, // regular-weight tracker text and brackets + {Fg: 108, Bg: 24}, // dim tracker odds, matching the place/lay payouts } type crapsCell struct { @@ -93,6 +99,26 @@ 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) } +// crapsSpan is one color run inside a centered line. centerSpans lays the runs +// end to end and centers their combined width, so a label can stay regular +// weight while its payout is dim, or a heading can pair two inks on one row. +type crapsSpan struct { + text string + ink int +} + +func (c *crapsCanvas) centerSpans(x1, x2, y int, spans ...crapsSpan) { + total := 0 + for _, s := range spans { + total += color.VisibleLen(s.text) + } + x := x1 + (x2-x1+1-total)/2 + for _, s := range spans { + c.text(x, y, s.text, s.ink) + x += color.VisibleLen(s.text) + } +} + // 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: '┼'} @@ -191,17 +217,15 @@ func renderCrapsTrackers(s *casino.CrapsSnapshot, mode string, unicode bool) str c.box(38, 4, 76, 6) c.text(3, 1, "FIRE", crapsBonus) for i, n := range []int{4, 5, 6, 8, 9, 10} { - ink := crapsBonus - if crapsHas(s.Made, n) { - ink = crapsMarked - } - c.text(15+i*9, 1, fmt.Sprintf("[%2d]", n), ink) + crapsTrackerPoint(c, 15+i*9, 1, n, crapsHas(s.Made, n)) } 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, 4) - c.center(lo+1, hi-1, 3, fmt.Sprintf("%d POINTS %s", i+4, payout), crapsBonus) + c.centerSpans(lo+1, hi-1, 3, + crapsSpan{fmt.Sprintf("%d POINTS", i+4), crapsBonusInk}, + crapsSpan{" " + payout, crapsBonusDim}) } c.text(2, 5, "SMALL", crapsBonus) c.text(40, 5, "TALL", crapsBonus) @@ -210,16 +234,27 @@ func renderCrapsTrackers(s *casino.CrapsSnapshot, mode string, unicode bool) str if i >= 5 { x = 48 + (i-5)*5 } - ink := crapsBonus - if crapsHas(s.Seen, n) { - ink = crapsMarked - } - c.text(x, 5, fmt.Sprintf("[%2d]", n), ink) + crapsTrackerPoint(c, x, 5, n, crapsHas(s.Seen, n)) } return c.render(mode, unicode) } + +// crapsTrackerPoint draws one bracketed total for the Fire/ATS tracker. The +// brackets stay regular weight while the number itself is bold; a made or +// seen point keeps its solid highlight across the whole token. +func crapsTrackerPoint(c *crapsCanvas, x, y, n int, marked bool) { + if marked { + c.text(x, y, fmt.Sprintf("[%2d]", n), crapsMarked) + return + } + c.text(x, y, "[", crapsBonusInk) + c.text(x+1, y, fmt.Sprintf("%2d", n), crapsBonus) + c.text(x+3, y, "]", crapsBonusInk) +} func renderCrapsFelt(s *casino.CrapsSnapshot, mode string, unicode bool) string { - c := newCrapsCanvas(26) + // Compact half-table: every rail keeps its label and value on adjacent + // rows, so the felt is five rows shorter than the spaced-out layout. + c := newCrapsCanvas(21) c.box(0, 0, 14, 6) c.center(1, 13, 4, "DON'T COME", crapsInk) c.center(1, 13, 5, "BAR 12", crapsMuted) @@ -241,9 +276,9 @@ func renderCrapsFelt(s *casino.CrapsSnapshot, mode string, unicode bool) string c.box(0, 6, 50, 10) c.center(1, 49, 7, "PLACE / BUY / LAY ON THE NUMBERS ABOVE", crapsMuted) c.center(1, 49, 9, "C O M E", crapsRed) - c.box(0, 10, 50, 16) - c.center(1, 49, 11, "FIELD", crapsGold) - c.center(1, 49, 13, "( 2 ) 3 4 9 10 11 ( 12 )", crapsInk) + c.box(0, 10, 50, 14) + c.center(1, 49, 11, "F I E L D", crapsGold) + c.center(1, 49, 12, "( 2 ) 3 4 9 10 11 ( 12 )", crapsInk) twelve := s.Field12 if twelve == 0 { twelve = 2 @@ -252,13 +287,17 @@ func renderCrapsFelt(s *casino.CrapsSnapshot, mode string, unicode bool) string if twelve == 3 { twelvePay = "TRIPLE" } - c.center(1, 49, 15, fmt.Sprintf("2 PAYS DOUBLE 12 PAYS %s", twelvePay), crapsGold) - c.box(0, 16, 50, 19) - c.center(1, 49, 18, "DON'T PASS / BAR 12", crapsInk) - c.box(0, 19, 50, 22) - c.center(1, 49, 21, "PASS LINE", crapsGold) - c.box(0, 22, 50, 25) - c.center(1, 49, 24, "FREE ODDS / BEHIND THE LINE", crapsInk) + c.center(1, 49, 13, fmt.Sprintf("2 PAYS DOUBLE 12 PAYS %s", twelvePay), crapsGoldInk) + c.box(0, 14, 50, 16) + c.centerSpans(1, 49, 15, crapsSpan{"DON'T PASS", crapsInk}, crapsSpan{" BAR 12", crapsMuted}) + c.box(0, 16, 50, 18) + c.center(1, 49, 17, "PASS LINE", crapsRed) + c.box(0, 18, 50, 20) + odds := s.Odds + if odds == "" { + odds = "3x-4x-5x" + } + c.centerSpans(1, 49, 19, crapsSpan{"FREE ODDS", crapsInk}, crapsSpan{" (" + odds + ")", crapsMuted}) // Centre action: four bonus spots, four hardways, six propositions, horn/hop. // 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"}} @@ -269,11 +308,11 @@ func renderCrapsFelt(s *casino.CrapsSnapshot, mode string, unicode bool) string 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) + c.center(xs[i]+1, xs[i+1]-1, 4, spot.pays, crapsBonusDim) } for i, n := range []int{4, 6, 8, 10} { - x, y := 50+(i%2)*13, 5+(i/2)*4 - c.box(x, y, x+13, y+4) + x, y := 50+(i%2)*13, 5+(i/2)*3 + c.box(x, y, x+13, y+3) c.center(x+1, x+12, y+1, fmt.Sprintf("HARD %d", n), crapsInk) pay := 7 if n == 6 || n == 8 { @@ -283,29 +322,29 @@ func renderCrapsFelt(s *casino.CrapsSnapshot, mode string, unicode bool) string if unicode { pair = crapsDie(n/2) + "+" + crapsDie(n/2) } - c.center(x+1, x+12, y+3, fmt.Sprintf("%s %d:1", pair, pay), crapsGold) + c.centerSpans(x+1, x+12, y+2, crapsSpan{pair + " ", crapsGold}, crapsSpan{fmt.Sprintf("%d:1", pay), crapsMuted}) } props := []string{"SEVEN", "CRAPS", "ELEVEN", "ACES", "ACE 3", "BOXCARS"} - odds := []string{"4:1", "7:1", "15:1", "30:1", "15:1", "30:1"} + oddsRows := []string{"4:1", "7:1", "15:1", "30:1", "15:1", "30:1"} px := []int{50, 59, 67, 76} for i, label := range props { - col, y := i%3, 13+(i/3)*4 + col, y := i%3, 11+(i/3)*3 x, x2 := px[col], px[col+1] - c.box(x, y, x2, y+4) + c.box(x, y, x2, y+3) c.center(x+1, x2-1, y+1, label, crapsRed) - c.center(x+1, x2-1, y+3, odds[i], crapsInk) + c.center(x+1, x2-1, y+2, oddsRows[i], crapsMuted) } - c.box(50, 21, 63, 25) - c.center(51, 62, 22, "HORN", crapsGold) + c.box(50, 17, 63, 20) + c.center(51, 62, 18, "HORN", crapsGold) horn := "2 3 11 12" if unicode { // Horn combinations as painted dice pairs: 2, 3, 11, 12. horn = strings.Join([]string{crapsDie(1) + crapsDie(1), crapsDie(1) + crapsDie(2), crapsDie(5) + crapsDie(6), crapsDie(6) + crapsDie(6)}, " ") } - c.center(51, 62, 24, horn, crapsMuted) - c.box(63, 21, 76, 25) - c.center(64, 75, 22, "HOP", crapsGold) - c.center(64, 75, 24, "15:1 / 30:1", crapsMuted) + c.center(51, 62, 19, horn, crapsMuted) + c.box(63, 17, 76, 20) + c.center(64, 75, 18, "HOP", crapsGold) + c.center(64, 75, 19, "15:1 / 30:1", crapsMuted) return c.render(mode, unicode) } -- cgit v1.2.3