aboutsummaryrefslogtreecommitdiff
path: root/internal/game/render_pai_gow.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-01 17:24:54 -0400
committerhistoria <[not public]>2026-10-01 17:24:54 -0400
commit4e2a95503e4ea4c830510f5aea23fac7948ddc41 (patch)
tree690b06df9094480febfaaaf7d9fc71ef7b81848d /internal/game/render_pai_gow.go
parentd762f8e1360cf38b8e11e69083c2dad59ec7130e (diff)
downloadthehouseoficarus-4e2a95503e4ea4c830510f5aea23fac7948ddc41.tar.gz
feat: set and autoset in pai gow, better showdown arrangement, better formatting
Diffstat (limited to 'internal/game/render_pai_gow.go')
-rw-r--r--internal/game/render_pai_gow.go205
1 files changed, 186 insertions, 19 deletions
diff --git a/internal/game/render_pai_gow.go b/internal/game/render_pai_gow.go
index d364c61..4b31373 100644
--- a/internal/game/render_pai_gow.go
+++ b/internal/game/render_pai_gow.go
@@ -6,6 +6,13 @@ import (
"thehouseoficarus/internal/cardart"
"thehouseoficarus/internal/casino"
+ "thehouseoficarus/internal/color"
+)
+
+var (
+ paiGowWin = color.ColorSpec{Fg: color.NearestXterm256(80, 220, 100), Bold: true}
+ paiGowLoss = color.ColorSpec{Fg: color.NearestXterm256(240, 80, 80), Bold: true}
+ paiGowPush = color.ColorSpec{Fg: 15, Bold: true}
)
func renderPaiGowFelt(cfg casino.TableConfig, mode string, unicode bool) string {
@@ -55,30 +62,190 @@ func renderPaiGowFelt(cfg casino.TableConfig, mode string, unicode bool) string
c.center(1, 75, 22, joker, crapsMuted)
return c.render(mode, unicode)
}
+
+// paiGowNumberLabels centers the card's original position number under it.
+func paiGowNumberLabels(nums []int, mode string) []string {
+ labels := make([]string, len(nums))
+ for i, n := range nums {
+ labels[i] = color.Render(mode, color.ColorSpec{Fg: 15, Bold: true}, fmt.Sprintf("%d", n))
+ }
+ return labels
+}
+
+// paiGowCardBlock renders one labeled run of cards: an optional centered
+// header over the card art and, when nums is set, position numbers below.
+func paiGowCardBlock(header string, cards []casino.CardView, nums []int, mode string, unicode bool, size int, style string, store *cardart.Store) []string {
+ if len(cards) == 0 {
+ return nil
+ }
+ var labels []string
+ if len(nums) > 0 {
+ labels = paiGowNumberLabels(nums, mode)
+ }
+ lines := renderCardRow(cards, labels, 0, mode, unicode, size, style, store)
+ if header == "" {
+ return lines
+ }
+ width := 0
+ for _, line := range lines {
+ width = max(width, color.VisibleLen(line))
+ }
+ return append([]string{rouletteCenter(header, width)}, lines...)
+}
+
+// paiGowJoinBlocks sets two card blocks side by side with a gutter between
+// them, so the high hand and the low hand read as separate groups.
+func paiGowJoinBlocks(left, right []string, gutter string) []string {
+ height := max(len(left), len(right))
+ leftWidth := 0
+ for _, line := range left {
+ leftWidth = max(leftWidth, color.VisibleLen(line))
+ }
+ lines := make([]string, 0, height)
+ for i := 0; i < height; i++ {
+ l, r := "", ""
+ if i < len(left) {
+ l = left[i]
+ }
+ if i < len(right) {
+ r = right[i]
+ }
+ lines = append(lines, l+strings.Repeat(" ", leftWidth-color.VisibleLen(l))+gutter+r)
+ }
+ return lines
+}
+
+// paiGowStrength paints a hand's name yellow when it won its comparison and
+// white when it did not.
+func paiGowStrength(mode, strength string, winning bool) string {
+ spec := color.ColorSpec{Fg: 15, Bold: true}
+ if winning {
+ spec = color.ColorSpec{Fg: pokerYellow, Bold: true}
+ }
+ return color.Render(mode, spec, strength)
+}
+
+// paiGowArranged renders the preview: the five-card high hand, a gap, then the
+// two-card low hand, each with its original position numbers underneath.
+func paiGowArranged(s *casino.PaiGowSnapshot, mode string, unicode bool, size int, style string, store *cardart.Store) []string {
+ high := paiGowCardBlock(color.Render(mode, paiGowPush, "HIGH"), s.PreviewHigh, s.PreviewHighNums, mode, unicode, size, style, store)
+ low := paiGowCardBlock(color.Render(mode, paiGowPush, "LOW"), s.PreviewLow, s.PreviewLowNums, mode, unicode, size, style, store)
+ lines := []string{"Your arranged hand — high first, then low. Type set to lock it, or choose again:"}
+ return append(lines, paiGowJoinBlocks(high, low, " ")...)
+}
+
+// paiGowOutcome renders the comparison narrative and settlement summary.
+func paiGowOutcome(sd *casino.PaiGowShowdown, mode string) []string {
+ var lines []string
+ if len(sd.Hands) >= 4 {
+ switch {
+ case sd.Hands[0].Winning && sd.Hands[2].Winning:
+ lines = append(lines, "You take both hands.")
+ case !sd.Hands[0].Winning && !sd.Hands[2].Winning:
+ lines = append(lines, "The dealer takes both hands.")
+ default:
+ high, low := "You take the high hand", "you take the low hand"
+ if !sd.Hands[0].Winning {
+ high = "The dealer takes the high hand"
+ }
+ if !sd.Hands[2].Winning {
+ low = "the dealer takes the low hand"
+ }
+ lines = append(lines, high+"; "+low+".")
+ }
+ }
+ spec, label := paiGowLoss, "DEALER WINS THE ROUND"
+ switch {
+ case strings.HasPrefix(sd.Result, "win"):
+ spec, label = paiGowWin, "YOU WIN THE ROUND"
+ case strings.HasPrefix(sd.Result, "push"):
+ spec, label = paiGowPush, "PUSH — YOUR WAGER IS RETURNED"
+ if detail := strings.Trim(strings.TrimPrefix(sd.Result, "push"), " ()"); detail != "" {
+ label = "PUSH (" + strings.ToUpper(detail) + ") — YOUR WAGER IS RETURNED"
+ }
+ }
+ lines = append(lines, color.Render(mode, spec, label))
+ if sd.Fortune != "" {
+ if sd.FortuneWin {
+ lines = append(lines, "Fortune: "+color.Render(mode, paiGowWin, strings.ToUpper(sd.Fortune)+" — WIN"))
+ } else {
+ lines = append(lines, "Fortune: no win.")
+ }
+ }
+ if sd.PushAce != "" {
+ if sd.PushAceWin {
+ lines = append(lines, "Push Ace High: "+color.Render(mode, paiGowWin, sd.PushAce+" — WIN"))
+ } else {
+ lines = append(lines, "Push Ace High: "+sd.PushAce+".")
+ }
+ }
+ if sd.ShowEnvy && sd.Envy > 0 {
+ lines = append(lines, "Envy: "+color.Render(mode, paiGowWin, fmt.Sprintf("+%d credits", sd.Envy)))
+ }
+ switch {
+ case sd.Pending:
+ lines = append(lines, color.Render(mode, paiGowLoss, "PAYMENT PENDING — type settle to retry the same result."))
+ case sd.Paid:
+ net := sd.Payout - sd.Stake
+ netSpec, netText := paiGowPush, "Net 0 credits."
+ if net > 0 {
+ netSpec, netText = paiGowWin, fmt.Sprintf("Net +%d credits.", net)
+ } else if net < 0 {
+ netSpec, netText = paiGowLoss, fmt.Sprintf("Net %d credits.", net)
+ }
+ lines = append(lines, color.Render(mode, netSpec, netText))
+ }
+ return lines
+}
+
+func paiGowShowdown(s *casino.PaiGowSnapshot, mode string, unicode bool, size int, style string, store *cardart.Store) []string {
+ sd := s.Showdown
+ var lines []string
+ for i, hand := range sd.Hands {
+ header := hand.Label
+ if hand.Strength != "" {
+ header += " (" + paiGowStrength(mode, hand.Strength, hand.HasWin && hand.Winning) + ")"
+ }
+ lines = append(lines, paiGowCardBlock(header, hand.Cards, nil, mode, unicode, size, style, store)...)
+ if i == 1 {
+ lines = append(lines, "")
+ }
+ }
+ lines = append(lines, "")
+ return append(lines, paiGowOutcome(sd, mode)...)
+}
+
func renderPaiGow(s *casino.PaiGowSnapshot, mode string, unicode bool, size int, style string, store *cardart.Store) string {
size = cardRenderSize(unicode, size)
- lines := []string{renderPaiGowFelt(s.Config, mode, unicode)}
- if len(s.Cards) > 0 {
+ var lines []string
+ if s.ShowFelt {
+ lines = append(lines, renderPaiGowFelt(s.Config, mode, unicode))
+ }
+ switch {
+ case s.Showdown != nil:
+ lines = append(lines, paiGowShowdown(s, mode, unicode, size, style, store)...)
+ case s.Preview:
+ lines = append(lines, paiGowArranged(s, mode, unicode, size, style, store)...)
+ case len(s.Cards) > 0:
lines = append(lines, "Your seven cards (positions 1-7):")
- lines = append(lines, cardsLine(mode, s.Cards, size, unicode, style, store)...)
- // Position labels use each card's actual rendered width; compact cards are
- // variable-width, so print a separate unambiguous position-to-card key.
- var positions []string
- for i, c := range s.Cards {
- positions = append(positions, fmt.Sprintf("%d=%s", i+1, cardSymbol(c)))
- }
- lines = append(lines, strings.Join(positions, " "))
- }
- for _, h := range []struct {
- label string
- cards []casino.CardView
- }{{"Your high", s.High}, {"Your low", s.Low}, {"Dealer high", s.DealerHigh}, {"Dealer low", s.DealerLow}, {"Tiebreaker: player / dealer", s.Tiebreaker}} {
- if len(h.cards) > 0 {
- lines = append(lines, h.label+":")
- lines = append(lines, cardsLine(mode, h.cards, size, unicode, style, store)...)
+ nums := make([]int, len(s.Cards))
+ for i := range nums {
+ nums[i] = i + 1
}
+ lines = append(lines, paiGowCardBlock("", s.Cards, nums, mode, unicode, size, style, store)...)
+ }
+ // Face Up reveals the dealer before the player's decision; the showdown
+ // replaces this block once it exists.
+ if s.Showdown == nil && len(s.DealerHigh) > 0 {
+ lines = append(lines, "")
+ lines = append(lines, paiGowCardBlock("Dealer high ("+s.DealerHighName+")", s.DealerHigh, nil, mode, unicode, size, style, store)...)
+ lines = append(lines, paiGowCardBlock("Dealer low ("+s.DealerLowName+")", s.DealerLow, nil, mode, unicode, size, style, store)...)
+ }
+ if len(s.Tiebreaker) > 0 {
+ lines = append(lines, "Tiebreaker — player / dealer:")
+ lines = append(lines, paiGowCardBlock("", s.Tiebreaker, nil, mode, unicode, size, style, store)...)
}
- if s.Result != "" {
+ if s.Showdown == nil && s.Result != "" {
lines = append(lines, s.Result)
}
return strings.Join(lines, "\n")