initiative_core/world/npc/species/
human.rs

1use super::{Age, Gender, Generate, Size};
2use rand::prelude::*;
3
4pub struct Species;
5
6impl Generate for Species {
7    fn gen_gender(rng: &mut impl Rng) -> Gender {
8        match rng.gen_range(1..=101) {
9            1..=50 => Gender::Feminine,
10            51..=100 => Gender::Masculine,
11            101 => Gender::NonBinaryThey,
12            _ => unreachable!(),
13        }
14    }
15
16    fn gen_age_years(rng: &mut impl Rng) -> u16 {
17        rng.gen_range(0..=79)
18    }
19
20    fn gen_years_from_age(rng: &mut impl Rng, age: &Age) -> u16 {
21        rng.gen_range(match age {
22            Age::Infant => 0..=1,
23            Age::Child => 2..=9,
24            Age::Adolescent => 10..=19,
25            Age::YoungAdult => 20..=29,
26            Age::Adult => 30..=39,
27            Age::MiddleAged => 40..=59,
28            Age::Elderly => 60..=69,
29            Age::Geriatric => 70..=79,
30        })
31    }
32
33    fn age_from_years(years: u16) -> Age {
34        match years {
35            i if i < 2 => Age::Infant,
36            i if i < 10 => Age::Child,
37            i if i < 20 => Age::Adolescent,
38            i if i < 30 => Age::YoungAdult,
39            i if i < 40 => Age::Adult,
40            i if i < 60 => Age::MiddleAged,
41            i if i < 70 => Age::Elderly,
42            _ => Age::Geriatric,
43        }
44    }
45
46    fn gen_size(rng: &mut impl Rng, age_years: u16, gender: &Gender) -> Size {
47        let is_female = match gender {
48            Gender::Masculine => rng.gen_bool(0.01),
49            Gender::Feminine => rng.gen_bool(0.99),
50            _ => rng.gen_bool(0.5),
51        };
52
53        match (age_years, is_female) {
54            (0, _) => {
55                let size = rng.gen_range(0..=30);
56                Size::Tiny {
57                    height: 20 + size / 3,
58                    weight: 7 + size / 2,
59                }
60            }
61            (1, _) => {
62                let size = rng.gen_range(0..=5);
63                Size::Tiny {
64                    height: 30 + size,
65                    weight: 22 + size,
66                }
67            }
68            (2..=9, _) => {
69                let y = (age_years - 2) as f32 / 8.;
70                let (height, weight) =
71                    super::gen_height_weight(rng, (33. + y * 18.)..=(35. + y * 22.), 14.0..=17.0);
72                Size::Small { height, weight }
73            }
74            (10..=19, true) => {
75                let y = (age_years - 10) as f32;
76                let (height, weight) = super::gen_height_weight(
77                    rng,
78                    (51. + y * 2.).min(61.)..=(65. + y * 2.).min(67.),
79                    (15. + y * 2.5 / 5.).min(18.5)..=(19. + y * 4.5 / 5.).min(25.),
80                );
81                Size::Medium { height, weight }
82            }
83            (10..=19, false) => {
84                let y = (age_years - 10) as f32 / 5.;
85                let (height, weight) = super::gen_height_weight(
86                    rng,
87                    (51. + y * 12.).min(66.)..=(57. + y * 13.).min(72.),
88                    (15. + y * 2.5).min(18.5)..=(18.5 + y * 4.5).min(29.),
89                );
90                Size::Medium { height, weight }
91            }
92            (_, true) => {
93                let (height, weight) = super::gen_height_weight(rng, 61.0..=67.0, 19.0..=25.0);
94                Size::Medium { height, weight }
95            }
96            (_, false) => {
97                let (height, weight) = super::gen_height_weight(rng, 66.0..=72.0, 18.5..=29.0);
98                Size::Medium { height, weight }
99            }
100        }
101    }
102}
103
104#[cfg(test)]
105mod test_generate_for_species {
106    use super::*;
107    use std::collections::HashMap;
108
109    #[test]
110    fn gen_age_years_test() {
111        let mut rng = SmallRng::seed_from_u64(0);
112
113        assert_eq!(
114            [35, 35, 78, 36, 71],
115            [
116                Species::gen_age_years(&mut rng),
117                Species::gen_age_years(&mut rng),
118                Species::gen_age_years(&mut rng),
119                Species::gen_age_years(&mut rng),
120                Species::gen_age_years(&mut rng),
121            ],
122        );
123    }
124
125    #[test]
126    fn gen_years_from_age_test() {
127        let ages = [
128            Age::Infant,
129            Age::Child,
130            Age::Adolescent,
131            Age::YoungAdult,
132            Age::Adult,
133            Age::MiddleAged,
134            Age::Elderly,
135            Age::Geriatric,
136        ];
137
138        for age in ages {
139            let mut rng = SmallRng::seed_from_u64(0);
140
141            for _ in 0..10 {
142                let age_years = Species::gen_years_from_age(&mut rng, &age);
143                assert_eq!(age, Species::age_from_years(age_years));
144            }
145        }
146    }
147
148    #[test]
149    fn age_from_years_test() {
150        assert_eq!(Age::Infant, Species::age_from_years(0));
151        assert_eq!(Age::Infant, Species::age_from_years(1));
152
153        assert_eq!(Age::Child, Species::age_from_years(2));
154        assert_eq!(Age::Child, Species::age_from_years(9));
155
156        assert_eq!(Age::Adolescent, Species::age_from_years(10));
157        assert_eq!(Age::Adolescent, Species::age_from_years(19));
158
159        assert_eq!(Age::YoungAdult, Species::age_from_years(20));
160        assert_eq!(Age::YoungAdult, Species::age_from_years(29));
161
162        assert_eq!(Age::Adult, Species::age_from_years(30));
163        assert_eq!(Age::Adult, Species::age_from_years(39));
164
165        assert_eq!(Age::MiddleAged, Species::age_from_years(40));
166        assert_eq!(Age::MiddleAged, Species::age_from_years(59));
167
168        assert_eq!(Age::Elderly, Species::age_from_years(60));
169        assert_eq!(Age::Elderly, Species::age_from_years(69));
170
171        assert_eq!(Age::Geriatric, Species::age_from_years(70));
172        assert_eq!(Age::Geriatric, Species::age_from_years(u16::MAX));
173    }
174
175    #[test]
176    fn gen_gender_test() {
177        let mut rng = SmallRng::seed_from_u64(0);
178        let mut genders: HashMap<String, u16> = HashMap::new();
179
180        for _ in 0..500 {
181            let gender = Species::gen_gender(&mut rng);
182            *genders.entry(format!("{}", gender)).or_default() += 1;
183        }
184
185        assert_eq!(3, genders.len());
186        assert_eq!(Some(&3), genders.get("non-binary (they/them)"));
187        assert_eq!(Some(&233), genders.get("feminine (she/her)"));
188        assert_eq!(Some(&264), genders.get("masculine (he/him)"));
189    }
190
191    #[test]
192    fn gen_size_male_test() {
193        let mut rng = SmallRng::seed_from_u64(0);
194
195        // (age, size, height, weight)
196        assert_eq!(
197            vec![
198                (0, "tiny", 24, 13),
199                (1, "tiny", 32, 24),
200                (2, "small", 35, 28),
201                (3, "small", 36, 30),
202                (4, "small", 41, 39),
203                (5, "small", 44, 38),
204                (6, "small", 42, 43),
205                (7, "small", 47, 44),
206                (8, "small", 50, 55),
207                (9, "small", 54, 56),
208                (10, "medium", 53, 79),
209                (11, "medium", 54, 82),
210                (12, "medium", 57, 73),
211                (13, "medium", 59, 83),
212                (14, "medium", 67, 120),
213                (15, "medium", 65, 130),
214                (16, "medium", 66, 124),
215                (17, "medium", 70, 125),
216                (18, "medium", 71, 127),
217                (19, "medium", 72, 197),
218                (20, "medium", 67, 122),
219            ],
220            (0u16..=20)
221                .map(move |y| {
222                    let size = Species::gen_size(&mut rng, y, &Gender::Masculine);
223                    (y, size.name(), size.height(), size.weight())
224                })
225                .collect::<Vec<_>>(),
226        );
227    }
228
229    #[test]
230    fn gen_size_female_test() {
231        let mut rng = SmallRng::seed_from_u64(0);
232
233        // (age, size, height, weight)
234        assert_eq!(
235            vec![
236                (0, "tiny", 24, 13),
237                (1, "tiny", 32, 24),
238                (2, "small", 35, 28),
239                (3, "small", 36, 30),
240                (4, "small", 41, 39),
241                (5, "small", 44, 38),
242                (6, "small", 42, 43),
243                (7, "small", 47, 44),
244                (8, "small", 50, 55),
245                (9, "small", 54, 56),
246                (10, "medium", 56, 91),
247                (11, "medium", 55, 87),
248                (12, "medium", 58, 75),
249                (13, "medium", 59, 82),
250                (14, "medium", 67, 119),
251                (15, "medium", 63, 122),
252                (16, "medium", 62, 108),
253                (17, "medium", 65, 108),
254                (18, "medium", 66, 111),
255                (19, "medium", 67, 160),
256                (20, "medium", 62, 107),
257            ],
258            (0u16..=20)
259                .map(move |y| {
260                    let size = Species::gen_size(&mut rng, y, &Gender::Feminine);
261                    (y, size.name(), size.height(), size.weight())
262                })
263                .collect::<Vec<_>>(),
264        );
265    }
266}