aboutsummaryrefslogtreecommitdiff
path: root/internal/game
diff options
context:
space:
mode:
Diffstat (limited to 'internal/game')
-rw-r--r--internal/game/casino.go48
-rw-r--r--internal/game/cmd_registry.go2
-rw-r--r--internal/game/pai_gow_test.go170
-rw-r--r--internal/game/render_pai_gow.go205
-rw-r--r--internal/game/render_video_poker.go35
5 files changed, 407 insertions, 53 deletions
diff --git a/internal/game/casino.go b/internal/game/casino.go
index 66c90a1..b15c965 100644
--- a/internal/game/casino.go
+++ b/internal/game/casino.go
@@ -897,6 +897,54 @@ func cardsLine(mode string, cards []casino.CardView, size int, unicode bool, sty
return lines
}
+// renderCardRow lays cards side by side, each column padded to its own art
+// or label width, and appends an optional centered label row. minWidth lets
+// the caller force a floor (video poker's HELD labels need it) while Pai Gow
+// passes 0. It is the shared "cards with numbers underneath" presentation.
+func renderCardRow(cards []casino.CardView, labels []string, minWidth int, mode string, unicode bool, size int, style string, art *cardart.Store) []string {
+ if len(cards) == 0 {
+ return nil
+ }
+ artLines := make([][]string, len(cards))
+ widths := make([]int, len(cards))
+ height := 0
+ for i, card := range cards {
+ artLines[i] = cardsLine(mode, []casino.CardView{card}, size, unicode, style, art)
+ height = max(height, len(artLines[i]))
+ widths[i] = minWidth
+ for _, line := range artLines[i] {
+ widths[i] = max(widths[i], color.VisibleLen(line))
+ }
+ if i < len(labels) {
+ widths[i] = max(widths[i], color.VisibleLen(labels[i]))
+ }
+ }
+ lines := make([]string, 0, height+1)
+ for row := 0; row < height; row++ {
+ parts := make([]string, len(cards))
+ for i := range cards {
+ text := ""
+ if row < len(artLines[i]) {
+ text = artLines[i][row]
+ }
+ parts[i] = rouletteCenter(text, widths[i])
+ }
+ lines = append(lines, strings.Join(parts, " "))
+ }
+ if len(labels) > 0 {
+ parts := make([]string, len(cards))
+ for i := range cards {
+ text := ""
+ if i < len(labels) {
+ text = labels[i]
+ }
+ parts[i] = rouletteCenter(text, widths[i])
+ }
+ lines = append(lines, strings.Join(parts, " "))
+ }
+ return lines
+}
+
func cardRenderSize(unicode bool, size int) int {
if size == 1 {
return 1
diff --git a/internal/game/cmd_registry.go b/internal/game/cmd_registry.go
index d8f6b76..acac68b 100644
--- a/internal/game/cmd_registry.go
+++ b/internal/game/cmd_registry.go
@@ -300,7 +300,7 @@ func (g *Game) executeCommand(sess *net.Session, cmd string, args []string, rawI
if p != nil && g.Casino != nil {
if t, playing := g.Casino.Participation(p.Name); playing && t != nil && t.Config.Game == casino.GamePaiGow {
numeric := cmd != "" && cmd[0] >= '0' && cmd[0] <= '9'
- if numeric || cmd == "set" || cmd == "house" || cmd == "settle" {
+ if numeric || cmd == "set" || cmd == "house" || cmd == "settle" || cmd == "autoset" {
g.executeTableAction(sess, strings.Join(append([]string{cmd}, args...), " "))
return
}
diff --git a/internal/game/pai_gow_test.go b/internal/game/pai_gow_test.go
index af6b749..9b37a68 100644
--- a/internal/game/pai_gow_test.go
+++ b/internal/game/pai_gow_test.go
@@ -39,19 +39,179 @@ func TestPaiGowCommandsLookAndSettlement(t *testing.T) {
if classifyCommand("67") != ClassInstant || classifyCommand("13456") != ClassInstant {
t.Fatal("numeric classification")
}
- // A numeric selection either locks a valid split or gives a clear foul;
- // either way it must reach Pai Gow rather than the generic unknown verb.
+ // A numeric selection either previews a valid arrangement or gives a clear
+ // foul; either way it must reach Pai Gow rather than the generic unknown
+ // verb, and must not lock the hand before "set".
conn.data = nil
g.executeCommand(sess, "67", nil, "67")
text = string(conn.data)
- if table.Status(sess.Player.Name)[0].Snapshot.(*casino.PaiGowSnapshot).Phase == "setting" && !strings.Contains(text, "FOUL HAND") {
+ snap = table.Status(sess.Player.Name)[0].Snapshot.(*casino.PaiGowSnapshot)
+ if snap.Phase == "setting" && !strings.Contains(text, "FOUL HAND") && !strings.Contains(text, "arranged hand") {
t.Fatal(text)
}
g.executeCommand(sess, "house", nil, "house")
+ g.executeCommand(sess, "set", nil, "set")
if table.Status(sess.Player.Name)[0].Snapshot.(*casino.PaiGowSnapshot).Phase != "waiting" || len(sess.Player.PendingWagers) != 0 {
t.Fatal("settlement did not finish")
}
}
+func pgTestSnap() *casino.PaiGowSnapshot {
+ cv := func(rank, suit string) casino.CardView { return casino.CardView{Rank: rank, Suit: suit} }
+ return &casino.PaiGowSnapshot{
+ Config: casino.TableConfig{Game: casino.GamePaiGow, PaiGow: casino.PaiGowConfig{Envy: true}},
+ Phase: "setting",
+ Cards: []casino.CardView{cv("A", "diamonds"), cv("K", "clubs"), cv("9", "clubs"), cv("A", "spades"), cv("Q", "spades"), cv("2", "diamonds"), cv("6", "hearts")},
+ }
+}
+
+func TestPaiGowFeltOnlyOnJoin(t *testing.T) {
+ s := pgTestSnap()
+ s.ShowFelt = true
+ withFelt := stripANSI(renderPaiGow(s, "none", true, 7, "dark", nil))
+ if !strings.Contains(withFelt, "FORTUNE") {
+ t.Fatalf("join did not show the felt:\n%s", withFelt)
+ }
+ s.ShowFelt = false
+ without := stripANSI(renderPaiGow(s, "none", true, 7, "dark", nil))
+ if strings.Contains(without, "FORTUNE") || strings.Contains(without, "COMMISSION") {
+ t.Fatalf("felt leaked into an action:\n%s", without)
+ }
+}
+
+func TestPaiGowCardsShowNumbersUnderneath(t *testing.T) {
+ s := pgTestSnap()
+ out := stripANSI(renderPaiGow(s, "none", true, 7, "dark", nil))
+ if !strings.Contains(out, "Your seven cards") {
+ t.Fatalf("missing dealt-hand header:\n%s", out)
+ }
+ // The old side-by-side position key must be gone; the numbers now sit
+ // under the cards, one per column.
+ if strings.Contains(out, "1=A") || strings.Contains(out, "=") {
+ t.Fatalf("stale position key still present:\n%s", out)
+ }
+ for _, n := range []string{"1", "2", "3", "4", "5", "6", "7"} {
+ if !strings.Contains(out, n) {
+ t.Fatalf("missing position %s:\n%s", n, out)
+ }
+ }
+}
+
+func TestPaiGowArrangedPreviewHighThenLow(t *testing.T) {
+ s := pgTestSnap()
+ s.Preview = true
+ s.PreviewHigh = s.Cards[:5]
+ s.PreviewLow = s.Cards[5:]
+ s.PreviewHighNums = []int{1, 2, 3, 4, 5}
+ s.PreviewLowNums = []int{6, 7}
+ out := stripANSI(renderPaiGow(s, "none", true, 7, "dark", nil))
+ if !strings.Contains(out, "HIGH") || !strings.Contains(out, "LOW") {
+ t.Fatalf("missing HIGH/LOW labels:\n%s", out)
+ }
+ // The five-card high hand precedes the two-card low hand, and its number
+ // row still keys back to the dealt positions.
+ hi := strings.Index(out, "HIGH")
+ lo := strings.Index(out, "LOW")
+ if hi < 0 || lo < 0 || hi > lo {
+ t.Fatalf("HIGH/LOW order wrong:\n%s", out)
+ }
+ if !strings.Contains(out, "arranged hand") {
+ t.Fatalf("missing set prompt:\n%s", out)
+ }
+}
+
+func TestPaiGowShowdownOrderNamesAndColors(t *testing.T) {
+ cv := func(rank, suit string) casino.CardView { return casino.CardView{Rank: rank, Suit: suit} }
+ s := pgTestSnap()
+ s.Phase = "waiting"
+ s.Showdown = &casino.PaiGowShowdown{
+ Result: "loss",
+ Hands: []casino.PaiGowHandView{
+ {Label: "Your high", Strength: "STRAIGHT ACE-HIGH", Cards: []casino.CardView{cv("A", "diamonds"), cv("K", "clubs"), cv("Q", "clubs"), cv("J", "spades"), cv("10", "spades")}, Winning: true, HasWin: true},
+ {Label: "Dealer high", Strength: "TWO PAIR ACES AND KINGS", Cards: []casino.CardView{cv("A", "clubs"), cv("A", "hearts"), cv("K", "diamonds"), cv("K", "spades"), cv("9", "clubs")}, Winning: false, HasWin: true},
+ {Label: "Your low", Strength: "PAIR OF ACES", Cards: []casino.CardView{cv("A", "diamonds"), cv("2", "diamonds")}, Winning: false, HasWin: true},
+ {Label: "Dealer low", Strength: "HIGH CARD KING", Cards: []casino.CardView{cv("K", "clubs"), cv("9", "hearts")}, Winning: true, HasWin: true},
+ },
+ Stake: 25, Payout: 50, Paid: true,
+ }
+ raw := renderPaiGow(s, "xterm256", true, 7, "dark", nil)
+ clean := stripANSI(raw)
+ high := strings.Index(clean, "Your high")
+ dealerHigh := strings.Index(clean, "Dealer high")
+ low := strings.Index(clean, "Your low")
+ dealerLow := strings.Index(clean, "Dealer low")
+ if !(high >= 0 && high < dealerHigh && dealerHigh < low && low < dealerLow) {
+ t.Fatalf("showdown order wrong:\n%s", clean)
+ }
+ for _, want := range []string{"STRAIGHT ACE-HIGH", "TWO PAIR ACES AND KINGS", "PAIR OF ACES", "HIGH CARD KING", "DEALER WINS THE ROUND", "Net +25 credits."} {
+ if !strings.Contains(clean, want) {
+ t.Fatalf("showdown missing %q:\n%s", want, clean)
+ }
+ }
+ // Winning hand strengths are bold yellow; losing ones are bold white.
+ if !strings.Contains(raw, "38;5;220m") {
+ t.Fatalf("winning strength not yellow:\n%q", raw)
+ }
+ if !strings.Contains(raw, "38;5;15m") {
+ t.Fatalf("losing strength not white:\n%q", raw)
+ }
+}
+
+func TestPaiGowOutcomeOmitsEnvyWithoutOthers(t *testing.T) {
+ s := pgTestSnap()
+ s.Phase = "waiting"
+ s.Showdown = &casino.PaiGowShowdown{
+ Result: "win",
+ Hands: []casino.PaiGowHandView{
+ {Label: "Your high", Strength: "PAIR OF KINGS", Winning: true, HasWin: true},
+ {Label: "Dealer high", Strength: "HIGH CARD ACE", Winning: false, HasWin: true},
+ {Label: "Your low", Strength: "PAIR OF ACES", Winning: true, HasWin: true},
+ {Label: "Dealer low", Strength: "HIGH CARD KING", Winning: false, HasWin: true},
+ },
+ Payout: 20, Paid: true,
+ }
+ out := stripANSI(renderPaiGow(s, "none", true, 7, "dark", nil))
+ if strings.Contains(out, "Envy") {
+ t.Fatalf("envy shown without other players:\n%s", out)
+ }
+ if !strings.Contains(out, "YOU WIN THE ROUND") {
+ t.Fatalf("overall win missing:\n%s", out)
+ }
+}
+
+func TestPaiGowSetCommandLocksPreview(t *testing.T) {
+ g, sess, conn := casinoCrapsGame(t, "name: Pai Gow\ncasino_tables:\n - id: fortune\n game: paigow\n min_bet: 5\n max_bet: 1000\n")
+ g.executePlay(sess, []string{"pai"}, "play pai")
+ g.executeBet(sess, []string{"20"}, "bet 20")
+ table, _ := g.Casino.Participation(sess.Player.Name)
+ // house only arranges; the hand stays unset until set.
+ conn.data = nil
+ g.executeCommand(sess, "house", nil, "house")
+ snap := table.Status(sess.Player.Name)[0].Snapshot.(*casino.PaiGowSnapshot)
+ if !snap.Preview || table.State() != "setting" {
+ t.Fatalf("house should preview, not settle: preview=%v phase=%s", snap.Preview, table.State())
+ }
+ // set locks and, for a lone bettor, settles the round.
+ g.executeCommand(sess, "set", nil, "set")
+ if table.State() != "waiting" {
+ t.Fatalf("set did not settle the round: %s", table.State())
+ }
+}
+
+func TestPaiGowAutosetConfigSkipsPreview(t *testing.T) {
+ g, sess, conn := casinoCrapsGame(t, "name: Pai Gow\ncasino_tables:\n - id: fortune\n game: paigow\n min_bet: 5\n max_bet: 1000\n pai_gow:\n autoset: true\n")
+ g.executePlay(sess, []string{"pai"}, "play pai")
+ g.executeBet(sess, []string{"20"}, "bet 20")
+ conn.data = nil
+ g.executeCommand(sess, "house", nil, "house")
+ text := stripANSI(string(conn.data))
+ if strings.Contains(text, "arranged hand") {
+ t.Fatalf("autoset showed the arrangement preview:\n%s", text)
+ }
+ if !strings.Contains(text, "DEALER WINS") && !strings.Contains(text, "YOU WIN") && !strings.Contains(text, "PUSH") {
+ t.Fatalf("autoset did not reach the showdown:\n%s", text)
+ }
+}
+
func TestPaiGowFeltAndJokerAllModes(t *testing.T) {
store := cardart.Load("../../data/cards.yaml")
for _, size := range []int{1, 5, 7, 9} {
@@ -228,8 +388,10 @@ func TestPaiGowStopMidRoundQueuesDeparture(t *testing.T) {
if _, playing := g.Casino.Participation(sess.Player.Name); !playing {
t.Fatal("stop mid-round removed the seat too early")
}
- // Finishing the hand applies the queued departure.
+ // Finishing the hand applies the queued departure. Autoset is off, so
+ // house arranges and set commits.
g.executeCommand(sess, "house", nil, "house")
+ g.executeCommand(sess, "set", nil, "set")
if _, playing := g.Casino.Participation(sess.Player.Name); playing {
t.Fatal("queued stop did not leave after the round")
}
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")
diff --git a/internal/game/render_video_poker.go b/internal/game/render_video_poker.go
index b8baef8..b8262c8 100644
--- a/internal/game/render_video_poker.go
+++ b/internal/game/render_video_poker.go
@@ -87,43 +87,20 @@ func renderPokerCards(cards []casino.CardView, held *[5]bool, mode string, unico
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, " "))
- }
+ var labels []string
+ minWidth := 0
if held != nil {
- labels := make([]string, len(cards))
+ labels = make([]string, len(cards))
+ minWidth = 6
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])
+ labels[i] = text
}
- lines = append(lines, strings.Join(labels, " "))
}
- return lines
+ return renderCardRow(cards, labels, minWidth, mode, unicode, size, style, store)
}
func renderVideoPoker(s *casino.VideoPokerSnapshot, mode string, unicode bool, size int, style string, store *cardart.Store) string {