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}