aboutsummaryrefslogtreecommitdiff
path: root/internal/game/render_video_poker.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-01 04:26:20 -0400
committerhistoria <[not public]>2026-10-01 04:26:20 -0400
commit0ba388bbe229dd1ccfcb0831d21a5542ae0935f1 (patch)
treec5686cf404e3dc5001d2c9b2792749ae7a3c9801 /internal/game/render_video_poker.go
parente3ff06fc79682673194c8913f0feb9e90920e7d0 (diff)
downloadthehouseoficarus-0ba388bbe229dd1ccfcb0831d21a5542ae0935f1.tar.gz
feat: video poker machines added to casino
Diffstat (limited to 'internal/game/render_video_poker.go')
-rw-r--r--internal/game/render_video_poker.go216
1 files changed, 216 insertions, 0 deletions
diff --git a/internal/game/render_video_poker.go b/internal/game/render_video_poker.go
new file mode 100644
index 0000000..d7b1a57
--- /dev/null
+++ b/internal/game/render_video_poker.go
@@ -0,0 +1,216 @@
+package game
+
+import (
+ "fmt"
+ "strings"
+
+ "thehouseoficarus/internal/cardart"
+ "thehouseoficarus/internal/casino"
+ "thehouseoficarus/internal/color"
+)
+
+var (
+ pokerBlue = color.NearestXterm256(0, 2, 105)
+ pokerRed = color.NearestXterm256(254, 0, 0)
+ pokerYellow = color.NearestXterm256(247, 231, 14)
+)
+
+func pokerInk(mode, text string, active bool) string {
+ bg := pokerBlue
+ if active {
+ bg = pokerRed
+ }
+ return color.Render(mode, color.ColorSpec{Fg: pokerYellow, Bg: bg, Bold: true}, text)
+}
+func renderPokerPayTable(s *casino.VideoPokerSnapshot, mode string) string {
+ rows := s.Config.PayRows(s.Hands)
+ if len(rows) == 0 {
+ return ""
+ }
+ nameWidth, coinWidth := len("COINS PER HAND")+3, 6
+ for _, row := range rows {
+ nameWidth = max(nameWidth, color.VisibleLen(row.Name)+3)
+ for _, n := range row.Coins {
+ coinWidth = max(coinWidth, len(fmt.Sprint(n))+2)
+ }
+ }
+ hasNext := s.Config.Feature == casino.PokerUltimateX
+ widths := []int{nameWidth, coinWidth, coinWidth, coinWidth, coinWidth, coinWidth}
+ if hasNext {
+ widths = append(widths, 8)
+ }
+ border := func(left, middle, right string) string {
+ parts := make([]string, len(widths))
+ for i, w := range widths {
+ parts[i] = strings.Repeat("─", w)
+ }
+ return pokerInk(mode, left+strings.Join(parts, middle)+right, false)
+ }
+ row := func(cells []string, leaders bool) string {
+ var b strings.Builder
+ b.WriteString(pokerInk(mode, "│", false))
+ for i, text := range cells {
+ if i == 0 && leaders {
+ text = " " + text + " " + strings.Repeat(".", widths[0]-color.VisibleLen(text)-2)
+ } else {
+ pad := max(0, widths[i]-color.VisibleLen(text))
+ text = strings.Repeat(" ", pad/2) + text + strings.Repeat(" ", pad-pad/2)
+ }
+ b.WriteString(pokerInk(mode, text, i > 0 && i <= 5 && s.Coins == i))
+ b.WriteString(pokerInk(mode, "│", false))
+ }
+ return b.String()
+ }
+ headers := []string{"COINS PER HAND", "1", "2", "3", "4", "5"}
+ if hasNext {
+ headers = append(headers, "NEXT X")
+ }
+ lines := []string{s.Config.GameLabel() + " — " + s.Config.Payout + " pay", border("┌", "┬", "┐"), row(headers, false), border("├", "┼", "┤")}
+ for _, pay := range rows {
+ cells := []string{pay.Name}
+ for _, n := range pay.Coins {
+ cells = append(cells, fmt.Sprint(n))
+ }
+ if hasNext {
+ cells = append(cells, fmt.Sprintf("%dx", pay.NextAward))
+ }
+ lines = append(lines, row(cells, true))
+ }
+ lines = append(lines, border("└", "┴", "┘"), fmt.Sprintf("1 coin = %d credits | %d hand(s) | selected coin column in red", s.Denomination, s.Hands))
+ if s.Config.Feature != "" {
+ lines = append(lines, fmt.Sprintf("Feature ON adds %d coin(s) per hand; requires five base coins.", s.Config.FeatureFeeCoins()))
+ }
+ return strings.Join(lines, "\n")
+}
+
+func renderPokerCards(cards []casino.CardView, held *[5]bool, mode string, unicode bool, size int, style string, store *cardart.Store) []string {
+ if len(cards) == 0 {
+ return nil
+ }
+ art := make([][]string, len(cards))
+ widths := make([]int, len(cards))
+ height := 0
+ for i, c := range cards {
+ art[i] = cardsLine(mode, []casino.CardView{c}, size, unicode, style, store)
+ height = max(height, len(art[i]))
+ for _, line := range art[i] {
+ widths[i] = max(widths[i], color.VisibleLen(line))
+ }
+ if held != nil {
+ widths[i] = max(widths[i], 6)
+ }
+ }
+ var lines []string
+ for r := 0; r < height; r++ {
+ parts := make([]string, len(cards))
+ for i := range cards {
+ text := ""
+ if r < len(art[i]) {
+ text = art[i][r]
+ }
+ parts[i] = rouletteCenter(text, widths[i])
+ }
+ lines = append(lines, strings.Join(parts, " "))
+ }
+ if held != nil {
+ labels := make([]string, len(cards))
+ for i := range cards {
+ text := fmt.Sprint(i + 1)
+ if held[i] {
+ text = color.Render(mode, color.ColorSpec{Fg: pokerYellow, Bg: -1, Bold: true}, text+" HELD")
+ }
+ labels[i] = rouletteCenter(text, widths[i])
+ }
+ lines = append(lines, strings.Join(labels, " "))
+ }
+ return lines
+}
+
+func renderVideoPoker(s *casino.VideoPokerSnapshot, mode string, unicode bool, size int, style string, store *cardart.Store) string {
+ if s.Hands > 1 {
+ size = min(size, 5)
+ }
+ if s.Hands >= 25 {
+ size = 1
+ }
+ if s.Hands >= 100 {
+ unicode = true
+ }
+ var lines []string
+ if s.ShowPayTable {
+ lines = append(lines, renderPokerPayTable(s, mode), "")
+ }
+ feature := "CLASSIC"
+ if s.Config.Feature != "" {
+ feature = s.Config.FeatureLabel() + " OFF"
+ if s.FeatureOn {
+ feature = s.Config.FeatureLabel() + " ON"
+ }
+ }
+ badge := pokerInk(mode, " ["+feature+"] ", false)
+ bonus := ""
+ mult := s.Multiplier
+ if s.Config.Feature == casino.PokerUltimateX {
+ for _, n := range s.Current {
+ mult = max(mult, n)
+ }
+ }
+ if mult > 1 {
+ text := fmt.Sprintf(" %dX! ", mult)
+ if s.Config.Feature == casino.PokerUltimateX && s.Hands > 1 {
+ text = fmt.Sprintf(" Up to %dX! ", mult)
+ }
+ bonus = pokerInk(mode, text, true)
+ }
+ if s.Dice[0] > 0 {
+ bonus += fmt.Sprintf(" Dice [%d] + [%d]", s.Dice[0], s.Dice[1])
+ }
+ if len(s.Cards) > 0 && len(s.Lines) == 0 {
+ art := renderPokerCards(s.Cards, &s.Held, mode, unicode, size, style, store)
+ if len(art) > 0 {
+ art[0] += " " + badge + bonus
+ }
+ lines = append(lines, art...)
+ } else if len(s.Lines) == 0 {
+ lines = append(lines, badge+bonus)
+ }
+ for i, line := range s.Lines {
+ art := renderPokerCards(line.Cards, nil, mode, unicode, size, style, store)
+ if i == 0 && len(art) > 0 {
+ art[0] += " " + badge + bonus
+ }
+ if len(s.Lines) > 1 && len(art) > 0 {
+ art[0] = fmt.Sprintf("%3d: ", i+1) + art[0]
+ }
+ lines = append(lines, art...)
+ text := "No win"
+ fg := 15
+ if line.Coins > 0 {
+ text = fmt.Sprintf("WIN: %s — %d coins", line.Category, line.Coins)
+ fg = pokerYellow
+ }
+ if line.Multiplier > 1 {
+ text += fmt.Sprintf(" (%dx)", line.Multiplier)
+ }
+ if len(s.Next) > i && s.Next[i] > 1 {
+ text += fmt.Sprintf(" | earns %dx", s.Next[i])
+ }
+ // Results always follow the cards, including compact hundred-play.
+ lines = append(lines, color.Render(mode, color.ColorSpec{Fg: fg, Bg: -1, Bold: true}, text))
+ }
+ if s.Result != "" {
+ fg := pokerYellow
+ if s.NoWin {
+ fg = 15
+ }
+ lines = append(lines, color.Render(mode, color.ColorSpec{Fg: fg, Bg: -1, Bold: true}, s.Result))
+ }
+ if !s.Drawing && len(s.Lines) == 0 && s.Config.Feature == casino.PokerUltimateX && len(s.Next) > 0 {
+ var parts []string
+ for i, n := range s.Next {
+ parts = append(parts, fmt.Sprintf("%d:%dx", i+1, n))
+ }
+ lines = append(lines, "Multipliers on the machine: "+strings.Join(parts, " "))
+ }
+ return "\n" + strings.Join(lines, "\n") + "\n"
+}