aboutsummaryrefslogtreecommitdiff
path: root/internal/game/render_cash_hunt.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-04 22:54:38 -0400
committerhistoria <[not public]>2026-10-04 22:54:38 -0400
commit8b78aa4cd930fde145959704ba7bdc0e57465b9b (patch)
treee9f3b5fe691084514359fb420cb57f5e41791a3d /internal/game/render_cash_hunt.go
parentc5ee77c345b4cafe10cf29b6c17a69e78cc5c630 (diff)
downloadthehouseoficarus-8b78aa4cd930fde145959704ba7bdc0e57465b9b.tar.gz
feat: better ux in cash hunt
Diffstat (limited to 'internal/game/render_cash_hunt.go')
-rw-r--r--internal/game/render_cash_hunt.go143
1 files changed, 117 insertions, 26 deletions
diff --git a/internal/game/render_cash_hunt.go b/internal/game/render_cash_hunt.go
index 374d6c8..0bb05fc 100644
--- a/internal/game/render_cash_hunt.go
+++ b/internal/game/render_cash_hunt.go
@@ -8,6 +8,10 @@ import (
"thehouseoficarus/internal/color"
)
+// cashHuntGradient is the board's accent gradient: red, orange, yellow, green,
+// cyan, purple. It deliberately omits dark blue and ends on purple.
+var cashHuntGradient = []int{196, 208, 220, 46, 51, 129}
+
func cashHuntPrizeColor(multiplier int) int {
switch {
case multiplier >= 10000:
@@ -25,18 +29,25 @@ func cashHuntPrizeColor(multiplier int) int {
case multiplier == 1:
return 120
default:
- return 240
+ // 0x is not a prize: match the dim square numbers so it recedes.
+ return 237
}
}
const (
cashHuntColumns = 10
- cashHuntTileWidth = 7
+ cashHuntTileWidth = 6
+ // One outer border on each side plus one separator between each of the
+ // columns.
+ cashHuntBoardWidth = cashHuntColumns*(cashHuntTileWidth+1) + 1
)
-// cashHuntTileColors is a dark rainbow, one hue per column. The shades stay
-// dark enough for bright prize text while replacing the old black/blue checker.
-var cashHuntTileColors = [cashHuntColumns]int{88, 94, 100, 28, 29, 30, 24, 18, 90, 89}
+// The whole board sits on black; the box-drawing grid supplies the separation.
+// cashHuntBoxFg keeps the grid lines a touch darker than the square numbers.
+const (
+ cashHuntTileBlack = 16
+ cashHuntBoxFg = 240
+)
// cashHuntCenter pads text to a fixed tile width, keeping every column aligned
// whether it holds a one- or three-digit square number.
@@ -48,7 +59,38 @@ func cashHuntCenter(text string, width int) string {
return strings.Repeat(" ", left) + text + strings.Repeat(" ", width-len(text)-left)
}
-func renderCashHunt(s *casino.CashHuntSnapshot, mode string, unicode bool) string {
+// cashHuntHeader builds the board's single title row: difficulty (and the
+// optional RTP readout) on the left, the gradient title centered, and the
+// board's top multiplier on the right. Every segment carries the board's black
+// background so the header reads as part of the same panel.
+func cashHuntHeader(s *casino.CashHuntSnapshot, mode, rtp string, top int) string {
+ left := strings.ToUpper(s.Risk)
+ if rtp != "" {
+ left += " " + rtp
+ }
+ rightLabel := "TOP PRIZE: "
+ rightValue := fmt.Sprintf("%dx", top)
+ right := rightLabel + rightValue
+ mid := "CASH HUNT"
+
+ inner := cashHuntBoardWidth - 2
+ gap := inner - len(left) - len(mid) - len(right)
+ if gap < 1 {
+ gap = 1
+ }
+ leftPad, rightPad := gap/2, gap-gap/2
+
+ left = color.Render(mode, color.ColorSpec{Fg: 250, Bg: cashHuntTileBlack}, left)
+ mid = color.Render(mode, color.ColorSpec{Gradient: cashHuntGradient, Bold: true, Bg: cashHuntTileBlack}, mid)
+ right = color.Render(mode, color.ColorSpec{Fg: 250, Bg: cashHuntTileBlack}, rightLabel) +
+ color.Render(mode, color.ColorSpec{Fg: cashHuntPrizeColor(top), Bold: true, Bg: cashHuntTileBlack}, rightValue)
+ space := func(n int) string {
+ return color.Render(mode, color.ColorSpec{Bg: cashHuntTileBlack}, strings.Repeat(" ", n))
+ }
+ return left + space(leftPad) + mid + space(rightPad) + right
+}
+
+func renderCashHunt(s *casino.CashHuntSnapshot, mode string, unicode bool, rtp string) string {
paint := func(text string, fg int) string { return color.Render(mode, color.ColorSpec{Fg: fg, Bg: -1}, text) }
title := paint("CASH HUNT / "+strings.ToUpper(s.Risk)+" / 98% RTP", 220)
switch s.Phase {
@@ -57,14 +99,15 @@ func renderCashHunt(s *casino.CashHuntSnapshot, mode string, unicode bool) strin
if unicode {
side, topLeft, topRight, bottomLeft, bottomRight, bar = "│", "╭", "╮", "╰", "╯", "─"
}
- lines := []string{topLeft + strings.Repeat(bar, 68) + topRight}
+ inner := cashHuntBoardWidth - 2
+ lines := []string{topLeft + strings.Repeat(bar, inner) + topRight}
for _, text := range []string{"* THE VAULT WHIRLS ... THE PRIZES VANISH! *", "All 100 positions have been hidden and mixed.", "The prizes are sealed. Your choice unlocks one.", "Choose your square: type 1-100, or pick N."} {
- padding := 68 - len(text)
+ padding := inner - len(text)
lines = append(lines, side+strings.Repeat(" ", padding/2)+text+strings.Repeat(" ", padding-padding/2)+side)
}
- lines = append(lines, bottomLeft+strings.Repeat(bar, 68)+bottomRight)
+ lines = append(lines, bottomLeft+strings.Repeat(bar, inner)+bottomRight)
for i, line := range lines {
- lines[i] = color.Render(mode, color.ColorSpec{Fg: -1, Bg: -1, Gradient: []int{196, 208, 220, 46, 51, 45, 39, 21, 201, 207}}, line)
+ lines[i] = color.Render(mode, color.ColorSpec{Fg: -1, Bg: -1, Gradient: cashHuntGradient}, line)
}
return strings.Join(lines, "\n")
case "paytable":
@@ -74,35 +117,83 @@ func renderCashHunt(s *casino.CashHuntSnapshot, mode string, unicode bool) strin
}
return strings.Join(append(lines, "Returns include stake. Each board's mix varies; jackpots are not guaranteed."), "\n")
case "preview", "result":
- // Ten columns, two lines per tile: all 100 labels and even 50000x fit
- // into a 70-column canvas without relying on cursor controls.
- lines := []string{title}
+ // The subtitle carries the run state; the boxed header and grid below it
+ // form the board itself.
+ lines := []string{}
+ top := 0
+ for _, value := range s.Squares {
+ top = max(top, value)
+ }
if s.Phase == "preview" {
- highest := 0
- for _, value := range s.Squares {
- highest = max(highest, value)
- }
- lines = append(lines, paint(fmt.Sprintf("PRIZE PREVIEW / top prize %dx / positions will be mixed", highest), cashHuntPrizeColor(highest)))
+ lines = append(lines, paint("PRIZE PREVIEW / positions will be mixed", cashHuntPrizeColor(top)))
} else {
lines = append(lines, paint(fmt.Sprintf("YOUR PICK: %d / %dx", s.Selected, s.Squares[s.Selected-1]), 220))
}
- for start := 0; start < casino.CashHuntSquares; start += cashHuntColumns {
- var numbers, prizes strings.Builder
- for k := start; k < start+cashHuntColumns; k++ {
- bg := cashHuntTileColors[k%cashHuntColumns]
+
+ tl, tr := "┌", "┐"
+ tm := "┬"
+ ml, mm, mr := "├", "┼", "┤"
+ bl, bm, br := "└", "┴", "┘"
+ hz, vt := "─", "│"
+ if !unicode {
+ tl, tr = "+", "+"
+ tm = "+"
+ ml, mm, mr = "+", "+", "+"
+ bl, bm, br = "+", "+", "+"
+ hz, vt = "-", "|"
+ }
+ border := func(left, mid, right string) string {
+ var b strings.Builder
+ b.WriteString(left)
+ for col := 0; col < cashHuntColumns; col++ {
+ b.WriteString(strings.Repeat(hz, cashHuntTileWidth))
+ if col < cashHuntColumns-1 {
+ b.WriteString(mid)
+ }
+ }
+ b.WriteString(right)
+ return b.String()
+ }
+ // Every grid character carries the board's black background, and a
+ // foreground a shade darker than the numbers, so the panel reads as one
+ // piece.
+ box := func(s string) string {
+ return color.Render(mode, color.ColorSpec{Fg: cashHuntBoxFg, Bg: cashHuntTileBlack}, s)
+ }
+
+ rows := casino.CashHuntSquares / cashHuntColumns
+ lines = append(lines,
+ box(tl+strings.Repeat(hz, cashHuntBoardWidth-2)+tr),
+ box(vt)+cashHuntHeader(s, mode, rtp, top)+box(vt),
+ // The header above is one merged cell, so the grid's top border uses
+ // down-T junctions (no vertical line rises into the header).
+ box(border(ml, tm, mr)),
+ )
+ for row := 0; row < rows; row++ {
+ numbers := make([]string, cashHuntColumns)
+ prizes := make([]string, cashHuntColumns)
+ for col := 0; col < cashHuntColumns; col++ {
+ k := row*cashHuntColumns + col
+ bg := cashHuntTileBlack
number := cashHuntCenter(fmt.Sprintf("%d", k+1), cashHuntTileWidth)
value := cashHuntCenter(fmt.Sprintf("%dx", s.Squares[k]), cashHuntTileWidth)
- nf, vf := 246, cashHuntPrizeColor(s.Squares[k])
+ nf, vf := 244, cashHuntPrizeColor(s.Squares[k])
selected := s.Selected == k+1
if selected {
number = cashHuntCenter(fmt.Sprintf(">%d<", k+1), cashHuntTileWidth)
nf, vf, bg = 16, 16, 226
}
- numbers.WriteString(color.Render(mode, color.ColorSpec{Fg: nf, Bg: bg, Bold: selected}, number))
- prizes.WriteString(color.Render(mode, color.ColorSpec{Fg: vf, Bg: bg, Bold: selected}, value))
+ numbers[col] = color.Render(mode, color.ColorSpec{Fg: nf, Bg: bg, Bold: selected}, number)
+ // Zero is not a prize: leave 0x unbolded so only real multipliers pop.
+ prizes[col] = color.Render(mode, color.ColorSpec{Fg: vf, Bg: bg, Bold: s.Squares[k] > 0}, value)
+ }
+ lines = append(lines, box(vt)+strings.Join(numbers, box(vt))+box(vt))
+ lines = append(lines, box(vt)+strings.Join(prizes, box(vt))+box(vt))
+ if row < rows-1 {
+ lines = append(lines, box(border(ml, mm, mr)))
}
- lines = append(lines, numbers.String(), prizes.String())
}
+ lines = append(lines, box(border(bl, bm, br)))
return strings.Join(lines, "\n")
default:
return title + "\n" + paint("100 squares. One choice. Prizes from 0x to 50000x.", 81)