aboutsummaryrefslogtreecommitdiff
path: root/internal/casino/craps_projection_test.go
blob: be48691df8a096e02dc587c75f9e8073b3c89737 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
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
}