diff options
Diffstat (limited to 'internal/game')
| -rw-r--r-- | internal/game/casino.go | 48 | ||||
| -rw-r--r-- | internal/game/cmd_registry.go | 2 | ||||
| -rw-r--r-- | internal/game/pai_gow_test.go | 170 | ||||
| -rw-r--r-- | internal/game/render_pai_gow.go | 205 | ||||
| -rw-r--r-- | internal/game/render_video_poker.go | 35 |
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 { |
