aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/baccarat_variants_test.go
blob: 094412e453bb99d0060f4946ead8a5728f0ead85 (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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package casino

import (
	"strings"
	"testing"
)

func baccaratCards(ranks string) []card {
	var cards []card
	for _, rank := range strings.Fields(ranks) {
		cards = append(cards, card{rank: rank})
	}
	return cards
}

func TestBaccaratVariantSettlement(t *testing.T) {
	cases := []struct {
		name, variant, spot, player, banker, result string
		bet, payout                                 int
	}{
		{"commission six", BaccaratCommission, "banker", "A A 2", "3 3", "win", 100, 195},
		{"super six two cards", BaccaratSuper6, "banker", "A A 2", "3 3", "win", 100, 150},
		{"super six three cards", BaccaratSuper6, "banker", "A A 2", "A A 4", "win", 100, 150},
		{"super six odd stake", BaccaratSuper6, "banker", "A A 2", "3 3", "win", 3, 4},
		{"super six tiny stake", BaccaratSuper6, "banker", "A A 2", "3 3", "win", 1, 1},
		{"super six other win", BaccaratSuper6, "banker", "A A 2", "3 4", "win", 100, 200},
		{"six tied banker", BaccaratSuper6, "banker", "2 4", "3 3", "push", 100, 100},
		{"six tied tie wager", BaccaratSuper6, "tie", "2 4", "3 3", "win", 100, 900},
		{"six loses", BaccaratSuper6, "banker", "3 4", "3 3", "loss", 100, 0},
		{"ez dragon banker", BaccaratEZ, "banker", "A A 2", "A A 5", "push", 100, 100},
		{"ez dragon player", BaccaratEZ, "player", "A A 2", "A A 5", "loss", 100, 0},
		{"ez dragon tie", BaccaratEZ, "tie", "A A 2", "A A 5", "loss", 100, 0},
		{"ez two card seven", BaccaratEZ, "banker", "A A 2", "3 4", "win", 100, 200},
		{"ez other three card", BaccaratEZ, "banker", "A A 2", "A A 4", "win", 100, 200},
		{"ez seven loses", BaccaratEZ, "banker", "A A 6", "A A 5", "loss", 100, 0},
		{"ez seven ties", BaccaratEZ, "tie", "3 4", "A A 5", "win", 100, 900},
		{"player pair on tie", BaccaratCommission, "player_pair", "3 3", "2 4", "win", 10, 120},
		{"banker pair loses main hand", BaccaratCommission, "banker_pair", "4 5", "3 3", "win", 10, 120},
		{"faces not rank pair", BaccaratCommission, "player_pair", "K Q 6", "3 4", "loss", 10, 0},
		{"third card not pair", BaccaratCommission, "player_pair", "A 2 A", "3 4", "loss", 10, 0},
		{"dragon wins despite main push", BaccaratEZ, "dragon_7", "A A 2", "A A 5", "win", 10, 410},
		{"dragon not two card", BaccaratEZ, "dragon_7", "A A 2", "3 4", "loss", 10, 0},
		{"dragon not tie", BaccaratEZ, "dragon_7", "3 4", "A A 5", "loss", 10, 0},
		{"panda win", BaccaratEZ, "panda_8", "A A 6", "A A 5", "win", 10, 260},
		{"panda not natural", BaccaratEZ, "panda_8", "4 4", "3 4", "loss", 10, 0},
		{"panda not tie", BaccaratEZ, "panda_8", "A A 6", "A A 6", "loss", 10, 0},
		{"super6 side two card", BaccaratSuper6, "super_6", "A A 2", "3 3", "win", 10, 130},
		{"super6 side three card", BaccaratSuper6, "super_6", "A A 2", "A A 4", "win", 10, 130},
		{"super6 side tie", BaccaratSuper6, "super_6", "2 4", "3 3", "loss", 10, 0},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			rules := BaccaratConfig{Variant: tc.variant, PairBets: true, Dragon7: true, Panda8: true, Super6Bet: true}
			result, payout, err := settleBaccaratHand(tc.spot, tc.bet, baccaratCards(tc.player), baccaratCards(tc.banker), rules)
			if err != nil || result != tc.result || payout != tc.payout {
				t.Fatalf("got %s/%d/%v; want %s/%d", result, payout, err, tc.result, tc.payout)
			}
		})
	}
	_, _, err := settleBaccaratHand("dragon_7", MaxCredits, baccaratCards("A A 2"), baccaratCards("A A 5"), BaccaratConfig{Dragon7: true})
	if err == nil {
		t.Fatal("optional payout overflow accepted")
	}
}

