diff options
| author | historia <[not public]> | 2026-10-01 17:24:54 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-10-01 17:24:54 -0400 |
| commit | 4e2a95503e4ea4c830510f5aea23fac7948ddc41 (patch) | |
| tree | 690b06df9094480febfaaaf7d9fc71ef7b81848d /internal/game/render_pai_gow.go | |
| parent | d762f8e1360cf38b8e11e69083c2dad59ec7130e (diff) | |
| download | thehouseoficarus-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.go | 205 |
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") |
