use crate::ctypes::*;
use crate::ffi;
use core::ops::{Bound, RangeBounds};
pub(crate) trait Rng {
fn rand16(&mut self) -> u16;
fn rand32(&mut self) -> u32;
fn rand_range32(&mut self, x: c_int, y: c_int) -> c_int;
}
struct GameRng;
impl Rng for GameRng {
fn rand16(&mut self) -> u16 {
unsafe { ffi::Rand16Bit() }
}
fn rand32(&mut self) -> u32 {
unsafe { ffi::Rand32Bit() }
}
fn rand_range32(&mut self, x: c_int, y: c_int) -> c_int {
unsafe { ffi::RandRange(x, y) }
}
}
pub fn get_seed() -> u16 {
unsafe { ffi::GetRngSeed() }
}
pub fn set_seed(seed: u16) {
unsafe { ffi::SetRngSeed(seed) }
}
pub fn rand_u16<R: RangeBounds<u16>>(range: R) -> u16 {
rand_u16_internal(&mut GameRng, range)
}
pub fn rand_i32<R: RangeBounds<i32>>(range: R) -> i32 {
rand_i32_internal(&mut GameRng, range)
}
pub(crate) fn rand_u16_internal<T: Rng, R: RangeBounds<u16>>(rng: &mut T, range: R) -> u16 {
<u16 as RangeCheckable>::check_range(&range);
let (min, max) = match (range.start_bound(), range.end_bound()) {
(Bound::Unbounded, Bound::Unbounded) => return rng.rand16(),
(Bound::Unbounded, Bound::Included(u)) => (u16::MIN, *u),
(Bound::Unbounded, Bound::Excluded(u)) => (u16::MIN, *u - 1),
(Bound::Included(l), Bound::Unbounded) => (*l, u16::MAX),
(Bound::Included(l), Bound::Included(u)) => (*l, *u),
(Bound::Included(l), Bound::Excluded(u)) => (*l, *u - 1),
(Bound::Excluded(_), _) => {
panic!("Excluded start ranges not supported.")
}
};
let range = 1 + max - min;
let buckets = u16::MAX / range;
let limit = buckets * range;
loop {
let r = rng.rand16();
if r < limit {
return min + (r / buckets);
}
}
}
pub(crate) fn rand_i32_internal<T: Rng, R: RangeBounds<i32>>(rng: &mut T, range: R) -> i32 {
<i32 as RangeCheckable>::check_range(&range);
match (range.start_bound(), range.end_bound()) {
(Bound::Unbounded, Bound::Unbounded) => rng.rand32() as i32, (Bound::Unbounded, Bound::Included(u)) => rng.rand_range32(i32::MIN, u + 1),
(Bound::Unbounded, Bound::Excluded(u)) => rng.rand_range32(i32::MIN, *u),
(Bound::Included(l), Bound::Unbounded) => rng.rand_range32(*l, i32::MAX),
(Bound::Included(l), Bound::Included(u)) => rng.rand_range32(*l, u + 1),
(Bound::Included(l), Bound::Excluded(u)) => rng.rand_range32(*l, *u),
(Bound::Excluded(_), _) => {
panic!("Excluded start ranges not supported.")
}
}
}
trait RangeCheckable {
fn check_range<R: RangeBounds<Self>>(range: &R);
}
impl RangeCheckable for i32 {
fn check_range<R: RangeBounds<Self>>(range: &R) {
match (range.start_bound(), range.end_bound()) {
(Bound::Included(l), Bound::Included(u)) => assert!(l <= u),
(Bound::Included(l), Bound::Excluded(u)) => assert!(l < u),
_ => (),
}
}
}
impl RangeCheckable for u16 {
fn check_range<R: RangeBounds<Self>>(range: &R) {
match (range.start_bound(), range.end_bound()) {
(Bound::Included(l), Bound::Included(u)) => assert!(l <= u),
(Bound::Included(l), Bound::Excluded(u)) => assert!(l < u),
_ => (),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::ctypes::c_int;
use alloc::vec::Vec;
use core::ops::{Bound, RangeBounds};
const RAND_16_RETURN: u16 = 2000;
const RAND_32_RETURN: i32 = 1234;
#[derive(Clone, Copy, Eq, PartialEq, Debug)]
enum RngCall {
Rand16,
Rand32,
RandRange32(c_int, c_int),
}
struct MockRng(Vec<RngCall>);
impl Rng for MockRng {
fn rand16(&mut self) -> u16 {
self.0.push(RngCall::Rand16);
RAND_16_RETURN
}
fn rand32(&mut self) -> u32 {
self.0.push(RngCall::Rand32);
RAND_32_RETURN as u32
}
fn rand_range32(&mut self, x: c_int, y: c_int) -> c_int {
self.0.push(RngCall::RandRange32(x, y));
RAND_32_RETURN
}
}
struct ExcludedStartRange<'a, T> {
end_bound: Bound<&'a T>,
excluded_start: T,
}
impl<'a, T> RangeBounds<T> for ExcludedStartRange<'a, T> {
fn start_bound(&self) -> Bound<&T> {
Bound::Excluded(&self.excluded_start)
}
fn end_bound(&self) -> Bound<&T> {
self.end_bound
}
}
#[test]
fn test_rand_i32_unbounded_unbounded() {
let mut mock = MockRng(Vec::new());
let result = rand_i32_internal(&mut mock, ..);
assert_eq!(mock.0.len(), 1);
assert_eq!(mock.0[0], RngCall::Rand32);
assert_eq!(result, RAND_32_RETURN);
}
#[test]
fn test_rand_i32_unbounded_included() {
let mut mock = MockRng(Vec::new());
let result = rand_i32_internal(&mut mock, ..=10);
assert_eq!(mock.0.len(), 1);
assert_eq!(mock.0[0], RngCall::RandRange32(i32::MIN, 11));
assert_eq!(result, RAND_32_RETURN);
}
#[test]
fn test_rand_i32_unbounded_excluded() {
let mut mock = MockRng(Vec::new());
let result = rand_i32_internal(&mut mock, ..10);
assert_eq!(mock.0.len(), 1);
assert_eq!(mock.0[0], RngCall::RandRange32(i32::MIN, 10));
assert_eq!(result, RAND_32_RETURN);
}
#[test]
fn test_rand_i32_included_unbounded() {
let mut mock = MockRng(Vec::new());
let result = rand_i32_internal(&mut mock, 10..);
assert_eq!(mock.0.len(), 1);
assert_eq!(mock.0[0], RngCall::RandRange32(10, i32::MAX));
assert_eq!(result, RAND_32_RETURN);
}
#[test]
fn test_rand_i32_included_included() {
let mut mock = MockRng(Vec::new());
let result = rand_i32_internal(&mut mock, 10..=20);
assert_eq!(mock.0.len(), 1);
assert_eq!(mock.0[0], RngCall::RandRange32(10, 21));
assert_eq!(result, RAND_32_RETURN);
}
#[test]
fn test_rand_i32_included_excluded() {
let mut mock = MockRng(Vec::new());
let result = rand_i32_internal(&mut mock, 10..20);
assert_eq!(mock.0.len(), 1);
assert_eq!(mock.0[0], RngCall::RandRange32(10, 20));
assert_eq!(result, RAND_32_RETURN);
}
#[test]
#[should_panic]
fn test_rand_i32_excluded_unbounded() {
rand_i32_internal(
&mut MockRng(Vec::new()),
ExcludedStartRange {
end_bound: Bound::Unbounded,
excluded_start: 0,
},
);
}
#[test]
#[should_panic]
fn test_rand_i32_excluded_included() {
rand_i32_internal(
&mut MockRng(Vec::new()),
ExcludedStartRange {
end_bound: Bound::Included(&0),
excluded_start: 0,
},
);
}
#[test]
#[should_panic]
fn test_rand_i32_excluded_excluded() {
rand_i32_internal(
&mut MockRng(Vec::new()),
ExcludedStartRange {
end_bound: Bound::Excluded(&0),
excluded_start: 0,
},
);
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
#[should_panic]
fn test_rand_i32_reverse_range_excluded() {
assert_eq!((3..2).into_iter().count(), 0);
rand_i32_internal(&mut MockRng(Vec::new()), 3..2);
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
#[should_panic]
fn test_rand_i32_empty_range_excluded() {
assert_eq!((3..3).into_iter().count(), 0);
rand_i32_internal(&mut MockRng(Vec::new()), 3..3);
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
#[should_panic]
fn test_rand_i32_reverse_range_included() {
assert_eq!((3..=1).into_iter().count(), 0);
rand_i32_internal(&mut MockRng(Vec::new()), 3..=1);
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
#[should_panic]
fn test_rand_i32_empty_range_included_anti() {
assert_eq!((3..=2).into_iter().count(), 0);
rand_i32_internal(&mut MockRng(Vec::new()), 3..=2);
}
#[test]
fn test_rand_u16_unbounded_unbounded() {
let mut mock = MockRng(Vec::new());
let result = rand_u16_internal(&mut mock, ..);
assert_eq!(mock.0.len(), 1);
assert_eq!(mock.0[0], RngCall::Rand16);
assert_eq!(result, RAND_16_RETURN);
}
#[test]
fn test_rand_u16_unbounded_included() {
let mut mock = MockRng(Vec::new());
let result = rand_u16_internal(&mut mock, ..=10);
assert_eq!(mock.0.len(), 1);
assert_eq!(mock.0[0], RngCall::Rand16);
assert_eq!(result, RAND_16_RETURN / (u16::MAX / 11));
}
#[test]
fn test_rand_u16_unbounded_excluded() {
let mut mock = MockRng(Vec::new());
let result = rand_u16_internal(&mut mock, ..10);
assert_eq!(mock.0.len(), 1);
assert_eq!(mock.0[0], RngCall::Rand16);
assert_eq!(result, RAND_16_RETURN % 10);
}
#[test]
fn test_rand_u16_included_unbounded() {
let mut mock = MockRng(Vec::new());
let result = rand_u16_internal(&mut mock, 10..);
assert_eq!(mock.0.len(), 1);
assert_eq!(mock.0[0], RngCall::Rand16);
assert_eq!(result, RAND_16_RETURN + 10);
}
#[test]
fn test_rand_u16_included_included() {
let mut mock = MockRng(Vec::new());
let result = rand_u16_internal(&mut mock, 10..=20);
assert_eq!(mock.0.len(), 1);
assert_eq!(mock.0[0], RngCall::Rand16);
assert_eq!(result, 10 + RAND_16_RETURN / (u16::MAX / 11));
}
#[test]
fn test_rand_u16_included_excluded() {
let mut mock = MockRng(Vec::new());
let result = rand_u16_internal(&mut mock, 10..20);
assert_eq!(mock.0.len(), 1);
assert_eq!(mock.0[0], RngCall::Rand16);
assert_eq!(result, (RAND_16_RETURN % 10) + 10);
}
#[test]
#[should_panic]
fn test_rand_u16_excluded_unbounded() {
rand_u16_internal(
&mut MockRng(Vec::new()),
ExcludedStartRange {
end_bound: Bound::Unbounded,
excluded_start: 0,
},
);
}
#[test]
#[should_panic]
fn test_rand_u16_excluded_included() {
rand_u16_internal(
&mut MockRng(Vec::new()),
ExcludedStartRange {
end_bound: Bound::Included(&0),
excluded_start: 0,
},
);
}
#[test]
#[should_panic]
fn test_rand_u16_excluded_excluded() {
rand_u16_internal(
&mut MockRng(Vec::new()),
ExcludedStartRange {
end_bound: Bound::Excluded(&0),
excluded_start: 0,
},
);
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
#[should_panic]
fn test_rand_u16_reverse_range_excluded() {
assert_eq!((3..2).into_iter().count(), 0);
rand_u16_internal(&mut MockRng(Vec::new()), 3..2);
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
#[should_panic]
fn test_rand_u16_empty_range_excluded() {
assert_eq!((3..3).into_iter().count(), 0);
rand_u16_internal(&mut MockRng(Vec::new()), 3..3);
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
#[should_panic]
fn test_rand_u16_reverse_range_included() {
assert_eq!((3..=1).into_iter().count(), 0);
rand_u16_internal(&mut MockRng(Vec::new()), 3..=1);
}
#[test]
#[allow(clippy::reversed_empty_ranges)]
#[should_panic]
fn test_rand_u16_empty_range_included_anti() {
assert_eq!((3..=2).into_iter().count(), 0);
rand_u16_internal(&mut MockRng(Vec::new()), 3..=2);
}
}