aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/blackjack_rules.go
blob: 8bf63a811e7a21a1d6357a73bc479707e909f3aa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
package casino

import (
	"fmt"
	"strings"
)

// BlackjackRulesText is the table's rule card, built from effective defaults.
// Keep fixed engine rules here too: players should not have to guess them.
func (c TableConfig) BlackjackRulesText() string {
	c = c.Normalize()
	b := c.Blackjack
	yesNo := func(v bool) string {
		if v {
			return "Yes"
		}
		return "No"
	}
	dealer := "stands on"
	if b.DealerHitsSoft17() {
		dealer = "hits"
	}
	surrender := "Not offered"
	if b.AllowsSurrender() {
		surrender = "Late (original two-card hand only)"
	}
	split := "Identical ranks only"
	if b.AllowsUnlikeTenSplit() {
		split = "Identical ranks or any two ten-valued cards"
	}
	return strings.Join([]string{
		fmt.Sprintf("Blackjack table %s — rules", c.ID),
		fmt.Sprintf("Number of decks: %d", b.Decks),
		"Blackjack pays " + payoutRatio(b.BlackjackPayout),
		"Dealer " + dealer + " soft 17",
		"Dealer hole card / peek: Yes; blackjack checked before player actions (after insurance)",
		"Ordinary wins pay 1:1; ties push; player and dealer blackjack push",
		"Double down: Any initial two cards; one additional card only",
		"Double after split: " + yesNo(b.AllowsDoubleAfterSplit()),
		fmt.Sprintf("Maximum split hands: %d (including original hand)", b.MaxSplitHands),
		"Re-splitting: " + yesNo(b.MaxSplitHands > 2) + " (within maximum hands)",
		"Re-splitting aces: " + yesNo(b.AllowsResplitAces()) + " (within maximum hands)",
		"Split pairs: " + split,
		"Split aces: One card per ace; no hitting or doubling",
		"Split-hand 21 pays 1:1, except the optional bonus below",
		"Split-and-doubled 21 receives blackjack payout: " + yesNo(b.CountsDoubleSplitBlackjack()),
		"Surrender: " + surrender,
		"Insurance: Offered against an ace; costs half the original bet; pays " + payoutRatio(b.InsurancePayout),
		"Fractional credits: Rounded down (including insurance stake and surrender return)",
		fmt.Sprintf("Shuffle threshold: %g%% of shoe remaining; may shuffle earlier for round coverage", b.ShuffleAt*100),
		"Shoe exhaustion: Reshuffle mid-round if necessary",
		"Bet limits: " + betRangeText(c.MinBet, c.MaxBet) + " credits",
		fmt.Sprintf("Seats: %d; betting window: %d ticks; action window: %d ticks", c.MaxPlayers, c.BettingTicks, b.ActionTicks),
	}, "\n")
}

func payoutRatio(p float64) string {
	switch p {
	case 1.5:
		return "3:2"
	case 1.2:
		return "6:5"
	default:
		return fmt.Sprintf("%g:1", p)
	}
}