1use rand::Rng;
2use rand_distr::{Triangular, TriangularError};
3use thedes_async_util::progress;
4use thedes_domain::{
5 game::{self, Game},
6 geometry::Coord,
7 player::{self, PlayerPosition},
8};
9use thedes_geometry::orientation::{Axis, Direction};
10use thiserror::Error;
11
12use crate::{map, random::PickedReproducibleRng};
13
14#[derive(Debug, Error)]
15pub enum InitError {
16 #[error("Error initializing map generator")]
17 Map(
18 #[from]
19 #[source]
20 map::InitError,
21 ),
22}
23
24#[derive(Debug, Error)]
25pub enum Error {
26 #[error("Error generating map")]
27 Map(
28 #[from]
29 #[source]
30 map::Error,
31 ),
32 #[error("Error creating random distribution for player's head in axis {1}")]
33 PlayerHeadDistr(#[source] TriangularError, Axis),
34 #[error("Failed to create a game")]
35 Creation(
36 #[source]
37 #[from]
38 game::InitError,
39 ),
40 #[error("Failed to create a player")]
41 CreatePlayer(
42 #[source]
43 #[from]
44 player::InitError,
45 ),
46}
47
48#[derive(Debug, Clone)]
49pub struct Config {
50 map_config: map::Config,
51}
52
53impl Default for Config {
54 fn default() -> Self {
55 Self::new()
56 }
57}
58
59impl Config {
60 pub fn new() -> Self {
61 Self { map_config: map::Config::new() }
62 }
63
64 pub fn with_map(self, map_config: map::Config) -> Self {
65 Self { map_config, ..self }
66 }
67
68 pub fn finish(
69 self,
70 rng: &mut PickedReproducibleRng,
71 ) -> Result<Generator, InitError> {
72 Ok(Generator { map_gen: self.map_config.finish(rng)? })
73 }
74}
75
76#[derive(Debug)]
77pub struct Generator {
78 map_gen: map::Generator,
79}
80
81impl Generator {
82 pub fn progress_goal(&self) -> usize {
83 self.map_gen.progress_goal() + 1
84 }
85
86 pub async fn execute(
87 self,
88 rng: &mut PickedReproducibleRng,
89 progress_logger: progress::Logger,
90 ) -> Result<Game, Error> {
91 progress_logger.set_status("generating map");
92 let map = self.map_gen.execute(rng, progress_logger.nest()).await?;
93
94 progress_logger.set_status("generating player");
95 let player_head_distr = map
96 .rect()
97 .size
98 .map_with_axes(|coord, axis| {
99 let min = 2.0;
100 let max = f64::from(coord) - 2.0 - f64::EPSILON;
101 let mode = min + (max - min) / 2.0;
102 Triangular::new(min, max, mode)
103 .map_err(|error| Error::PlayerHeadDistr(error, axis))
104 })
105 .transpose()?;
106 let player_head_offset =
107 player_head_distr.as_ref().map(|distr| rng.sample(distr) as Coord);
108 let player_head = map.rect().top_left + player_head_offset;
109 let player_facing_index = rng.random_range(0 .. Direction::ALL.len());
110 let player_facing = Direction::ALL[player_facing_index];
111 let player_pos = PlayerPosition::new(player_head, player_facing)?;
112 let game = Game::new(map, player_pos)?;
113 progress_logger.increment();
114
115 progress_logger.set_status("done");
116 Ok(game)
117 }
118}