package casino import "testing" // RNG seams: outcomes must not be replayable from a clock, while tests keep // a deterministic, injectable seed. func TestNewManagerSeedIsADeterministicSeam(t *testing.T) { cfg := MachineConfig{ID: "slots", Game: GameSlots} same := []*Machine{ NewManager(7).EnsureMachine(1, cfg), NewManager(7).EnsureMachine(1, cfg), } other := NewManager(8).EnsureMachine(1, cfg) first := machineDraws(same[0]) if got := machineDraws(same[1]); !sameDraws(first, got) { t.Fatalf("the same seed produced different slot streams: %v vs %v", first, got) } if got := machineDraws(other); sameDraws(first, got) { t.Fatalf("different seeds produced the same slot stream: %v", got) } } func TestNewManagerEntropySeedsFromCryptoRand(t *testing.T) { if seed := entropySeed(); seed < 0 { t.Fatalf("entropy seed %d is negative", seed) } seen := make(map[int64]bool) for i := 0; i < 4; i++ { m := NewManagerEntropy() if m.seed < 0 { t.Fatalf("NewManagerEntropy seed %d is negative", m.seed) } seen[m.seed] = true } if len(seen) < 2 { t.Fatalf("NewManagerEntropy reused one seed across %d managers", len(seen)) } } // Each game gets its own stream seeded from the manager's: no two tables or // machines may share reel results. func TestManagerGamesKeepIndependentStreams(t *testing.T) { m := NewManager(3) first := m.EnsureMachine(1, MachineConfig{ID: "slots_a", Game: GameSlots}) second := m.EnsureMachine(1, MachineConfig{ID: "slots_b", Game: GameSlots}) if first == nil || second == nil { t.Fatal("machines were not created") } if sameDraws(machineDraws(first), machineDraws(second)) { t.Fatal("two machines share one RNG stream") } } // machineDraws consumes n values from a game's own stream. func machineDraws(m *Machine) []int64 { draws := make([]int64, 8) for i := range draws { draws[i] = m.rng.Int63() } return draws } func sameDraws(a, b []int64) bool { if len(a) != len(b) { return false } for i := range a { if a[i] != b[i] { return false } } return true }