package casino import ( "fmt" "testing" ) // TestCrapsProjectionMatchesSettlement pins the player-facing roll table to // the real settlement path: for every pair, at several points, the projected // net equals what resolveRoll would actually pay. func TestCrapsProjectionMatchesSettlement(t *testing.T) { tb := crapsFixture() tb.join("alice") w := &testWallet{chips: 10000} tb.bet("alice", "pass", 10, w) tb.bet("alice", "field", 10, w) tb.bet("alice", "hard 6", 10, w) tb.bet("alice", "place 4", 10, w) tb.bet("alice", "hop 2 3", 10, w) tb.bet("alice", "horn", 4, w) tb.resolveRoll(3, 3) tb.bet("alice", "odds pass", 20, w) tb.bet("alice", "dontcome", 10, w) tb.bet("alice", "fire", 1, w) for _, point := range []int{0, 4, 6, 10} { tb.point = point for a := 1; a <= 6; a++ { for b := 1; b <= 6; b++ { projected := 99999 for _, row := range tb.projectionFor(tb.players.get("alice")) { if row.Total == a+b && row.Rolls == fmt.Sprintf("%d+%d", min(a, b), max(a, b)) { projected = row.Net } } settled := tb.simulateNet("alice", a, b) if projected != settled { t.Fatalf("pair %d+%d point %d: projected %d, settled %d", a, b, point, projected, settled) } } } } } // simulateNet resolves one pair on a deep copy of the table (fresh test // wallets) and returns alice's true net through the ordinary settlement path. func (t *crapsTable) simulateNet(name string, a, b int) int { clone := &crapsTable{tableCore: newTableCore[*crapsPlayer](&tableBase{RoomID: t.RoomID, Config: t.Config}), rng: t.rng} wallets := map[string]*testWallet{} for _, seat := range t.players.ordered() { wallet := &testWallet{} wallet.open = append(wallet.open, seat.Wallet.(*testWallet).open...) wallets[seat.Name] = wallet cp := &crapsPlayer{Participant: Participant{Name: seat.Name, Connected: true, HasBet: seat.HasBet, Wallet: wallet}} for _, bet := range seat.bets { cp.bets = append(cp.bets, &crapsBet{kind: bet.kind, number: bet.number, stake: bet.stake, odds: bet.odds}) } clone.players.add(seat.Name, cp) } clone.point = t.point clone.made = map[int]bool{} for n := range t.made { clone.made[n] = true } clone.seen = map[int]bool{} for n := range t.seen { clone.seen[n] = true } before := walletWealth(wallets[name]) clone.resolveRoll(a, b) return walletWealth(wallets[name]) - before } // walletWealth is cash in hand plus the cash value of every pending stake, // so a resolved win/loss reports true settlement profit. func walletWealth(w *testWallet) int { pending := 0 for _, wager := range w.open { pending += wager.Total } return w.chips + w.paid + pending }