summaryrefslogtreecommitdiff
path: root/2026/games/rpc2dot0/src/random.rs
blob: 14a9f78856305fb2056d43b083fee6e45e0f7381 (plain)
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
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
use crate::color::EntityColor;
use crate::constants::get_baseline_ratio;
use crate::math::Vec2;

use rand::{Rng, RngCore, SeedableRng};
use rand_chacha::ChaCha8Rng;

#[derive(Debug, Clone)]
pub struct RandomState {
    rng: ChaCha8Rng,
}

impl RandomState {
    /// RandomState::new(0x67676767) when debugging
    pub fn new(seed: usize) -> Self {
        log::info!("eatyourgreens:: RandomState new seed is {:?}", seed);
        Self {
            rng: ChaCha8Rng::seed_from_u64(seed as u64),
        }
    }

    pub fn next_u32(&mut self) -> u32 {
        self.rng.next_u32()
    }

    pub fn next_f32(&mut self) -> f32 {
        self.rng.r#gen::<f32>()
    }

    pub fn next_range(&mut self, min: f32, max: f32) -> f32 {
        self.rng.gen_range(min..max)
    }

    pub fn next_unit_vector(&mut self) -> Vec2 {
        let angle = self.rng.gen_range(0.0..std::f32::consts::TAU);
        Vec2::new(angle.cos(), angle.sin())
    }

    pub fn next_bool(&mut self, p: f64) -> bool {
        self.rng.gen_bool(p)
    }

    // this is used for rogue colors,
    // when used for random spawn, there is a risk
    // of color overcrowding. (like too much green or whatever)
    pub fn random_color(&mut self) -> EntityColor {
        let next_color_prob = self.next_f32();
        match next_color_prob {
            x if x < 0.60 => EntityColor::Green,
            x if x < 0.80 => EntityColor::Yellow,
            x if x < 0.90 => EntityColor::Red,
            _ => EntityColor::Bleu,
        }
    }

    // don't give it too much toughts this is designed by a clanker (cause too much math hurts my soul, I am not sorry)
    // coded by yours truely something within the spirit of
    // wiki :
    // https://en.wikipedia.org/wiki/Fitness_proportionate_selection
    // https://en.wikipedia.org/wiki/Reservoir_sampling#Weighted_random_sampling

    // we try to have a basline that is adjustable by difficulty
    // then we have weights for our biased random picker
    // used to spanwing entities
    // seed compatiable aka : reproducible behaviour with a fixed D and S (D=difficulty, S=seed)
    /*
       difficulty 1.0 => Green: 0.60  Red: 0.10   (baseline, no shift)
       difficulty 1.5 => Green: 0.525 Red: 0.175  (shift = 0.075)
       difficulty 2.0 => Green: 0.45  Red: 0.25   (shift = 0.15)
       difficulty 3.0 => Green: 0.30  Red: 0.40   (shift = 0.30)
       difficulty 5.0 => Green: 0.05  Red: 0.65   (capped: Green can't go below 5%)
    */
    // not tested and I won't test it, fuck that.
    pub fn random_color_biased(&mut self, counts: [u32; 4], difficulty: f32) -> EntityColor {
        let baseline: [f32; 4] = {
            let mut b = [0.0f32; 4];
            b[EntityColor::Red as usize] = get_baseline_ratio(EntityColor::Red, difficulty);
            b[EntityColor::Green as usize] = get_baseline_ratio(EntityColor::Green, difficulty);
            b[EntityColor::Bleu as usize] = get_baseline_ratio(EntityColor::Bleu, difficulty);
            b[EntityColor::Yellow as usize] = get_baseline_ratio(EntityColor::Yellow, difficulty);
            b
        };

        let total = counts.iter().sum::<u32>().max(1) as f32;

        let mut weights = [0.0f32; 4];
        for i in 0..4 {
            let actual_ratio = counts[i] as f32 / total;
            weights[i] = (2.0 * baseline[i] - actual_ratio).max(0.05);
        }
        let sum: f32 = weights.iter().sum();
        let roll: f32 = self.next_f32() * sum;
        let mut cumulative: f32 = 0.0f32;
        for (i, &w) in weights.iter().enumerate() {
            cumulative += w;
            if roll < cumulative {
                return match i {
                    0 => EntityColor::Red,
                    1 => EntityColor::Green,
                    2 => EntityColor::Bleu,
                    _ => EntityColor::Yellow,
                };
            }
        }
        unreachable!("Should never happen")
    }
}