Skip to main content

thedes_gen/
event.rs

1use std::array;
2
3use rand::Rng;
4use rand_distr::{
5    Distribution,
6    Triangular,
7    TriangularError,
8    weighted::WeightedIndex,
9};
10use thedes_domain::{
11    event::Event,
12    game::Game,
13    geometry::Coord,
14    monster::{self, MonsterPosition},
15};
16use thedes_geometry::{
17    orientation::Direction,
18    rect::{InvalidRectDistr, UniformRectDistr},
19};
20use thiserror::Error;
21
22use crate::random::ProabilityWeight;
23
24#[derive(Debug, Error)]
25pub enum DistrError {
26    #[error("Invalid map rectangle")]
27    InvalidMapRect(
28        #[from]
29        #[source]
30        InvalidRectDistr,
31    ),
32    #[error("Failed to create distribution for monster follow limit")]
33    InvalidMonsterFollowLimitDistr(#[source] TriangularError),
34    #[error("Failed to create distribution for monster follow period")]
35    InvalidMonsterFollowPeriodDistr(#[source] TriangularError),
36}
37
38#[derive(Debug, Error)]
39pub enum InvalidMonsterFollowLimit {
40    #[error(
41        "Monster-follow limit must be in the interval [{}, {}], given {}",
42        DistrConfig::MIN_FOLLOW_LIMIT,
43        DistrConfig::MAX_FOLLOW_LIMIT,
44        _0
45    )]
46    Range(u32),
47    #[error(
48        "Peak monster follow limit {peak} must be between min and max {min} \
49         and {max}"
50    )]
51    PeakOutOfBounds { min: u32, max: u32, peak: u32 },
52    #[error(
53        "Minimum monster follow limit {min} cannot be greater than maximum \
54         {max}"
55    )]
56    BoundOrder { min: u32, max: u32 },
57}
58
59#[derive(Debug, Error)]
60pub enum InvalidMonsterFollowPeriod {
61    #[error(
62        "Monster-follow period must be in the interval [{}, {}], given {}",
63        DistrConfig::MIN_FOLLOW_PERIOD,
64        DistrConfig::MAX_FOLLOW_PERIOD,
65        _0
66    )]
67    Range(Coord),
68    #[error(
69        "Peak monster follow period {peak} must be between min and max {min} \
70         and {max}"
71    )]
72    PeakOutOfBounds { min: Coord, max: Coord, peak: Coord },
73    #[error(
74        "Minimum monster follow period {min} cannot be greater than maximum \
75         {max}"
76    )]
77    BoundOrder { min: Coord, max: Coord },
78}
79
80#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
81pub enum EventType {
82    TrySpawnMonster,
83    VanishMonster,
84    TryMoveMonster,
85    MonsterAttack,
86    MonsterGrowl,
87    FollowPlayer,
88}
89
90impl EventType {
91    pub const COUNT: usize = 6;
92
93    pub const ALL: [Self; Self::COUNT] = [
94        Self::TrySpawnMonster,
95        Self::VanishMonster,
96        Self::TryMoveMonster,
97        Self::MonsterAttack,
98        Self::MonsterGrowl,
99        Self::FollowPlayer,
100    ];
101}
102
103#[derive(Debug, Clone)]
104pub struct EventTypeDistr {
105    cumulative_weights: [ProabilityWeight; EventType::COUNT],
106}
107
108impl EventTypeDistr {
109    pub fn new<F>(mut density_function: F) -> Self
110    where
111        F: FnMut(EventType) -> ProabilityWeight,
112    {
113        let mut accumuled_weight = 0;
114        let cumulative_weights = array::from_fn(|i| {
115            accumuled_weight += density_function(EventType::ALL[i]);
116            accumuled_weight
117        });
118        Self { cumulative_weights }
119    }
120
121    pub fn from_monster_count(x: Coord) -> Self {
122        let cut = 10000;
123        let x = x as ProabilityWeight;
124        Self::new(|ty| {
125            let weight = match ty {
126                EventType::TrySpawnMonster => {
127                    if x == 0 {
128                        1
129                    } else if x < cut {
130                        cut * 2 - x
131                    } else {
132                        x / cut
133                    }
134                },
135                EventType::VanishMonster => {
136                    if x == 0 {
137                        0
138                    } else if x < cut {
139                        x
140                    } else {
141                        x - cut
142                    }
143                },
144                EventType::TryMoveMonster => x * cut / 100,
145                EventType::MonsterAttack => x * cut / 5,
146                EventType::MonsterGrowl => x * cut / 300,
147                EventType::FollowPlayer => x,
148            };
149            weight
150        })
151    }
152}
153
154impl Distribution<EventType> for EventTypeDistr {
155    fn sample<R>(&self, rng: &mut R) -> EventType
156    where
157        R: Rng + ?Sized,
158    {
159        let last_cumulative_weight =
160            self.cumulative_weights[self.cumulative_weights.len() - 1];
161        let sampled_weight = rng.random_range(0 .. last_cumulative_weight);
162        for (i, cumulative_weight) in
163            self.cumulative_weights.into_iter().enumerate()
164        {
165            if sampled_weight < cumulative_weight {
166                return EventType::ALL[i];
167            }
168        }
169        panic!("sampled weight {sampled_weight} is out of requested bounds")
170    }
171}
172
173#[derive(Debug, Clone)]
174pub struct DistrConfig {
175    monster_follow_limit_min: u32,
176    monster_follow_limit_peak: u32,
177    monster_follow_limit_max: u32,
178    monster_follow_period_min: Coord,
179    monster_follow_period_peak: Coord,
180    monster_follow_period_max: Coord,
181}
182
183impl DistrConfig {
184    pub const MIN_FOLLOW_PERIOD: Coord = 1;
185    pub const MAX_FOLLOW_PERIOD: Coord = Coord::MAX;
186
187    pub const MIN_FOLLOW_LIMIT: u32 = 1;
188    pub const MAX_FOLLOW_LIMIT: u32 = u32::MAX;
189
190    pub fn new() -> Self {
191        Self {
192            monster_follow_period_min: 1500,
193            monster_follow_period_peak: 2000,
194            monster_follow_period_max: 2500,
195            monster_follow_limit_min: 100,
196            monster_follow_limit_peak: 1000,
197            monster_follow_limit_max: 5000,
198        }
199    }
200
201    pub fn monster_follow_limit_min(&self) -> u32 {
202        self.monster_follow_limit_min
203    }
204
205    pub fn set_monster_follow_limit_min(
206        &mut self,
207        value: u32,
208    ) -> Result<(), InvalidMonsterFollowLimit> {
209        if value < Self::MIN_FOLLOW_LIMIT || value > Self::MAX_FOLLOW_LIMIT {
210            Err(InvalidMonsterFollowLimit::Range(value))?;
211        }
212        if self.monster_follow_limit_peak() < value {
213            Err(InvalidMonsterFollowLimit::BoundOrder {
214                min: value,
215                max: self.monster_follow_limit_max(),
216            })?;
217        }
218        if self.monster_follow_limit_max() <= value {
219            Err(InvalidMonsterFollowLimit::PeakOutOfBounds {
220                min: value,
221                max: self.monster_follow_limit_max(),
222                peak: self.monster_follow_limit_peak(),
223            })?;
224        }
225
226        self.monster_follow_limit_min = value;
227        Ok(())
228    }
229
230    pub fn with_monster_follow_limit_min(
231        mut self,
232        value: u32,
233    ) -> Result<Self, InvalidMonsterFollowLimit> {
234        self.set_monster_follow_limit_min(value)?;
235        Ok(self)
236    }
237
238    pub fn monster_follow_limit_peak(&self) -> u32 {
239        self.monster_follow_limit_peak
240    }
241
242    pub fn set_monster_follow_limit_peak(
243        &mut self,
244        value: u32,
245    ) -> Result<(), InvalidMonsterFollowLimit> {
246        if self.monster_follow_limit_max() < value
247            || self.monster_follow_limit_min() > value
248        {
249            Err(InvalidMonsterFollowLimit::PeakOutOfBounds {
250                min: value,
251                max: self.monster_follow_limit_max(),
252                peak: self.monster_follow_limit_peak(),
253            })?;
254        }
255
256        self.monster_follow_limit_peak = value;
257        Ok(())
258    }
259
260    pub fn with_monster_follow_limit_peak(
261        mut self,
262        value: u32,
263    ) -> Result<Self, InvalidMonsterFollowLimit> {
264        self.set_monster_follow_limit_peak(value)?;
265        Ok(self)
266    }
267
268    pub fn monster_follow_limit_max(&self) -> u32 {
269        self.monster_follow_limit_max
270    }
271
272    pub fn set_monster_follow_limit_max(
273        &mut self,
274        value: u32,
275    ) -> Result<(), InvalidMonsterFollowLimit> {
276        if value < Self::MIN_FOLLOW_LIMIT || value > Self::MAX_FOLLOW_LIMIT {
277            Err(InvalidMonsterFollowLimit::Range(value))?;
278        }
279        if self.monster_follow_limit_peak() > value {
280            Err(InvalidMonsterFollowLimit::BoundOrder {
281                min: self.monster_follow_limit_min(),
282                max: value,
283            })?;
284        }
285        if self.monster_follow_limit_min() >= value {
286            Err(InvalidMonsterFollowLimit::PeakOutOfBounds {
287                min: self.monster_follow_limit_min(),
288                max: value,
289                peak: self.monster_follow_limit_peak(),
290            })?;
291        }
292
293        self.monster_follow_limit_max = value;
294
295        Ok(())
296    }
297
298    pub fn with_monster_follow_limit_max(
299        mut self,
300        value: u32,
301    ) -> Result<Self, InvalidMonsterFollowLimit> {
302        self.set_monster_follow_limit_max(value)?;
303        Ok(self)
304    }
305
306    pub fn monster_follow_period_min(&self) -> Coord {
307        self.monster_follow_period_min
308    }
309
310    pub fn set_monster_follow_period_min(
311        &mut self,
312        value: Coord,
313    ) -> Result<(), InvalidMonsterFollowPeriod> {
314        if value < Self::MIN_FOLLOW_PERIOD || value > Self::MAX_FOLLOW_PERIOD {
315            Err(InvalidMonsterFollowPeriod::Range(value))?;
316        }
317        if self.monster_follow_period_peak() < value {
318            Err(InvalidMonsterFollowPeriod::BoundOrder {
319                min: value,
320                max: self.monster_follow_period_max(),
321            })?;
322        }
323        if self.monster_follow_period_max() <= value {
324            Err(InvalidMonsterFollowPeriod::PeakOutOfBounds {
325                min: value,
326                max: self.monster_follow_period_max(),
327                peak: self.monster_follow_period_peak(),
328            })?;
329        }
330
331        self.monster_follow_period_min = value;
332        Ok(())
333    }
334
335    pub fn with_monster_follow_period_min(
336        mut self,
337        value: Coord,
338    ) -> Result<Self, InvalidMonsterFollowPeriod> {
339        self.set_monster_follow_period_min(value)?;
340        Ok(self)
341    }
342
343    pub fn monster_follow_period_peak(&self) -> Coord {
344        self.monster_follow_period_peak
345    }
346
347    pub fn set_monster_follow_period_peak(
348        &mut self,
349        value: Coord,
350    ) -> Result<(), InvalidMonsterFollowPeriod> {
351        if self.monster_follow_period_max() < value
352            || self.monster_follow_period_min() > value
353        {
354            Err(InvalidMonsterFollowPeriod::PeakOutOfBounds {
355                min: value,
356                max: self.monster_follow_period_max(),
357                peak: self.monster_follow_period_peak(),
358            })?;
359        }
360
361        self.monster_follow_period_peak = value;
362        Ok(())
363    }
364
365    pub fn with_monster_follow_period_peak(
366        mut self,
367        value: Coord,
368    ) -> Result<Self, InvalidMonsterFollowPeriod> {
369        self.set_monster_follow_period_peak(value)?;
370        Ok(self)
371    }
372
373    pub fn monster_follow_period_max(&self) -> Coord {
374        self.monster_follow_period_max
375    }
376
377    pub fn set_monster_follow_period_max(
378        &mut self,
379        value: Coord,
380    ) -> Result<(), InvalidMonsterFollowPeriod> {
381        if value < Self::MIN_FOLLOW_PERIOD || value > Self::MAX_FOLLOW_PERIOD {
382            Err(InvalidMonsterFollowPeriod::Range(value))?;
383        }
384        if self.monster_follow_period_peak() > value {
385            Err(InvalidMonsterFollowPeriod::BoundOrder {
386                min: self.monster_follow_period_min(),
387                max: value,
388            })?;
389        }
390        if self.monster_follow_period_min() >= value {
391            Err(InvalidMonsterFollowPeriod::PeakOutOfBounds {
392                min: self.monster_follow_period_min(),
393                max: value,
394                peak: self.monster_follow_period_peak(),
395            })?;
396        }
397
398        self.monster_follow_period_max = value;
399
400        Ok(())
401    }
402
403    pub fn with_monster_follow_period_max(
404        mut self,
405        value: Coord,
406    ) -> Result<Self, InvalidMonsterFollowPeriod> {
407        self.set_monster_follow_period_max(value)?;
408        Ok(self)
409    }
410
411    pub fn finish<'a>(
412        &self,
413        game: &'a Game,
414    ) -> Result<EventDistr<'a>, DistrError> {
415        let monsters = game.monster_registry();
416        let monster_count = monsters.len() as Coord;
417        let event_type_distr =
418            EventTypeDistr::from_monster_count(monster_count);
419        let map_rect_uniform_distr = UniformRectDistr::new(game.map().rect())?;
420
421        let monster_follow_limit_distr = Triangular::new(
422            self.monster_follow_limit_min as f64,
423            self.monster_follow_limit_max as f64,
424            self.monster_follow_limit_peak as f64,
425        )
426        .map_err(DistrError::InvalidMonsterFollowLimitDistr)?;
427
428        let monster_follow_period_distr = Triangular::new(
429            f64::from(self.monster_follow_period_min),
430            f64::from(self.monster_follow_period_max),
431            f64::from(self.monster_follow_period_peak),
432        )
433        .map_err(DistrError::InvalidMonsterFollowPeriodDistr)?;
434
435        Ok(EventDistr {
436            monsters,
437            event_type_distr,
438            map_rect_uniform_distr,
439            monster_follow_limit_distr,
440            monster_follow_period_distr,
441        })
442    }
443}
444
445#[derive(Debug, Clone)]
446pub struct EventDistr<'a> {
447    monsters: &'a monster::Registry,
448    event_type_distr: EventTypeDistr,
449    map_rect_uniform_distr: UniformRectDistr<Coord>,
450    monster_follow_limit_distr: Triangular<f64>,
451    monster_follow_period_distr: Triangular<f64>,
452}
453
454impl<'a> Distribution<Event> for EventDistr<'a> {
455    fn sample<R>(&self, rng: &mut R) -> Event
456    where
457        R: Rng + ?Sized,
458    {
459        match self.event_type_distr.sample(rng) {
460            EventType::TrySpawnMonster => {
461                let body = self.map_rect_uniform_distr.sample(rng);
462                let facing = rng.random();
463                Event::TrySpawnMonster(MonsterPosition::new(body, facing))
464            },
465            EventType::VanishMonster => {
466                let index = rng.random_range(.. self.monsters.len());
467                let (id, _) = self
468                    .monsters
469                    .get_by_index_as(index)
470                    .expect("inconsistent indexing");
471                Event::VanishMonster(id)
472            },
473            EventType::TryMoveMonster => {
474                let index = rng.random_range(.. self.monsters.len());
475                let (id, monster) = self
476                    .monsters
477                    .get_by_index_as(index)
478                    .expect("inconsistent indexing");
479                let curr_direction = monster.position().facing();
480                let directions = Direction::ALL;
481                let weights = directions.map(|direction| {
482                    if direction == curr_direction { 5 } else { 1 }
483                });
484                let weighted = WeightedIndex::new(&weights)
485                    .expect("no weight should be zero, no overflow");
486                let direction = directions[weighted.sample(rng)];
487                Event::TryMoveMonster(id, direction)
488            },
489            EventType::MonsterAttack => {
490                let index = rng.random_range(.. self.monsters.len());
491                let (id, _) = self
492                    .monsters
493                    .get_by_index_as(index)
494                    .expect("inconsistent indexing");
495                Event::MonsterAttack(id)
496            },
497            EventType::MonsterGrowl => {
498                let index = rng.random_range(.. self.monsters.len());
499                let (id, _) = self
500                    .monsters
501                    .get_by_index_as(index)
502                    .expect("inconsistent indexing");
503                Event::MonsterGrowl(id)
504            },
505            EventType::FollowPlayer => {
506                let index = rng.random_range(.. self.monsters.len());
507                let (id, _) = self
508                    .monsters
509                    .get_by_index_as(index)
510                    .expect("inconsistent indexing");
511                let limit = self.monster_follow_limit_distr.sample(rng) as u32;
512                let period =
513                    self.monster_follow_period_distr.sample(rng) as Coord;
514                Event::FollowPlayer { id, period, limit }
515            },
516        }
517    }
518}