aboutsummaryrefslogtreecommitdiff
path: root/internal/game/render_craps.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/render_craps.go')
-rw-r--r--internal/game/render_craps.go238
1 files changed, 177 insertions, 61 deletions
diff --git a/internal/game/render_craps.go b/internal/game/render_craps.go
index 80f05ee..7ec015a 100644
--- a/internal/game/render_craps.go
+++ b/internal/game/render_craps.go
@@ -20,6 +20,8 @@ const (
crapsMarked
crapsPuckOn
crapsPuckOff
+ crapsFire
+ crapsAts
)
var crapsPaint = []color.ColorSpec{
@@ -30,6 +32,8 @@ 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
}
type crapsCell struct {
@@ -39,6 +43,21 @@ type crapsCell struct {
}
type crapsCanvas struct{ cells [][]crapsCell }
+// fill paints one rectangular region with an ink without touching glyphs or
+// box edges, so a whole tracker section can share one background colour.
+func (c *crapsCanvas) fill(x1, y1, x2, y2, ink int) {
+ for y := y1; y <= y2; y++ {
+ if y < 0 || y >= len(c.cells) {
+ continue
+ }
+ for x := x1; x <= x2; x++ {
+ if x >= 0 && x < len(c.cells[y]) {
+ c.cells[y][x].ink = ink
+ }
+ }
+ }
+}
+
// crapsCanvasWidth is the fixed felt width every craps render uses.
const crapsCanvasWidth = 77
@@ -138,14 +157,33 @@ func crapsHas(ns []int, n int) bool {
func crapsDie(n int) string {
return string([]rune{'⚀', '⚁', '⚂', '⚃', '⚄', '⚅'}[n-1])
}
+
+// crapsDrawPuck paints the point puck: OFF on the layout, ON in the point's
+// number box. It is shared by the bank and variant felts.
+func crapsDrawPuck(c *crapsCanvas, x, y int, on, unicode bool) {
+ ink, word := crapsPuckOff, "OFF"
+ if on {
+ ink, word = crapsPuckOn, "ON "
+ }
+ a, b, d := "+---+", "|"+word+"|", "+---+"
+ if unicode {
+ a, b, d = "╭───╮", "│"+word+"│", "╰───╯"
+ }
+ c.text(x, y, a, ink)
+ c.text(x, y+1, b, ink)
+ 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.box(0, 0, 76, 2)
c.box(0, 5, 38, 8)
c.box(38, 5, 76, 8)
- c.text(3, 1, "FIRE", crapsGold)
+ c.text(3, 1, "FIRE", crapsFire)
for i, n := range []int{4, 5, 6, 8, 9, 10} {
- ink, mark := crapsMuted, " "
+ ink, mark := crapsFire, " "
if crapsHas(s.Made, n) {
ink, mark = crapsMarked, "*"
}
@@ -155,29 +193,29 @@ func renderCrapsTrackers(s *casino.CrapsSnapshot, mode string, unicode bool) str
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), crapsGold)
- c.center(lo+1, hi-1, 4, payout, crapsInk)
+ 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.text(2, 6, "ALL SMALL", crapsGold)
- c.text(40, 6, "TALL", crapsGold)
+ c.text(2, 6, "ALL SMALL", crapsAts)
+ c.text(40, 6, "TALL", crapsAts)
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 := crapsMuted, " "
+ ink, mark := crapsAts, " "
if crapsHas(s.Seen, n) {
ink, mark = crapsMarked, "*"
}
c.text(x, 6, fmt.Sprintf("%2d%s ", n, mark), ink)
}
- c.center(1, 37, 7, "* = rolled", crapsMuted)
- c.center(39, 75, 7, "* = rolled", crapsMuted)
+ 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], crapsGold)
- c.center(lo+1, hi-1, 10, row[1], crapsInk)
+ c.center(lo+1, hi-1, 9, row[0], crapsAts)
+ c.center(lo+1, hi-1, 10, row[1], crapsAts)
}
return c.render(mode, unicode)
}
@@ -187,31 +225,18 @@ func renderCrapsFelt(s *casino.CrapsSnapshot, mode string, unicode bool) string
c.center(1, 13, 4, "DON'T COME", crapsInk)
c.center(1, 13, 5, "BAR 12", crapsMuted)
nums := []int{4, 5, 6, 8, 9, 10}
- labels := []string{"4", "5", "SIX", "8", "NINE", "10"}
+ numLabels := []string{"4", "5", "SIX", "8", "NINE", "10"}
pays := []string{"9:5", "7:5", "7:6", "7:6", "7:5", "9:5"}
- puck := func(x int, on bool) {
- ink, word := crapsPuckOff, "OFF"
- if on {
- ink, word = crapsPuckOn, "ON "
- }
- a, b, d := "+---+", "|"+word+"|", "+---+"
- if unicode {
- a, b, d = "╭───╮", "│"+word+"│", "╰───╯"
- }
- c.text(x, 1, a, ink)
- c.text(x, 2, b, ink)
- c.text(x, 3, d, ink)
- }
if s.Point == 0 {
- puck(5, false)
+ crapsDrawPuck(c, 5, 1, false, unicode)
}
for i, n := range nums {
x := 14 + i*6
c.box(x, 0, x+6, 6)
- c.center(x+1, x+5, 4, labels[i], crapsGold)
+ c.center(x+1, x+5, 4, numLabels[i], crapsGold)
c.center(x+1, x+5, 5, pays[i], crapsMuted)
if s.Point == n {
- puck(x+1, true)
+ crapsDrawPuck(c, x+1, 1, true, unicode)
}
}
c.box(0, 6, 50, 10)
@@ -224,7 +249,11 @@ func renderCrapsFelt(s *casino.CrapsSnapshot, mode string, unicode bool) string
if twelve == 0 {
twelve = 2
}
- c.center(1, 49, 15, fmt.Sprintf("2 PAYS DOUBLE 12 PAYS %d:1", twelve), crapsGold)
+ twelvePay := "DOUBLE"
+ 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)
@@ -278,38 +307,132 @@ func renderCrapsFelt(s *casino.CrapsSnapshot, mode string, unicode bool) string
c.center(64, 75, 24, "15:1 / 30:1", crapsMuted)
return c.render(mode, unicode)
}
-func renderCrapsDice(dice [2]int, mode string, unicode bool, style string) string {
+
+// renderHighPointFelt draws High Point's number row, point puck and pass
+// line, with the come-out special case printed on the felt.
+func renderHighPointFelt(s *casino.CrapsSnapshot, mode string, unicode bool) string {
+ c := newCrapsCanvas(16)
+ 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"}
+ 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)
+ if s.Point == n {
+ crapsDrawPuck(c, x+3, 6, true, unicode)
+ }
+ }
+ c.box(2, 12, 74, 14)
+ c.center(3, 73, 13, "PASS LINE", crapsGold)
+ return c.render(mode, unicode)
+}
+
+// renderCraplessFelt gives every total a point box and shows the pass rail;
+// crapless has no don't bets or bonuses.
+func renderCraplessFelt(s *casino.CrapsSnapshot, mode string, unicode bool) string {
+ c := newCrapsCanvas(16)
+ c.box(0, 0, 76, 15)
+ c.center(1, 75, 1, "CRAPLESS CRAPS", crapsGold)
+ c.center(1, 75, 2, "COME-OUT 7 WINS | ALL OTHER TOTALS BECOME POINTS", crapsInk)
+ nums := []int{2, 3, 4, 5, 6, 8, 9, 10, 11, 12}
+ for i, n := range nums {
+ x := 3 + i*7
+ c.box(x, 5, x+6, 10)
+ c.center(x+1, x+5, 9, fmt.Sprint(n), crapsGold)
+ if s.Point == n {
+ crapsDrawPuck(c, x+1, 6, true, unicode)
+ }
+ }
+ c.box(2, 12, 74, 14)
+ c.center(3, 73, 13, "PASS LINE", crapsGold)
+ return c.render(mode, unicode)
+}
+
+// renderNewYorkFelt is the pass/don't-pass, buy/lay layout.
+func renderNewYorkFelt(s *casino.CrapsSnapshot, mode string, unicode bool) string {
+ c := newCrapsCanvas(16)
+ c.box(0, 0, 76, 15)
+ c.center(1, 75, 1, "NEW YORK CRAPS", crapsGold)
+ c.center(1, 75, 2, "PASS | DON'T PASS (BAR 12) | FREE ODDS | BUY / LAY 4 5 6 8 9 10", crapsInk)
+ nums := []int{4, 5, 6, 8, 9, 10}
+ for i, n := range nums {
+ x := 3 + i*12
+ c.box(x, 5, x+10, 10)
+ c.center(x+1, x+9, 9, fmt.Sprint(n), crapsGold)
+ if s.Point == n {
+ crapsDrawPuck(c, x+3, 6, true, unicode)
+ }
+ }
+ c.box(2, 12, 74, 14)
+ c.center(3, 73, 13, "PASS LINE / DON'T PASS", crapsGold)
+ return c.render(mode, unicode)
+}
+
+// renderDieRichFelt shows the single-die come-out targets and the four point
+// faces, with the pass line beneath them.
+func renderDieRichFelt(s *casino.CrapsSnapshot, mode string, unicode bool) string {
+ c := newCrapsCanvas(15)
+ c.box(0, 0, 76, 14)
+ c.center(1, 75, 1, "DIE RICH CRAPS", crapsGold)
+ c.center(1, 75, 2, "ONE DIE | COME-OUT 6 WINS, 1 LOSES | 2-5 BECOME THE POINT", crapsInk)
+ nums := []int{2, 3, 4, 5}
+ for i, n := range nums {
+ x := 8 + i*16
+ c.box(x, 5, x+12, 10)
+ c.center(x+1, x+11, 9, fmt.Sprint(n), crapsGold)
+ if s.Point == n {
+ crapsDrawPuck(c, x+4, 6, true, unicode)
+ }
+ }
+ c.box(2, 11, 74, 13)
+ c.center(3, 73, 12, "PASS LINE", crapsGold)
+ 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.
+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"}}
- top, bottom, v, pip := "+-------+", "+-------+", "|", "o"
+ pip := "o"
if unicode {
- top, bottom, v, pip = "╭───────╮", "╰───────╯", "│", "●"
+ pip = "●"
}
- spec := color.ColorSpec{Fg: 15, Bg: 0, Bold: true}
- if style == "light" {
- spec = color.ColorSpec{Fg: 0, Bg: 15, Bold: true}
+ top, bottom, v := "+-------+", "+-------+", "|"
+ if unicode {
+ top, bottom, v = "╭───────╮", "╰───────╯", "│"
}
+ spec := color.ColorSpec{Fg: 0, Bg: 15, Bold: true}
var out strings.Builder
- for row := 0; row < 5; row++ {
+ for row := 0; row < 7; row++ {
out.WriteString(" ")
for i, n := range dice {
if n == 0 {
continue
}
- line := top
- if row == 4 {
+ var line string
+ switch row {
+ case 0:
+ line = top
+ case 6:
line = bottom
- } else if row > 0 {
- line = v + " " + strings.ReplaceAll(patterns[n][row-1], "o", pip) + " " + v
+ 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 {
out.WriteString(" ")
}
}
- if row == 2 {
+ if row == 3 {
out.WriteString(color.Render(mode, color.ColorSpec{Fg: 229, Bg: -1, Bold: true}, fmt.Sprintf(" Total: %d", dice[0]+dice[1])))
}
out.WriteByte('\n')
@@ -353,37 +476,30 @@ func renderCrapsSnapshot(s *casino.CrapsSnapshot, mode string, unicode bool, sty
} else if variant == "simplified" {
out.WriteString(renderSimplifiedCrapsFelt(mode, unicode))
} else {
- height := 7
- if variant == "high_point" || variant == "die_rich" {
- height = 6
- }
- c := newCrapsCanvas(height)
- c.box(0, 0, 76, height-1)
- c.center(1, 75, 1, "HOUSE EDITION — rules teaches this edition", crapsGold)
switch variant {
+ case "high_point":
+ out.WriteString(renderHighPointFelt(s, mode, unicode))
case "crapless":
- c.center(1, 75, 3, "POINTS: 2 3 4 5 6 8 9 10 11 12 | PASS / COME / ODDS", crapsInk)
- c.center(1, 75, 4, "PLACE / BUY / LAY / FIELD / HARD / HOP / PROPOSITIONS", crapsInk)
- c.center(1, 75, 5, "NO DON'T BETS OR BONUSES", crapsMuted)
+ out.WriteString(renderCraplessFelt(s, mode, unicode))
case "new_york":
- c.center(1, 75, 3, "PASS / DON'T PASS (BAR 12) / ODDS / BUY / LAY", crapsInk)
- c.center(1, 75, 4, "BUY / LAY: 4 5 6 8 9 10 | 5% WIN-ONLY COMMISSION", crapsMuted)
+ out.WriteString(renderNewYorkFelt(s, mode, unicode))
+ case "die_rich":
+ out.WriteString(renderDieRichFelt(s, mode, unicode))
default:
- c.center(1, 75, 3, "PASS ONLY | WINS PAY 1:1 | NO ODDS OR BONUSES", crapsInk)
+ c := newCrapsCanvas(7)
+ c.box(0, 0, 76, 6)
+ c.center(1, 75, 3, "HOUSE EDITION — rules teaches this edition", crapsGold)
+ out.WriteString(c.render(mode, unicode))
}
- out.WriteString(c.render(mode, unicode))
}
out.WriteByte('\n')
if variant == "diceless" {
out.WriteString(renderCrapsCards(s.Dice, mode, unicode, style))
} else {
- out.WriteString(renderCrapsDice(s.Dice, mode, unicode, style))
+ out.WriteString(renderCrapsDice(s.Dice, mode, unicode))
}
- if len(s.Queue) > 0 {
- out.WriteString(title(" Shooter ") + subtle(s.Queue[0]) + "\n")
- if len(s.Queue) > 1 {
- out.WriteString(subtle(" On deck "+strings.Join(s.Queue[1:], " > ")) + "\n")
- }
+ if len(s.Queue) > 1 {
+ out.WriteString(subtle(" On deck "+strings.Join(s.Queue[1:], " > ")) + "\n")
}
hint := ""
if s.Phase != "waiting" && s.Phase != "" {