aboutsummaryrefslogtreecommitdiff
path: root/internal/game/render_plinko.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game/render_plinko.go')
-rw-r--r--internal/game/render_plinko.go103
1 files changed, 57 insertions, 46 deletions
diff --git a/internal/game/render_plinko.go b/internal/game/render_plinko.go
index 17f533a..7988d8c 100644
--- a/internal/game/render_plinko.go
+++ b/internal/game/render_plinko.go
@@ -44,8 +44,12 @@ func plinkoColorAt(x int, scale float64) int {
max(0, min(255, int(float64(rgb[2])*scale))))
}
-// plinkoHintColor is the dim white used by the one-time midpoint pocket ruler.
-func plinkoHintColor() int { return 238 }
+// plinkoHintRow is where the pocket reminders are overlaid. It sits two rows
+// below the midpoint so the letters clear the payout legend, which only runs
+// down to the midpoint row.
+const plinkoHintRow = plinkoRows/2 + 2
+
+const plinkoHintColor = 238
// Foregrounds are deliberately faint so the balls stay the brightest thing
// on the board.
@@ -82,15 +86,24 @@ func (line *plinkoLine) text(x int, text string, fg int) {
}
func (line plinkoLine) render(mode string) string {
var b strings.Builder
- for _, cell := range line {
- fg := cell.fg
- // Basic ANSI has no dim white; the dim attribute plus bright black is
- // its closest readable stand-in for a faint white hint.
- dim := false
- if cell.guide && mode == "ansi" {
- fg, dim = 7, true
+ // Paint contiguous runs, not individual cells. This keeps the same fixed
+ // canvas without flooding a slow MUD connection with redundant resets.
+ for x := 0; x < len(line); {
+ cell := line[x]
+ end := x + 1
+ for end < len(line) && line[end].fg == cell.fg && line[end].guide == cell.guide {
+ end++
+ }
+ var text strings.Builder
+ for _, c := range line[x:end] {
+ text.WriteRune(c.glyph)
}
- b.WriteString(color.Render(mode, color.ColorSpec{Fg: fg, Bg: -1, Dim: dim}, string(cell.glyph)))
+ fg, dim := cell.fg, cell.guide && mode == "ansi"
+ if dim {
+ fg = 7 // Basic ANSI approximates the faint gray with dim white.
+ }
+ b.WriteString(color.Render(mode, color.ColorSpec{Fg: fg, Bg: -1, Dim: dim}, text.String()))
+ x = end
}
return b.String()
}
@@ -125,43 +138,38 @@ func plinkoLegendText(e plinkoLegendEntry) string {
return fmt.Sprintf("%c %-7s", e.label, e.text)
}
-// The legend is written down the left, outside the pyramid, one entry per
-// row, so every value is known well before the ball reaches the pockets.
-func renderPlinkoLegend(s *casino.PlinkoSnapshot, mode string) []string {
- var lines []string
+// The standalone paytable is compact text, not another board canvas.
+func renderPlinkoPaytable(s *casino.PlinkoSnapshot, mode string) string {
+ balls := "balls"
+ if s.Total == 1 {
+ balls = "ball"
+ }
+ parts := []string{fmt.Sprintf("%s %d %s %.2f%% RTP:", s.Risk, s.Total, balls, s.RTP)}
for _, e := range plinkoLegendEntries(s) {
- line := newPlinkoLine()
- line.text(1, plinkoLegendText(e), plinkoPocketColor(2+4*e.pocket))
- lines = append(lines, line.render(mode))
+ parts = append(parts, color.Render(mode, color.ColorSpec{Fg: plinkoPocketColor(2 + 4*e.pocket), Bg: -1}, fmt.Sprintf("%c%s", e.label, e.text)))
}
- return lines
+ return strings.Join(parts, " ")
}
-// plinkoHintLine is the one-time pocket ruler printed at the board's midpoint,
-// so players learn the pocket columns without a letter on every row.
-func plinkoHintLine(labels [17]rune, mode string) string {
- hint := newPlinkoLine()
+// plinkoOverlayHints prints the pocket reminders across the reminder row. The
+// letters sit on the fixed pocket columns (2+4k) so they line up with the base
+// rail. Hints only fill empty cells: a falling ball must never disappear.
+func plinkoOverlayHints(line *plinkoLine, labels [17]rune) {
for k, label := range labels {
- hint[2+4*k] = plinkoCell{glyph: label, fg: plinkoHintColor(), guide: true}
+ x := 2 + 4*k
+ if line[x].glyph == ' ' {
+ line[x] = plinkoCell{glyph: label, fg: plinkoHintColor, guide: true}
+ }
}
- return hint.render(mode)
}
// The legend appears down the left, then each snapshot appends one scanline.
-// The pocket hints print once, as a ruler at the board's midpoint.
+// The pocket reminders are overlaid on their own board row, not a line of their
+// own.
func renderPlinko(s *casino.PlinkoSnapshot, mode string, unicode bool) string {
labels := casino.PlinkoPocketLabels(s.Pockets)
if s.Paytable {
- title := "=== P L I N K O / " + strings.ToUpper(s.Risk) + " ==="
- heading := newPlinkoLine()
- heading.text((plinkoWidth-len(title))/2, title, plinkoPocketColor(34))
- note := newPlinkoLine()
- note.text(1, "Matching letters share a payout; returns include the stake.", plinkoPocketColor(34))
- lines := []string{heading.render(mode), note.render(mode)}
- lines = append(lines, renderPlinkoLegend(s, mode)...)
- tail := newPlinkoLine()
- tail.text(1, "White = one ball; shades to red = all balls overlapping.", plinkoPocketColor(34))
- return strings.Join(append(lines, tail.render(mode)), "\n")
+ return renderPlinkoPaytable(s, mode)
}
ball, peg := 'o', '.'
if unicode {
@@ -169,10 +177,7 @@ func renderPlinko(s *casino.PlinkoSnapshot, mode string, unicode bool) string {
}
line := newPlinkoLine()
left, right := 32-2*s.Row, 36+2*s.Row
- line[left] = plinkoCell{glyph: '/', fg: plinkoBorderColor(left)}
- line[right] = plinkoCell{glyph: '\\', fg: plinkoBorderColor(right)}
- // Pegs sit at their true between-lane positions; they are never moved to
- // make room for a guide letter.
+ // Pegs sit at their true between-lane positions.
for k := 1; k <= s.Row; k++ {
x := left + 4*k
line[x] = plinkoCell{glyph: peg, fg: plinkoPegColor(x)}
@@ -191,21 +196,27 @@ func renderPlinko(s *casino.PlinkoSnapshot, mode string, unicode bool) string {
}
line[left+2+4*k] = plinkoCell{glyph: glyph, fg: plinkoBallColor(count, s.Total)}
}
+ line[left] = plinkoCell{glyph: '/', fg: plinkoBorderColor(left)}
+ line[right] = plinkoCell{glyph: '\\', fg: plinkoBorderColor(right)}
if entries := plinkoLegendEntries(s); s.Row < len(entries) {
e := entries[s.Row]
line.text(1, plinkoLegendText(e), plinkoPocketColor(2+4*e.pocket))
}
- lines := []string{line.render(mode)}
- if s.Row == plinkoRows/2 {
- lines = append(lines, plinkoHintLine(labels, mode))
+ if s.Row == plinkoHintRow {
+ plinkoOverlayHints(&line, labels)
}
- if s.Row == 16 {
+ lines := []string{line.render(mode)}
+ if s.Row == plinkoRows {
rail := newPlinkoLine()
for k, label := range labels {
x := 1 + 4*k
- rail[x] = plinkoCell{glyph: '[', fg: plinkoPegColor(x)}
- rail[x+1] = plinkoCell{glyph: label, fg: plinkoPocketColor(x + 1)}
- rail[x+2] = plinkoCell{glyph: ']', fg: plinkoPegColor(x + 2)}
+ left, right := '[', ']'
+ fg := plinkoPocketColor(x + 1)
+ if s.Counts[k] > 0 {
+ left, right = '>', '<'
+ fg = plinkoBallColor(s.Counts[k], s.Total)
+ }
+ rail.text(x, string([]rune{left, label, right}), fg)
}
lines = append(lines, rail.render(mode))
}