func TestBaccaratVariantValidation(t *testing.T) {
	for _, variant := range []string{"", BaccaratCommission, BaccaratSuper6, BaccaratEZ} {
		cfg := TableConfig{ID: "test", Game: GameBaccarat, Baccarat: BaccaratConfig{Variant: variant}}
		if err := cfg.Validate(); err != nil {
			t.Fatal(err)
		}
		if err := cfg.Normalize().Validate(); err != nil {
			t.Fatal(err)
		}
	}
	for _, rules := range []BaccaratConfig{{Variant: "typo"}, {Variant: BaccaratSuper6, BankerPayout: .95}, {Variant: BaccaratEZ, BankerPayout: .98}, {BankerPayout: 1}} {
		if err := (TableConfig{ID: "test", Game: GameBaccarat, Baccarat: rules}).Validate(); err == nil {
			t.Fatalf("accepted %+v", rules)
		}
	}
	for _, rules := range []BaccaratConfig{{Variant: BaccaratEZ}, {PairBets: true}, {Dragon7: true}, {Panda8: true}, {Super6Bet: true}} {
		if err := (TableConfig{ID: "test", Game: GameBlackjack, Baccarat: rules}).Validate(); err == nil {
			t.Fatalf("foreign rule accepted: %+v", rules)
		}
	}
}

func TestBaccaratEZLiveSettlementAndOptionalMenus(t *testing.T) {
	cfg := TableConfig{ID: "ez", Game: GameBaccarat, Baccarat: BaccaratConfig{Variant: BaccaratEZ, Dragon7: true}}
	table := newBaccaratTestTable(cfg)
	events := table.join("alice")
	rulesView := false
	for _, e := range events {
		if view, ok := e.Snapshot.(*BaccaratRulesView); ok {
			rulesView = true
			if !view.Config.Baccarat.Dragon7 {
				t.Fatal("rules felt missing the enabled Dragon 7 wager")
			}
		}
	}
	if !rulesView {
		t.Fatal("join missing rules felt")
	}
	table.join("bob")
	forceShoe(table, baccaratCards("A A A A 2 5")...)
	wa, wb := &testWallet{credits: 100}, &testWallet{credits: 100}
	table.bet("alice", "banker", 10, wa)
	events = table.bet("bob", "dragon_7", 10, wb)
	if wa.paid != 10 || wb.paid != 410 {
		t.Fatalf("banker=%d dragon=%d", wa.paid, wb.paid)
	}
	if !hasMessage(events, "Banker bets push; Player bets lose") {
		t.Fatal("special outcome not explained")
	}
	table = newBaccaratTestTable(TableConfig{ID: "plain", Game: GameBaccarat})
	table.join("alice")
	wallet := &testWallet{credits: 100}
	events = table.bet("alice", "dragon_7", 10, wallet)
	if wallet.credits != 100 || table.players.get("alice").HasBet || !hasMessage(events, "Available: player|banker|tie") {
		t.Fatal("disabled optional bet accepted")
	}
}

func TestBaccaratRuleCard(t *testing.T) {
	for _, variant := range []string{BaccaratCommission, BaccaratSuper6, BaccaratEZ} {
		cfg := TableConfig{ID: "test", Game: GameBaccarat, Baccarat: BaccaratConfig{Variant: variant, Decks: 6, TiePayout: 9}}
		text := cfg.BaccaratRulesText()
		for _, want := range []string{"Number of decks: 6", "Tie pays 9:1", "Player pair (player_pair): Not offered", "Dragon 7 (dragon_7): Not offered", "Panda 8 (panda_8): Not offered", "Super 6 bet (super_6): Not offered", "One per player per round"} {
			if !strings.Contains(text, want) {
				t.Errorf("missing %q: %s", want, text)
			}
		}
		switch variant {
		case BaccaratCommission:
			if !strings.Contains(text, "commission 5%") {
				t.Fatal(text)
			}
		case BaccaratSuper6:
			if !strings.Contains(text, "winning 6 pays 1:2") {
				t.Fatal(text)
			}
		case BaccaratEZ:
			if !strings.Contains(text, "winning three-card 7 pushes") {
				t.Fatal(text)
			}
		}
	}
}