Skip to main content

thedes_domain/
game.rs

1use std::{
2    collections::{HashMap, VecDeque},
3    fs::File,
4    io::{self, BufReader, BufWriter},
5    path::{Path, PathBuf},
6};
7
8use serde::{Deserialize, Serialize};
9use thedes_geometry::orientation::Direction;
10use thiserror::Error;
11use tokio::task;
12
13use crate::{
14    block::{Block, PlaceableBlock, SpecialBlock},
15    event::{self, Event, MetaEvent},
16    geometry::{Coord, CoordPair, Rect},
17    map::{AccessError, Map},
18    monster::{self, IdShortageError, Monster, MonsterPosition},
19    player::{Player, PlayerPosition},
20    stat::StatValue,
21};
22
23#[derive(Debug, Error)]
24pub enum LoadErrorSource {
25    #[error("I/O error happened")]
26    Io(#[from] io::Error),
27    #[error("Failed to deserialize")]
28    Deserialize(#[from] serde_json::Error),
29}
30
31#[derive(Debug, Error)]
32#[error("Failed to load game from {path}")]
33pub struct LoadError {
34    pub path: PathBuf,
35    #[source]
36    pub source: LoadErrorSource,
37}
38
39#[derive(Debug, Error)]
40pub enum SaveErrorSource {
41    #[error("I/O error happened")]
42    Io(#[from] io::Error),
43    #[error("Failed to serialize")]
44    Serialize(#[from] serde_json::Error),
45}
46
47#[derive(Debug, Error)]
48#[error("Failed to save game to {path}")]
49pub struct SaveError {
50    pub path: PathBuf,
51    #[source]
52    pub source: SaveErrorSource,
53}
54
55#[derive(Debug, Error)]
56pub enum InitError {
57    #[error(
58        "Player with head {} and pointer {} is outside of map {map_rect}",
59        .player_position.head(),
60        .player_position.pointer(),
61    )]
62    PlayerOutsideMap {
63        map_rect: Rect,
64        player_position: PlayerPosition,
65        source: AccessError,
66    },
67}
68
69#[derive(Debug, Error)]
70pub enum MovePlayerError {
71    #[error("Failed to access player positions")]
72    Access(
73        #[from]
74        #[source]
75        AccessError,
76    ),
77}
78
79#[derive(Debug, Error)]
80pub enum SpawnMonsterError {
81    #[error("Failed to access map location")]
82    MapAccess(
83        #[from]
84        #[source]
85        AccessError,
86    ),
87    #[error("Run out of identifiers")]
88    IdShortage(
89        #[from]
90        #[source]
91        IdShortageError,
92    ),
93}
94
95#[derive(Debug, Error)]
96pub enum VanishMonsterError {
97    #[error("Invalid monster ID")]
98    InvalidId(
99        #[from]
100        #[source]
101        monster::InvalidId,
102    ),
103    #[error("Failed to access map location")]
104    MapAccess(
105        #[from]
106        #[source]
107        AccessError,
108    ),
109}
110
111#[derive(Debug, Error)]
112pub enum MoveMonsterError {
113    #[error("Invalid monster ID")]
114    InvalidId(
115        #[from]
116        #[source]
117        monster::InvalidId,
118    ),
119    #[error("Failed to access map location")]
120    MapAccess(
121        #[from]
122        #[source]
123        AccessError,
124    ),
125}
126
127#[derive(Debug, Error)]
128pub enum MonsterAttackError {
129    #[error("Invalid monster ID")]
130    InvalidId(
131        #[from]
132        #[source]
133        monster::InvalidId,
134    ),
135    #[error("Failed to access map location")]
136    MapAccess(
137        #[from]
138        #[source]
139        AccessError,
140    ),
141}
142
143#[derive(Debug, Error)]
144pub enum MonsterGrowlError {
145    #[error("Invalid monster ID")]
146    InvalidId(
147        #[from]
148        #[source]
149        monster::InvalidId,
150    ),
151}
152
153#[derive(Debug, Error)]
154pub enum MonsterFollowError {
155    #[error("Invalid monster ID")]
156    InvalidId(
157        #[from]
158        #[source]
159        monster::InvalidId,
160    ),
161    #[error("Failed to move monster")]
162    MoveMonster(#[from] MoveMonsterError),
163}
164
165fn blocks_movement(block: Block, this: SpecialBlock) -> bool {
166    match block {
167        Block::Placeable(block) => block != PlaceableBlock::Air,
168        Block::Special(block) => {
169            block != this
170                && matches!(
171                    block,
172                    SpecialBlock::Player | SpecialBlock::Monster(_)
173                )
174        },
175    }
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize)]
179pub struct Game {
180    map: Map,
181    player: Player,
182    monster_registry: monster::Registry,
183    event_schedule: HashMap<u64, Vec<Event>>,
184    event_epoch: u64,
185    meta_events: VecDeque<MetaEvent>,
186}
187
188impl Game {
189    pub fn new(mut map: Map, player: Player) -> Result<Self, InitError> {
190        if let Err(source) = map
191            .set_block(player.position().head(), SpecialBlock::Player)
192            .and_then(|_| {
193                map.set_block(player.position().pointer(), SpecialBlock::Player)
194            })
195        {
196            return Err(InitError::PlayerOutsideMap {
197                map_rect: map.rect(),
198                player_position: player.position().clone(),
199                source,
200            });
201        }
202        Ok(Self {
203            map,
204            player,
205            monster_registry: monster::Registry::new(),
206            event_schedule: HashMap::new(),
207            event_epoch: 0,
208            meta_events: VecDeque::new(),
209        })
210    }
211
212    pub async fn load(path: &Path) -> Result<Self, LoadError> {
213        task::block_in_place(|| {
214            let file =
215                File::open(&path).map_err(LoadErrorSource::from).map_err(
216                    |source| LoadError { path: path.to_owned(), source },
217                )?;
218            let mut file = BufReader::new(file);
219            serde_json::from_reader(&mut file)
220                .map_err(LoadErrorSource::from)
221                .map_err(|source| LoadError { path: path.to_owned(), source })
222        })
223    }
224
225    pub async fn save(&self, path: &Path) -> Result<(), SaveError> {
226        task::block_in_place(|| {
227            let file =
228                File::create(&path).map_err(SaveErrorSource::from).map_err(
229                    |source| SaveError { path: path.to_owned(), source },
230                )?;
231            let mut file = BufWriter::new(file);
232            serde_json::to_writer(&mut file, self)
233                .map_err(SaveErrorSource::from)
234                .map_err(|source| SaveError {
235                    path: path.to_owned(),
236                    source,
237                })?;
238            Ok(())
239        })
240    }
241
242    pub fn schedule_event(&mut self, event: Event, event_ticks: u32) {
243        let event_ticks = u64::from(event_ticks) + self.event_epoch;
244        self.event_schedule.entry(event_ticks).or_default().push(event);
245    }
246
247    pub fn execute_events(&mut self) -> Result<(), event::ApplyError> {
248        let old_epoch = self.event_epoch;
249        self.event_epoch += 1;
250        for event in
251            self.event_schedule.remove(&old_epoch).into_iter().flatten()
252        {
253            event.apply(self)?;
254        }
255        Ok(())
256    }
257
258    pub fn emit_meta_event(&mut self, meta_event: MetaEvent) {
259        self.meta_events.push_back(meta_event);
260    }
261
262    pub fn read_one_meta_event(&mut self) -> Option<MetaEvent> {
263        self.meta_events.pop_front()
264    }
265
266    pub fn map(&self) -> &Map {
267        &self.map
268    }
269
270    pub fn place_block(
271        &mut self,
272        point: CoordPair,
273        block: PlaceableBlock,
274    ) -> Result<(), AccessError> {
275        self.map.set_placeable_block(point, block)
276    }
277
278    pub fn player(&self) -> &Player {
279        &self.player
280    }
281
282    pub fn move_player_pointer(
283        &mut self,
284        direction: Direction,
285    ) -> Result<(), MovePlayerError> {
286        if self.player.position().facing() == direction {
287            self.move_player_head(direction)?;
288        } else {
289            self.make_player_face(direction)?;
290        }
291        Ok(())
292    }
293
294    pub fn move_player_head(
295        &mut self,
296        direction: Direction,
297    ) -> Result<(), MovePlayerError> {
298        let Ok(new_head) = self
299            .map
300            .rect()
301            .checked_move_point_unit(self.player.position().head(), direction)
302        else {
303            return Ok(());
304        };
305        let Ok(new_pointer) = self
306            .map
307            .rect()
308            .checked_move_point_unit(new_head, self.player.position().facing())
309        else {
310            return Ok(());
311        };
312        if blocks_movement(self.map.get_block(new_head)?, SpecialBlock::Player)
313        {
314            return Ok(());
315        }
316        if blocks_movement(
317            self.map.get_block(new_pointer)?,
318            SpecialBlock::Player,
319        ) {
320            return Ok(());
321        }
322        self.map
323            .set_block(self.player.position().head(), PlaceableBlock::Air)?;
324        self.map
325            .set_block(self.player.position().pointer(), PlaceableBlock::Air)?;
326        self.player.position_mut().set_head(new_head);
327        self.map
328            .set_block(self.player.position().head(), SpecialBlock::Player)?;
329        self.map.set_block(
330            self.player.position().pointer(),
331            SpecialBlock::Player,
332        )?;
333        Ok(())
334    }
335
336    pub fn make_player_face(
337        &mut self,
338        direction: Direction,
339    ) -> Result<(), MovePlayerError> {
340        let Ok(new_head) = self
341            .map()
342            .rect()
343            .checked_move_point_unit(self.player.position().head(), direction)
344        else {
345            return Ok(());
346        };
347        if blocks_movement(self.map.get_block(new_head)?, SpecialBlock::Player)
348        {
349            return Ok(());
350        }
351        self.map
352            .set_block(self.player.position().pointer(), PlaceableBlock::Air)?;
353        self.player.position_mut().face(direction);
354        self.map.set_block(
355            self.player.position().pointer(),
356            SpecialBlock::Player,
357        )?;
358        Ok(())
359    }
360
361    pub fn monster_registry(&self) -> &monster::Registry {
362        &self.monster_registry
363    }
364
365    pub fn try_spawn_moster(
366        &mut self,
367        pos: MonsterPosition,
368    ) -> Result<(), SpawnMonsterError> {
369        let block_value = self.map().get_block(pos.body())?;
370        if block_value == Block::Placeable(PlaceableBlock::Air) {
371            let monster = Monster::new(pos);
372            let monster_id = self.monster_registry.create_as(monster)?;
373            self.map
374                .set_block(pos.body(), SpecialBlock::Monster(monster_id))?;
375        }
376        Ok(())
377    }
378
379    pub fn vanish_monster(
380        &mut self,
381        id: monster::Id,
382    ) -> Result<(), VanishMonsterError> {
383        let monster = self.monster_registry.remove(id)?;
384        self.map.set_block(monster.position().body(), PlaceableBlock::Air)?;
385        Ok(())
386    }
387
388    pub fn try_move_monster(
389        &mut self,
390        id: monster::Id,
391        direction: Direction,
392    ) -> Result<(), MoveMonsterError> {
393        let pos = self.monster_registry.get_by_id(id)?.position();
394        if pos.facing() == direction {
395            self.move_monster_head(id, direction)?;
396        } else {
397            self.make_monster_face(id, direction)?;
398        }
399        Ok(())
400    }
401
402    pub fn move_monster_head(
403        &mut self,
404        id: monster::Id,
405        direction: Direction,
406    ) -> Result<(), MoveMonsterError> {
407        let pos = self.monster_registry.get_by_id(id)?.position();
408        let Ok(new_body) =
409            self.map.rect().checked_move_point_unit(pos.body(), direction)
410        else {
411            return Ok(());
412        };
413        if blocks_movement(
414            self.map.get_block(new_body)?,
415            SpecialBlock::Monster(id),
416        ) {
417            return Ok(());
418        }
419        self.map.set_block(pos.body(), PlaceableBlock::Air)?;
420        self.monster_registry
421            .get_by_id_mut(id)?
422            .position_mut()
423            .set_body(new_body);
424        self.map.set_block(new_body, SpecialBlock::Monster(id))?;
425        Ok(())
426    }
427
428    pub fn make_monster_face(
429        &mut self,
430        id: monster::Id,
431        direction: Direction,
432    ) -> Result<(), MoveMonsterError> {
433        self.monster_registry.get_by_id_mut(id)?.position_mut().face(direction);
434        Ok(())
435    }
436
437    pub fn monster_attack(
438        &mut self,
439        id: monster::Id,
440    ) -> Result<(), MonsterAttackError> {
441        let monster = self.monster_registry.get_by_id(id)?;
442        let Some(next_block) = monster
443            .position()
444            .body()
445            .checked_move_unit(monster.position().facing())
446        else {
447            return Ok(());
448        };
449        self.emit_meta_event(MetaEvent::MonsterHit(next_block));
450        let Ok(block) = self.map.get_block(next_block) else {
451            return Ok(());
452        };
453        match block {
454            Block::Special(SpecialBlock::Player) => {
455                self.player.damage(1);
456            },
457            _ => (),
458        }
459        Ok(())
460    }
461
462    pub fn monster_growl(
463        &mut self,
464        id: monster::Id,
465    ) -> Result<(), MonsterGrowlError> {
466        let monster = self.monster_registry.get_by_id(id)?;
467        self.emit_meta_event(MetaEvent::MonsterGrowl(
468            monster.position().body(),
469        ));
470        Ok(())
471    }
472
473    pub fn monster_follow_player(
474        &mut self,
475        id: monster::Id,
476        speed: Coord,
477        limit: u32,
478    ) -> Result<(), MonsterFollowError> {
479        let monster = self.monster_registry.get_by_id(id)?;
480
481        let (_, Some(vec)) = self
482            .player()
483            .position()
484            .head()
485            .diagonal_direction_from(monster.position().body())
486            .max_with_axis_by_key(|opt| opt.map(|vec| vec.magnitude))
487        else {
488            return Ok(());
489        };
490
491        self.try_move_monster(id, vec.direction)?;
492
493        if let Some(new_limit) = limit.checked_sub(1) {
494            self.schedule_event(
495                Event::FollowPlayer { id, period: speed, limit: new_limit },
496                u32::from(speed),
497            );
498        }
499        Ok(())
500    }
501
502    pub fn damage_player(&mut self, amount: StatValue) {
503        self.player.damage(amount);
504    }
505
506    pub fn heal_player(&mut self, amount: StatValue) {
507        self.player.heal(amount);
508    }
509}