aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/craps_projection_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/casino/craps_projection_test.go')
-rw-r--r--internal/casino/craps_projection_test.go83
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
+}