1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
|
package game
import (
"fmt"
"strings"
"thehouseoficarus/internal/casino"
"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:
return 225
case multiplier >= 500:
return 201
case multiplier >= 100:
return 220
case multiplier >= 20:
return 207
case multiplier >= 5:
return 141
case multiplier >= 2:
return 81
case multiplier == 1:
return 120
default:
// 0x is not a prize: match the dim square numbers so it recedes.
return 237
}
}
const (
cashHuntColumns = 10
cashHuntTileWidth = 6
// One outer border on each side plus one separator between each of the
// columns.
cashHuntBoardWidth = cashHuntColumns*(cashHuntTileWidth+1) + 1
)
// 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.
func cashHuntCenter(text string) string {
width := cashHuntTileWidth
if len(text) >= width {
return text
}
left := (width - len(text)) / 2
return strings.Repeat(" ", left) + text + strings.Repeat(" ", width-len(text)-left)
}
// 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 {
case "hidden":
side, topLeft, topRight, bottomLeft, bottomRight, bar := "|", "+", "+", "+", "+", "-"
if unicode {
side, topLeft, topRight, bottomLeft, bottomRight, bar = "│", "╭", "╮", "╰", "╯", "─"
}
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 := 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, inner)+bottomRight)
for i, line := range lines {
lines[i] = color.Render(mode, color.ColorSpec{Fg: -1, Bg: -1, Gradient: cashHuntGradient}, line)
}
return strings.Join(lines, "\n")
case "paytable":
lines := []string{title, "Prize Chance per square"}
for _, prize := range s.Odds {
lines = append(lines, paint(fmt.Sprintf("%6dx %10.6f%%", prize.Multiplier, float64(prize.Weight)/1_000_000), cashHuntPrizeColor(prize.Multiplier)))
}
return strings.Join(append(lines, "Returns include stake. Each board's mix varies; jackpots are not guaranteed."), "\n")
case "preview", "result":
// 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" {
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))
}
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))
value := cashHuntCenter(fmt.Sprintf("%dx", s.Squares[k]))
nf, vf := 244, cashHuntPrizeColor(s.Squares[k])
selected := s.Selected == k+1
if selected {
number = cashHuntCenter(fmt.Sprintf(">%d<", k+1))
nf, vf, bg = 16, 16, 226
}
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, 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)
}
}
|