diff options
| author | historia <[not public]> | 2026-10-04 17:44:27 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-10-04 17:44:27 -0400 |
| commit | 93dcb67ffee05ebf1de0c9a78a8b906eaf161007 (patch) | |
| tree | 88cdd3d732f11eee616698a9ca32f699d2b41d77 /internal/game/render_plinko.go | |
| parent | 3a39a020e4449bc4d6f728ef837cffb9f7bd0c56 (diff) | |
| download | thehouseoficarus-93dcb67ffee05ebf1de0c9a78a8b906eaf161007.tar.gz | |
feat: new casino game: cash hunt, fixed verb issue with pai gow
Diffstat (limited to 'internal/game/render_plinko.go')
| -rw-r--r-- | internal/game/render_plinko.go | 103 |
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)) } |
