diff options
Diffstat (limited to 'internal/casino/craps_projection_test.go')
| -rw-r--r-- | internal/casino/craps_projection_test.go | 83 |
1 files changed, 83 insertions, 0 deletions
diff --git a/internal/casino/craps_projection_test.go b/internal/casino/craps_projection_test.go new file mode 100644 index 0000000..be48691 --- /dev/null +++ b/internal/casino/craps_projection_test.go @@ -0,0 +1,83 @@ +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{} + for _, wager := range seat.Wallet.(*testWallet).open { + wallet.open = append(wallet.open, wager) + } + 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 +} |
