agb_eb_ext 0.25.2

AGB Extension methods
Documentation
use agb::input::{Button, ButtonController};
use agb::rng::RandomNumberGenerator;

/// RNG seed generator
///
/// Uses input and maths to generate seeds
///
/// # Usage
///
/// Call [`update`](SeedGen::update) up to once per loop/frame
/// then call [`create_rng`](SeedGen::create_rng) to create a [`RandomNumberGenerator`] with that seed
pub struct SeedGen {
    pub seed: [u32; 4],
}

impl Default for SeedGen {
    fn default() -> SeedGen {
        Self {
            seed: [0x15f1c1; 4],
        }
    }
}

impl SeedGen {
    pub fn create_rng(&self) -> RandomNumberGenerator {
        RandomNumberGenerator::new_with_seed(self.seed)
    }

    pub fn update(&mut self, button_controller: &ButtonController) {
        self.seed[1] = self.seed[1].rotate_left(1);
        self.seed[1] |= button_controller.is_pressed(Button::Left) as u32;
        self.seed[1] |= (button_controller.is_pressed(Button::Right) as u32) << 1;
        self.seed[1] |= (button_controller.is_pressed(Button::Up) as u32) << 2;
        self.seed[1] |= (button_controller.is_pressed(Button::Down) as u32) << 3;
        self.seed[1] |= (button_controller.is_pressed(Button::A) as u32) << 4;
        self.seed[1] |= (button_controller.is_pressed(Button::B) as u32) << 5;
        self.seed[1] |= (button_controller.is_pressed(Button::L) as u32) << 6;
        self.seed[1] |= (button_controller.is_pressed(Button::R) as u32) << 7;
        self.seed[0] = self.seed[0].wrapping_add(1);
        self.seed[2] ^= self.seed[0].wrapping_mul(0x9e3779b9);
        self.seed[3] =
            self.seed[3].wrapping_add(button_controller.just_pressed_vector::<i32>().x as u32);
        self.seed[3] = self.seed[3].rotate_right(1);
    }
}

#[inline]
pub fn next_u16(rng: &mut RandomNumberGenerator) -> u16 {
    rng.next_i32() as u16
}

///inclusive
#[inline]
pub fn next_u16_in(rng: &mut RandomNumberGenerator, min: u16, max: u16) -> u16 {
    debug_assert!(min <= max, "min must not be greater than max");

    let span = (max as u32).wrapping_sub(min as u32).wrapping_add(1);
    let random_val = next_u16(rng) as u32;

    let scaled = (random_val * span) >> 16;

    (scaled as u16) + min
}

/// Random index in `0..max` without division (multiply-shift)
#[inline]
pub fn next_in(rng: &mut RandomNumberGenerator, max: usize) -> usize {
    let random_val = rng.next_i32() as u32 as u64;
    ((random_val * max as u64) >> 32) as usize
}

/// Shuffles a slice (or array/`Vec` via deref) in place using Fisher–Yates
pub fn shuffle<T>(rng: &mut RandomNumberGenerator, items: &mut [T]) {
    for i in (1..items.len()).rev() {
        let j = next_in(rng, i + 1);
        items.swap(i, j);
    }
}

/// Returns a random element from a slice (or array/`Vec` via deref), or `None` if it is empty
#[inline]
pub fn random_get<'a, T>(rng: &mut RandomNumberGenerator, items: &'a [T]) -> Option<&'a T> {
    if items.is_empty() {
        None
    } else {
        Some(&items[next_in(rng, items.len())])
    }
}

/// Returns a random mutable element from a slice (or array/`Vec` via deref), or `None` if it is empty
#[inline]
pub fn random_get_mut<'a, T>(
    rng: &mut RandomNumberGenerator,
    items: &'a mut [T],
) -> Option<&'a mut T> {
    if items.is_empty() {
        None
    } else {
        let idx = next_in(rng, items.len());
        Some(&mut items[idx])
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test_case]
    fn next_u16_in_stays_in_range(_gba: &mut agb::Gba) {
        let mut rng = RandomNumberGenerator::new_with_seed([1, 2, 3, 4]);
        for _ in 0..1000 {
            let v = next_u16_in(&mut rng, 5, 10);
            assert!((5..=10).contains(&v), "{v} out of range");
        }
    }

    #[test_case]
    fn next_u16_in_handles_extremes(_gba: &mut agb::Gba) {
        let mut rng = RandomNumberGenerator::new_with_seed([5, 6, 7, 8]);
        assert_eq!(next_u16_in(&mut rng, 7, 7), 7);
        for _ in 0..100 {
            next_u16_in(&mut rng, 0, u16::MAX);
            next_u16_in(&mut rng, u16::MAX - 1, u16::MAX);
        }
    }

    #[test_case]
    fn shuffle_keeps_all_elements(_gba: &mut agb::Gba) {
        let mut rng = RandomNumberGenerator::new_with_seed([9, 10, 11, 12]);
        let mut items = [1u8, 2, 3, 4, 5, 6, 7, 8];
        shuffle(&mut rng, &mut items);
        for expected in 1u8..=8 {
            assert!(
                items.contains(&expected),
                "{expected} missing after shuffle"
            );
        }
    }

    #[test_case]
    fn shuffle_handles_small_slices(_gba: &mut agb::Gba) {
        let mut rng = RandomNumberGenerator::new_with_seed([13, 14, 15, 16]);
        let mut empty: [u8; 0] = [];
        shuffle(&mut rng, &mut empty);
        let mut single = [42u8];
        shuffle(&mut rng, &mut single);
        assert_eq!(single, [42]);
    }

    #[test_case]
    fn choose_returns_contained_element(_gba: &mut agb::Gba) {
        let mut rng = RandomNumberGenerator::new_with_seed([17, 18, 19, 20]);
        let items = [10u8, 20, 30, 40, 50];
        for _ in 0..100 {
            let v = *random_get(&mut rng, &items).unwrap();
            assert!(items.contains(&v), "{v} not in source slice");
        }
    }

    #[test_case]
    fn choose_handles_empty_and_mut(_gba: &mut agb::Gba) {
        let mut rng = RandomNumberGenerator::new_with_seed([21, 22, 23, 24]);
        let empty: [u8; 0] = [];
        assert!(random_get(&mut rng, &empty).is_none());

        let mut items = [1u8, 1, 1];
        if let Some(v) = random_get_mut(&mut rng, &mut items) {
            *v = 9;
        }
        assert_eq!(items.iter().filter(|&&v| v == 9).count(), 1);
    }
}