aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/baccarat_multi_wager_test.go
diff options
context:
space:
mode:
authorhistoria <[not public]>2026-10-02 01:54:37 -0400
committerhistoria <[not public]>2026-10-02 01:54:37 -0400
commitd3a305d1d7962f82af2397760cf8008758bddd1d (patch)
treef91f0deb342c20f2993e5366f772c3432648f426 /internal/casino/baccarat_multi_wager_test.go
parent8eea0e4e06a4aaa6a18edabd9897d3bea1ea2ca0 (diff)
downloadthehouseoficarus-d3a305d1d7962f82af2397760cf8008758bddd1d.tar.gz
feat: better 'list' bet table, multiple bets in baccarat
Diffstat (limited to 'internal/casino/baccarat_multi_wager_test.go')
-rw-r--r--internal/casino/baccarat_multi_wager_test.go118
1 files changed, 118 insertions, 0 deletions
diff --git a/internal/casino/baccarat_multi_wager_test.go b/internal/casino/baccarat_multi_wager_test.go
new file mode 100644
index 0000000..2a56e63
--- /dev/null
+++ b/internal/casino/baccarat_multi_wager_test.go
@@ -0,0 +1,118 @@
+package casino
+
+import "testing"
+
+// TestBaccaratSideBetsRideWithMain pins the multi-wager contract: a side bet
+// is committed before the main wager, both settle in the same round, and only
+// the main wager deals the cards.
+func TestBaccaratSideBetsRideWithMain(t *testing.T) {
+ cfg := TableConfig{ID: "baccarat", Game: GameBaccarat, Baccarat: BaccaratConfig{PairBets: true}}
+ table := newBaccaratTestTable(cfg)
+ table.join("alice")
+ forceShoe(table, baccaratCards("5 5 5 5 5 5")...)
+ w := &testWallet{credits: 1000}
+ if events := table.bet("alice", "player_pair", 10, w); hasMessage(events, "New baccarat hand!") {
+ t.Fatal("side bet alone dealt a round")
+ }
+ if table.players.get("alice").HasBet {
+ t.Fatal("side bet counted as the round's main wager")
+ }
+ events := table.bet("alice", "player", 10, w)
+ if !hasMessage(events, "New baccarat hand!") {
+ t.Fatalf("main wager did not deal: %+v", events)
+ }
+ // Player and banker tie at 5; the player main pushes and the player pair wins 11:1.
+ if w.paid != 130 {
+ t.Fatalf("paid=%d, want 130", w.paid)
+ }
+ var labels []string
+ for _, e := range events {
+ for _, line := range e.Settlement {
+ labels = append(labels, line.Label)
+ }
+ }
+ if len(labels) != 2 || labels[0] != "player" || labels[1] != "player_pair" {
+ t.Fatalf("settlement labels=%v, want [player player_pair]", labels)
+ }
+ // The snapshot carries both bets for the table view.
+ var snapshot *BaccaratSnapshot
+ for _, e := range events {
+ if s, ok := e.Snapshot.(*BaccaratSnapshot); ok && len(s.Bets) > 0 {
+ snapshot = s
+ }
+ }
+ if snapshot == nil || len(snapshot.Bets) != 2 ||
+ snapshot.Bets[0].Spot != "player" || snapshot.Bets[0].Amount != 10 ||
+ snapshot.Bets[1].Spot != "player_pair" || snapshot.Bets[1].Amount != 10 {
+ t.Fatalf("snapshot bets=%+v", snapshot)
+ }
+}
+
+// A side wager may not follow the main wager: the main is the deal trigger.
+func TestBaccaratSideBetAfterMainRefused(t *testing.T) {
+ table := newBaccaratTestTable(TableConfig{ID: "baccarat", Game: GameBaccarat, Baccarat: BaccaratConfig{PairBets: true}, MaxPlayers: 2})
+ table.join("alice")
+ table.join("bob")
+ w := &testWallet{credits: 1000}
+ table.bet("alice", "player", 10, w)
+ if events := table.bet("alice", "player_pair", 10, w); !hasMessage(events, "before your main wager") {
+ t.Fatalf("side bet after main accepted: %+v", events)
+ }
+}
+
+// A bare "bet" repeats the remembered side bets and the main wager together.
+func TestBaccaratRebetAll(t *testing.T) {
+ cfg := TableConfig{ID: "baccarat", Game: GameBaccarat, Baccarat: BaccaratConfig{PairBets: true}}
+ table := newBaccaratTestTable(cfg)
+ table.join("alice")
+ forceShoe(table, baccaratCards("5 5 5 5 5 5")...)
+ table.bet("alice", "player_pair", 10, &testWallet{credits: 1000})
+ table.bet("alice", "player", 10, &testWallet{credits: 1000})
+ forceShoe(table, baccaratCards("5 5 5 5 5 5")...)
+ w := &testWallet{credits: 1000}
+ events := table.rebetAll("alice", w)
+ if !hasMessage(events, "New baccarat hand!") || w.paid != 130 {
+ t.Fatalf("rebet all did not replay both wagers: paid=%d events=%+v", w.paid, events)
+ }
+}
+
+// unbet returns the main wager and every side bet in one step.
+func TestBaccaratUnbetReturnsAllWagers(t *testing.T) {
+ table := newBaccaratTestTable(TableConfig{ID: "baccarat", Game: GameBaccarat, Baccarat: BaccaratConfig{PairBets: true}})
+ table.join("alice")
+ w := &testWallet{credits: 1000}
+ table.bet("alice", "player_pair", 10, w)
+ table.bet("alice", "banker_pair", 15, w)
+ if events := table.unbetAllLocked("alice"); !hasMessage(events, "take back 25") {
+ t.Fatalf("unbet did not return both side stakes: %+v", events)
+ }
+ if w.paid != 25 || len(w.open) != 0 {
+ t.Fatalf("paid=%d open=%d, want 25/0", w.paid, len(w.open))
+ }
+}
+
+// A side-only seat is not part of the round: the timer returns the side stake
+// and the other seat's main wager still deals.
+func TestBaccaratSideOnlySeatRefundedOnTimeout(t *testing.T) {
+ cfg := TableConfig{ID: "baccarat", Game: GameBaccarat, Baccarat: BaccaratConfig{PairBets: true}, MaxPlayers: 2, BettingTicks: 2}
+ table := newBaccaratTestTable(cfg)
+ table.join("alice")
+ table.join("bob")
+ wa, wb := &testWallet{credits: 1000}, &testWallet{credits: 1000}
+ table.bet("alice", "player_pair", 10, wa)
+ table.bet("bob", "player", 10, wb)
+ forceShoe(table, baccaratCards("5 5 5 5 5 5")...)
+ var events []Event
+ for i := 0; i < 4 && !hasMessage(events, "New baccarat hand!"); i++ {
+ events = append(events, table.tick()...)
+ }
+ if !hasMessage(events, "New baccarat hand!") {
+ t.Fatalf("side-only seat held the round open: %+v", events)
+ }
+ if wa.paid != 10 {
+ t.Fatalf("alice side stake not returned: paid=%d", wa.paid)
+ }
+ if !hasMessage(events, "side bets is returned") {
+ t.Fatalf("missing side-bet refund notice: %+v", events)
+ }
+}