package casino import "testing" // A disconnect must only forfeit a hand that is actually in flight. A stake // placed in the betting window has had no card dealt for it, so refunding it // reveals nothing; destroying it there would let an ordinary transient TCP // drop swallow an undelt wager. // TestBlackjackPreDealDisconnectRefundsStake: alice bets, the betting window // is open (bob has not bet, no cards dealt), alice's socket drops. Her stake // comes back, the wager record is resolved, and the round that follows pays // nothing on top of the refund. func TestBlackjackPreDealDisconnectRefundsStake(t *testing.T) { table := newBlackjackTable(1, TableConfig{ID: "blackjack", Game: GameBlackjack, MaxPlayers: 2, BettingTicks: 3}, newTestRand()) table.join("alice") table.join("bob") wallet := &testWallet{credits: 100} table.bet("alice", "", 10, wallet) if table.phase != blackjackBetting { t.Fatalf("setup: expected the betting window (phase=%s)", table.phase) } if wallet.credits != 90 { t.Fatalf("setup: stake not committed (credits=%d)", wallet.credits) } events := table.markDisconnected("alice") if !hasMessage(events, "alice stands up") { t.Fatalf("missing disconnect event: %+v", events) } if got := wallet.credits + wallet.paid; got != 100 { t.Fatalf("pre-deal disconnect returned %d of 100; the undelt stake must be refunded", got) } if len(wallet.open) != 0 { t.Fatalf("refunded stake left outstanding: %+v", wallet.open) } // The window resolves with bob sitting out; nothing may pay the // refunded round afterwards. for i := 0; i < 10; i++ { table.tick() } if got := wallet.credits + wallet.paid; got != 100 { t.Fatalf("refunded pre-deal round paid again: balance %d", got) } if table.phase != blackjackWaiting { t.Fatalf("table did not reset after the window expired: phase=%s", table.phase) } } // TestBlackjackMidHandDisconnectForfeitsStake: the documented mid-hand // forfeit policy is unchanged — cards are face up, so the stake stays with // the house. func TestBlackjackMidHandDisconnectForfeitsStake(t *testing.T) { table := newBlackjackTable(1, TableConfig{ID: "blackjack", Game: GameBlackjack, MaxPlayers: 2}, newTestRand()) table.shoe = tableauShoe( card{rank: "10"}, card{rank: "7"}, // dealer card{rank: "10"}, card{rank: "8"}, // alice 18 card{rank: "10"}, card{rank: "6"}, // bob 16 ) wallet := &testWallet{credits: 100} table.join("alice") table.join("bob") table.bet("alice", "", 10, wallet) table.bet("bob", "", 10, &testWallet{credits: 100}) if table.phase != blackjackPlaying { t.Fatalf("setup: expected a dealt hand (phase=%s)", table.phase) } table.markDisconnected("alice") if wallet.credits != 90 || wallet.paid != 0 { t.Fatalf("mid-hand disconnect refunded a dealt hand: credits=%d paid=%d, want 90/0 (forfeit)", wallet.credits, wallet.paid) } } // TestBaccaratBettingWindowDisconnectRefundsStake: baccarat deals // synchronously once every connected seat has bet, so an outstanding wager // there has never had a card dealt for it either. func TestBaccaratBettingWindowDisconnectRefundsStake(t *testing.T) { table := newBaccaratTable(1, TableConfig{ID: "baccarat", Game: GameBaccarat, MaxPlayers: 2, BettingTicks: 3}, newTestRand()) table.join("alice") table.join("bob") wallet := &testWallet{credits: 100} table.bet("alice", baccaratSpotPlayer, 10, wallet) if table.phase != baccaratBetting { t.Fatalf("setup: expected the betting window (phase=%s)", table.phase) } table.markDisconnected("alice") if got := wallet.credits + wallet.paid; got != 100 { t.Fatalf("pre-deal baccarat disconnect returned %d of 100; want the stake refunded", got) } if len(wallet.open) != 0 { t.Fatalf("refunded baccarat stake left outstanding: %+v", wallet.open) } for i := 0; i < 10; i++ { table.tick() } if got := wallet.credits + wallet.paid; got != 100 { t.Fatalf("refunded pre-deal coup paid again: balance %d", got) } }