smrng/drops/
analysis.rs

1use serde::Serialize;
2
3use crate::Rng;
4
5use super::{Drop, DropSet, DropTable};
6
7/// The simulated results of farming an enemy across a set of seeds.
8#[derive(Default, PartialEq, Eq, Hash, Serialize, Debug)]
9pub struct DropAnalysis {
10    /// The number of seeds sampled.
11    pub seeds: u32,
12
13    pub nothing: u32,
14    pub small_energy: u32,
15    pub big_energy: u32,
16    pub missile: u32,
17    pub super_missile: u32,
18    pub power_bomb: u32,
19}
20
21impl DropAnalysis {
22    fn update(&mut self, drop: Drop) {
23        match drop {
24            Drop::Nothing => self.nothing += 1,
25            Drop::SmallEnergy => self.small_energy += 1,
26            Drop::BigEnergy => self.big_energy += 1,
27            Drop::Missile => self.missile += 1,
28            Drop::SuperMissile => self.super_missile += 1,
29            Drop::PowerBomb => self.power_bomb += 1,
30        }
31    }
32}
33
34/// Generates a `DropAnalysis` for a set of seeds, simulating the actual RNG behavior (including
35/// correlation between successive calls).
36pub fn analyze_correlated(
37    table: &DropTable,
38    possible_drops: &DropSet,
39    n: u32,
40    rng: Rng,
41    seeds: impl IntoIterator<Item = u16>,
42) -> DropAnalysis {
43    let mut analysis = DropAnalysis::default();
44
45    for seed in seeds {
46        let mut rng = rng.with_seed(seed);
47        for drop in table.roll_multiple(&mut rng, possible_drops, n) {
48            analysis.update(drop);
49        }
50
51        analysis.seeds += 1;
52    }
53    analysis
54}
55
56/// Generates a `DropAnalysis` for a set of seeds; simulating RNG distribution across the given set
57/// of seeds, but assuming successive calls are independent.
58pub fn analyze_uncorrelated<S: IntoIterator<Item = u16>>(
59    table: &DropTable,
60    possible_drops: &DropSet,
61    n: u32,
62    seeds: S,
63) -> DropAnalysis
64where
65    S::IntoIter: ExactSizeIterator + Clone,
66{
67    let mut analysis = DropAnalysis::default();
68    let seeds = seeds.into_iter();
69    let num_seeds = seeds.len() as u32;
70    analysis.seeds = num_seeds;
71
72    let drop_count = table.count.unwrap_or(1) + table.extra.as_ref().map(|_| 1).unwrap_or(0);
73
74    seeds
75        .cycle()
76        .take((num_seeds * drop_count * n) as usize)
77        .map(|seed| Rng::RESET.with_seed(seed))
78        .for_each(|mut rng| analysis.update(table.roll_one(&mut rng, possible_drops)));
79
80    analysis
81}