aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/table_ux_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/casino/table_ux_test.go')
-rw-r--r--internal/casino/table_ux_test.go181
1 files changed, 181 insertions, 0 deletions
diff --git a/internal/casino/table_ux_test.go b/internal/casino/table_ux_test.go
new file mode 100644
index 0000000..7f93951
--- /dev/null
+++ b/internal/casino/table_ux_test.go
@@ -0,0 +1,181 @@
+package casino
+
+import (
+ "strings"
+ "testing"
+)
+
+func uxBlackjack() *blackjackTable {
+ t := newBlackjackTable(1, TableConfig{ID: "bj", Game: GameBlackjack, Blackjack: BlackjackConfig{ActionTicks: 8}}, newTestRand())
+ t.phase = blackjackPlaying
+ t.dealer.cards = []card{{rank: "10", suit: "spades"}, {rank: "7", suit: "hearts"}}
+ t.players.add("alice", &blackjackPlayer{Participant: Participant{Name: "alice", Connected: true, HasBet: true, Wager: Wager{Total: 10}}, hands: []*blackjackHand{{bet: 10, cards: []card{{rank: "10"}, {rank: "6"}}}}})
+ t.prompt()
+ return t
+}
+
+func TestTableStatusDoesNotExtendAnyDeadlineOrRevealHoleCard(t *testing.T) {
+ table := uxBlackjack()
+ table.tick()
+ beforeTimer, beforeCeiling := table.timer, table.ceiling
+ for i := 0; i < 3; i++ {
+ events := table.status("alice")
+ snap := events[0].Snapshot.(*BlackjackSnapshot)
+ if !snap.Dealer[0].Hidden || snap.Dealer[0].Rank != "?" {
+ t.Fatal("status revealed hole card")
+ }
+ if !snap.Players[0].Active || snap.Players[0].Hand != 1 || snap.Players[0].Bet != 10 {
+ t.Fatalf("missing active hand: %+v", snap.Players[0])
+ }
+ }
+ if table.timer != beforeTimer || table.ceiling != beforeCeiling {
+ t.Fatal("status extended deadline")
+ }
+}
+
+func TestBlackjackWarnsOnceThenAutoStands(t *testing.T) {
+ for _, multi := range []bool{false, true} {
+ table := uxBlackjack()
+ if multi {
+ table.players.add("bob", &blackjackPlayer{Participant: Participant{Name: "bob", Connected: true, HasBet: true}, hands: []*blackjackHand{{bet: 10, stood: true, cards: []card{{rank: "10"}, {rank: "8"}}}}})
+ }
+ warnings := 0
+ timeout := false
+ for i := 0; i < 81; i++ {
+ for _, e := range table.tick() {
+ if e.PrivateTo == "alice" && strings.Contains(e.Message, "No response means stand") {
+ warnings++
+ if e.Color != "casino_warning" {
+ t.Fatal("warning not colored")
+ }
+ }
+ if strings.Contains(e.Message, "standing.") {
+ timeout = true
+ if e.Color != "casino_warning" {
+ t.Fatal("timeout not colored")
+ }
+ }
+ }
+ }
+ if warnings != 1 || !timeout {
+ t.Fatalf("multi=%v warnings=%d timeout=%v", multi, warnings, timeout)
+ }
+ }
+}
+
+func TestInsuranceRequiresExplicitDecisionAndWarns(t *testing.T) {
+ table := uxBlackjack()
+ table.phase = blackjackInsurance
+ table.timer.start(8)
+ before := len(table.players.get("alice").hands[0].cards)
+ events := table.action("alice", "hit")
+ if !hasMessage(events, "No card action") || table.players.get("alice").declinedInsurance || len(table.players.get("alice").hands[0].cards) != before {
+ t.Fatal("hit silently declined or took card")
+ }
+ warnings := 0
+ for i := 0; i < 8; i++ {
+ for _, e := range table.tick() {
+ if strings.Contains(e.Message, "Insurance closes") {
+ warnings++
+ if e.Color != "casino_warning" {
+ t.Fatal("insurance warning color")
+ }
+ }
+ }
+ }
+ if warnings != 1 || table.phase != blackjackPlaying {
+ t.Fatalf("warnings=%d phase=%s", warnings, table.phase)
+ }
+}
+
+func TestBettingWarningOnlyTargetsPlayersWhoNeedToBet(t *testing.T) {
+ table := newBaccaratTestTable(TableConfig{ID: "bac", Game: GameBaccarat, BettingTicks: 8})
+ for _, n := range []string{"alice", "bob", "carol"} {
+ table.join(n)
+ }
+ table.action("carol", "sitout")
+ table.bet("alice", "banker", 10, &testWallet{credits: 100})
+ warnings := 0
+ for i := 0; i < 8; i++ {
+ for _, e := range table.tick() {
+ if e.Color == "casino_warning" {
+ warnings++
+ if e.PrivateTo != "bob" {
+ t.Fatalf("warning sent to %s", e.PrivateTo)
+ }
+ }
+ }
+ }
+ if warnings != 1 {
+ t.Fatalf("warnings=%d", warnings)
+ }
+}
+
+func TestSitoutDoesNotBlockOthersAndBetRejoins(t *testing.T) {
+ table := newBaccaratTestTable(TableConfig{ID: "bac", Game: GameBaccarat})
+ table.join("alice")
+ table.join("bob")
+ table.bet("alice", "banker", 10, &testWallet{credits: 100})
+ events := table.action("bob", "sitout")
+ if !hasMessage(events, "New baccarat hand!") || !table.players.get("bob").SittingOut {
+ t.Fatal("sitout held up round")
+ }
+ table.action("alice", "sitout")
+ events = table.bet("bob", "player", 10, &testWallet{credits: 100})
+ if !hasMessage(events, "New baccarat hand!") || table.players.get("bob").SittingOut {
+ t.Fatal("bet failed to rejoin")
+ }
+}
+
+func TestQueuedLeaveAndSitoutPreserveCommittedBlackjackHands(t *testing.T) {
+ for _, action := range []string{"leave_after", "sitout"} {
+ table := uxBlackjack()
+ hand := table.players.get("alice").hands[0]
+ table.action("alice", action)
+ if !table.hasParticipant("alice") || hand.stood || hand.surrendered {
+ t.Fatalf("%s altered committed hand", action)
+ }
+ events := table.action("alice", "stand")
+ if !hasMessage(events, "Net -10 credits") {
+ t.Fatalf("missing settlement: %+v", events)
+ }
+ if action == "leave_after" && table.hasParticipant("alice") {
+ t.Fatal("queued leave retained seat")
+ }
+ if action == "sitout" && !table.players.get("alice").SittingOut {
+ t.Fatal("queued sitout not applied")
+ }
+ }
+}
+
+func TestSplitAndInsuranceRoundNetIncludesEveryStake(t *testing.T) {
+ table := uxBlackjack()
+ p := table.players.get("alice")
+ p.hands = []*blackjackHand{{bet: 20, stood: true, cards: []card{{rank: "10"}, {rank: "10"}}}, {bet: 10, bust: true, cards: []card{{rank: "10"}, {rank: "10"}, {rank: "5"}}}}
+ p.insurance = 5
+ events := table.settle()
+ for _, text := range []string{"Hand 1/2 | WIN | Net +20 credits | 40 returned", "Hand 2/2 | LOSS | Net -10 credits", "INSURANCE | Net -5 credits", "Round complete | Net +5 credits"} {
+ if !hasMessage(events, text) {
+ t.Fatalf("missing %q in %+v", text, events)
+ }
+ }
+}
+
+func TestRebetMenusAndMidRoundJoin(t *testing.T) {
+ table := uxBlackjack()
+ events := table.join("bob")
+ if !hasMessage(events, "Round in progress") {
+ t.Fatal("mid-round join offered betting")
+ }
+ bac := newBaccaratTestTable(TableConfig{ID: "bac", Game: GameBaccarat})
+ bac.join("alice")
+ first := bac.menuEvent("alice")
+ if first.Menu.Commands[0].Command == "bet <amount>" {
+ t.Fatal("first baccarat bet omitted target")
+ }
+ bac.bet("alice", "banker", 25, &testWallet{credits: 100})
+ menu := bac.menuEvent("alice").Menu
+ if !menu.Repeat || !strings.Contains(menu.Commands[0].Desc, "Repeat 25 on banker") {
+ t.Fatalf("missing rebet details: %+v", menu)
+ }
+}