diff options
Diffstat (limited to 'internal/game/render_blackjack_felt.go')
| -rw-r--r-- | internal/game/render_blackjack_felt.go | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/internal/game/render_blackjack_felt.go b/internal/game/render_blackjack_felt.go new file mode 100644 index 0000000..41a5382 --- /dev/null +++ b/internal/game/render_blackjack_felt.go @@ -0,0 +1,137 @@ +package game + +import ( + "fmt" + "strconv" + + "thehouseoficarus/internal/casino" +) + +// A blackjack felt: the classic centre-display phrases a real table prints +// (blackjack payout, dealer draw rule, insurance), then the remaining +// configurable rules arranged in labelled boxes. It reuses the craps felt +// canvas and palette: fixed 77-column art, dark green baize, gold headings. +// Fixed engine behaviour (peek timing, rounding) is left off the felt. +func renderBlackjackRulesFelt(cfg casino.TableConfig, mode string, unicode bool) string { + b := cfg.Blackjack.Normalize() + c := newCrapsCanvas(21) + // Centre display: the phrases printed on every real blackjack table. + c.box(0, 0, 76, 5) + c.center(1, 75, 2, "BLACKJACK PAYS "+ratioText(b.BlackjackPayout), crapsGold) + dealer := "DEALER MUST STAND ON 17 AND DRAW TO 16" + if b.DealerHitsSoft17() { + dealer = "DEALER HITS SOFT 17" + } + c.center(1, 75, 3, dealer, crapsInk) + c.center(1, 75, 4, "INSURANCE PAYS "+ratioText(b.InsurancePayout), crapsGold) + // Four labelled rule boxes share edges with the centre display above. + boxY := [4][2]int{{5, 12}, {5, 12}, {12, 20}, {12, 20}} + xs := [4][2]int{{0, 38}, {38, 76}, {0, 38}, {38, 76}} + for i := range boxY { + c.box(xs[i][0], boxY[i][0], xs[i][1], boxY[i][1]) + } + titles := [4]string{"THE SHOE", "PAYS", "DOUBLE & SPLIT", "HOUSE"} + for i, title := range titles { + c.center(xs[i][0]+1, xs[i][1]-1, boxY[i][0]+1, title, crapsGold) + } + rows := [4][][2]string{ + { + {"DECKS", fmt.Sprintf("%d", b.Decks)}, + {"SHUFFLE AT", fmt.Sprintf("%g%% REMAINING", b.ShuffleAt*100)}, + {"SHOE LOW", "RESHUFFLE MID-ROUND"}, + {"HOLE CARD", "YES (PEEK)"}, + {"", ""}, + }, + { + {"ORDINARY WIN", "1:1"}, + {"TIE", "PUSH"}, + {"BJ VS BJ", "PUSH"}, + {"SPLIT-HAND 21", "1:1"}, + {"SPLIT+DOUBLE 21", splitDouble21Text(b)}, + {"", ""}, + }, + { + {"DOUBLE", "ANY TWO, ONE CARD"}, + {"AFTER SPLIT", yesNoText(b.AllowsDoubleAfterSplit())}, + {"MAX HANDS", fmt.Sprintf("%d (RESPLIT %s)", b.MaxSplitHands, yesNoText(b.MaxSplitHands > 2))}, + {"RESPLIT ACES", yesNoText(b.AllowsResplitAces())}, + {"PAIRS", pairsText(b)}, + {"SPLIT ACES", "ONE CARD, NO HITS"}, + }, + { + {"SURRENDER", surrenderText(b)}, + {"INSURANCE COST", "HALF THE BET"}, + {"BET LIMITS", betLimitsText(cfg.MinBet, cfg.MaxBet)}, + {"SEATS", fmt.Sprintf("%d", cfg.MaxPlayers)}, + {"BETTING WINDOW", fmt.Sprintf("%d TICKS", cfg.BettingTicks)}, + {"ACTION WINDOW", fmt.Sprintf("%d TICKS", b.ActionTicks)}, + }, + } + for i, box := range rows { + for j, row := range box { + if row[0] == "" && row[1] == "" { + continue + } + c.bjRule(xs[i][0], xs[i][1], boxY[i][0]+2+j, row[0], row[1]) + } + } + return c.render(mode, unicode) +} + +// bjRule paints one "LABEL ..... VALUE" row inside a box: the label muted at +// the left margin, the value right-aligned to the box's inner right edge. +func (c *crapsCanvas) bjRule(x1, x2, y int, label, value string) { + c.text(x1+2, y, label, crapsMuted) + vx := x2 - 1 - len([]rune(value)) + if vx < x1+2+len([]rune(label))+1 { + vx = x1 + 2 + len([]rune(label)) + 1 + } + c.text(vx, y, value, crapsInk) +} + +// betLimitsText renders the wager range compactly so it fits inside a box. +func betLimitsText(minBet, maxBet int) string { + if maxBet == int(^uint(0)>>1) { + return fmt.Sprintf("%d+ CREDITS", minBet) + } + return fmt.Sprintf("%d-%d CREDITS", minBet, maxBet) +} + +func ratioText(p float64) string { + switch p { + case 1.5: + return "3 TO 2" + case 1.2: + return "6 TO 5" + case 2: + return "2 TO 1" + case 1: + return "1 TO 1" + default: + return strconv.FormatFloat(p, 'g', -1, 64) + " TO 1" + } +} +func splitDouble21Text(b casino.BlackjackConfig) string { + if b.CountsDoubleSplitBlackjack() { + return "BLACKJACK " + ratioText(b.BlackjackPayout) + } + return "1:1" +} +func yesNoText(v bool) string { + if v { + return "YES" + } + return "NO" +} +func pairsText(b casino.BlackjackConfig) string { + if b.AllowsUnlikeTenSplit() { + return "RANKS OR TENS" + } + return "IDENTICAL RANKS" +} +func surrenderText(b casino.BlackjackConfig) string { + if b.AllowsSurrender() { + return "LATE, TWO CARDS" + } + return "NOT OFFERED" +} |
