diff options
| author | historia <[not public]> | 2026-09-24 20:06:41 -0400 |
|---|---|---|
| committer | historia <[not public]> | 2026-09-24 20:07:31 -0400 |
| commit | 13db3e50cb6e29eedbf25416e5284bf08b0adefb (patch) | |
| tree | d5e9b99030b25cc7fc102288b694c133f8832f84 /internal/casino/table_ux_test.go | |
| parent | d114c747a7672c7e4daab3a5b5f1b2f066ebc912 (diff) | |
| download | thehouseoficarus-13db3e50cb6e29eedbf25416e5284bf08b0adefb.tar.gz | |
feat: casino table ux: aliases, rebet hints, deadline notifications, spectator mode
Diffstat (limited to 'internal/casino/table_ux_test.go')
| -rw-r--r-- | internal/casino/table_ux_test.go | 181 |
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) + } +} |
