Files
BlockSeasons/lib/core/rng.dart
T
airkjw 0210c14858 Add pure-Dart engine core: RNG, grid, placement, line clear, scoring, piece generator
PCG32 seeded RNG; immutable 8x8 GridState with occupancy bitmask;
placement legality + anyPlacementExists; simultaneous row/col clears
with single-count gem credit; combo scoring with one-move grace;
weighted-bag generator with pity bias and depth-3 solvability nudge.
All TDD, 51 tests green.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 13:05:55 +09:00

49 lines
1.6 KiB
Dart

/// Deterministic seedable PRNG for the game engine.
///
/// dart:math's Random is implementation-unspecified across platforms; piece
/// sequences must be byte-identical on iOS, Android, and in tests, so we own
/// the algorithm (PCG32, https://www.pcg-random.org).
class SeededRng {
SeededRng(int seed, [int sequence = 0])
: _state = 0,
_inc = ((sequence << 1) | 1) {
_nextRaw();
_state = _state + seed;
_nextRaw();
}
// Dart native ints are 64-bit with wrapping arithmetic, which is exactly
// what the PCG32 state transition needs.
int _state;
final int _inc;
static const _multiplier = 6364136223846793005;
static const _mask32 = 0xFFFFFFFF;
/// One PCG32 step: 32-bit output via xorshift-high + random rotation.
int _nextRaw() {
final old = _state;
_state = old * _multiplier + _inc;
final xorshifted = (((old >>> 18) ^ old) >>> 27) & _mask32;
final rot = old >>> 59;
return ((xorshifted >>> rot) | (xorshifted << ((-rot) & 31))) & _mask32;
}
/// Uniform integer in [0, max), bias-free via rejection sampling.
int nextInt(int max) {
assert(max > 0);
final threshold = (0x100000000 - (0x100000000 % max)) & _mask32;
if (threshold == 0) return _nextRaw() % max;
while (true) {
final r = _nextRaw();
if (r < threshold) return r % max;
}
}
/// Uniform double in [0, 1).
double nextDouble() => _nextRaw() / 4294967296.0;
/// Derives an independent deterministic stream (e.g. per retry attempt).
SeededRng fork(int streamId) => SeededRng(_nextRaw() ^ streamId, streamId);
}