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
//! Pluggable RNG for game effects (shuffles, coin flips, dice rolls).
//!
//! By default, effects use the system's thread-local RNG. For parity testing,
//! a deterministic RNG (e.g. JavaRandom) can be injected to match Java Forge's
//! `MyRandom` consumption order exactly.
//!
//! # WASM Compatibility
//!
//! The `rand` crate 0.8+ supports WASM via `getrandom`. For browser WASM,
//! ensure the WASM entry point crate (wasm) includes:
//! ```toml
//! getrandom = { version = "0.2", features = ["js"] }
//! ```
//! This enables `thread_rng()` to work in browser environments.
use crate::ids::CardId;
/// Trait for game-level randomness, used by effect resolvers.
///
/// This abstraction lets parity tests inject a Java-compatible RNG
/// that matches `java.util.Random` and `Collections.shuffle()` exactly,
/// while normal gameplay uses the default thread-local RNG.
pub trait GameRng {
/// Shuffle a slice of CardIds in-place.
/// Must match `java.util.Collections.shuffle(list, rng)` for parity.
fn shuffle_cards(&mut self, cards: &mut [CardId]);
/// Return a random integer in `[0, bound)`.
/// Must match `java.util.Random.nextInt(bound)` for parity.
fn next_int(&mut self, bound: i32) -> i32;
/// Debug: return the total number of RNG calls made so far (if tracked).
fn call_count(&self) -> u64 {
0
}
}
/// Default RNG using `rand::thread_rng()` — non-deterministic, for normal gameplay.
pub struct ThreadRngAdapter;
impl GameRng for ThreadRngAdapter {
fn shuffle_cards(&mut self, cards: &mut [CardId]) {
use rand::seq::SliceRandom;
cards.shuffle(&mut rand::thread_rng());
}
fn next_int(&mut self, bound: i32) -> i32 {
use rand::Rng;
rand::thread_rng().gen_range(0..bound)
}
}