66 lines
1.3 KiB
Go
66 lines
1.3 KiB
Go
package game
|
|
|
|
import (
|
|
"crypto/rand"
|
|
"encoding/binary"
|
|
"math/bits"
|
|
)
|
|
|
|
// RNG is a random number generator for shotgun.
|
|
//
|
|
// Currently xoshiro256**. Subject to change.
|
|
type RNG struct {
|
|
w, x, y, z uint64
|
|
}
|
|
|
|
// NewRNG produces a new, uniquely seeded RNG.
|
|
func NewRNG() RNG {
|
|
r := RNG{}
|
|
b := make([]byte, 8*4)
|
|
// Loop to ensure the state is never all-zero, which is an invalid state
|
|
// for xoshiro.
|
|
for r.w == 0 && r.x == 0 && r.y == 0 && r.z == 0 {
|
|
rand.Read(b)
|
|
r.w = binary.LittleEndian.Uint64(b)
|
|
r.x = binary.LittleEndian.Uint64(b[8:])
|
|
r.y = binary.LittleEndian.Uint64(b[16:])
|
|
r.z = binary.LittleEndian.Uint64(b[24:])
|
|
}
|
|
return r
|
|
}
|
|
|
|
// Uint64 produces a 64-bit pseudo-random value.
|
|
func (rng *RNG) Uint64() uint64 {
|
|
w, x, y, z := rng.w, rng.x, rng.y, rng.z
|
|
r := bits.RotateLeft64(x*5, 7) * 9
|
|
t := x << 17
|
|
y ^= w
|
|
z ^= x
|
|
x ^= y
|
|
w ^= z
|
|
y ^= t
|
|
z = bits.RotateLeft64(z, 45)
|
|
rng.w, rng.x, rng.y, rng.z = w, x, y, z
|
|
return r
|
|
}
|
|
|
|
// Intn produces an int in [0, n). Panics if n <= 0.
|
|
func (rng *RNG) Intn(n int) int {
|
|
if n <= 0 {
|
|
panic("shotgun: rng.Intn max below zero")
|
|
}
|
|
bad := ^uint(0) - ^uint(0)%uint(n)
|
|
x := uint(rng.Uint64())
|
|
for x > bad {
|
|
x = uint(rng.Uint64())
|
|
}
|
|
return int(x % uint(n))
|
|
}
|
|
|
|
func ShuffleSlice[E any, S ~[]E](rng *RNG, s S) {
|
|
for i := len(s) - 1; i > 0; i-- {
|
|
j := rng.Intn(i + 1)
|
|
s[i], s[j] = s[j], s[i]
|
|
}
|
|
}
|