//! The `map` module contains structures of the dungeon game map //! including the current `Floor`, and map `Tile`. use strum::IntoEnumIterator; use strum_macros::EnumIter; use crate::wfc::Wfc; use std::{ cell::RefCell, hash::{DefaultHasher, Hash, Hasher}, }; use crate::{const_pos, pos::Pos}; /// `MAP_SIZE` is the size of the size of the dungeon grid. pub const MAP_SIZE: u16 = 100; /// `MAP_SIZE` as a usize pub const MAP_SIZE_USIZE: usize = MAP_SIZE as usize; /// The number of tiles in the dungeon grid pub const TILE_COUNT: usize = MAP_SIZE_USIZE * MAP_SIZE_USIZE; /// The `Tile` enum represents what is (or is not) at /// any given spot in the dungeon grid. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, EnumIter)] pub enum Tile { /// `Wall` represents an impassible wall Wall, /// `Air` represents empty walkable space Air, /// `Stairs` represents stairs to another floor Stairs, } impl Tile { /// Returns a list of all possible tiles pub fn values() -> impl Iterator { Self::iter() } /// Returns if the tile is a wall pub fn is_wall(self) -> bool { self == Self::Wall } } impl Default for Tile { fn default() -> Self { Self::Air } } /// The `Floor` type represents the current playing /// grid of the dungeon. It contains the tiles of the /// grid, and the starting position of the player. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Floor { /// The dungeon grid tiles: Box<[Tile; TILE_COUNT]>, /// The position the player starts at player_start: Pos, /// The seed used when generating the dungeon grid seed: u64, /// The computed hash of the tile map hash: RefCell, /// If the tiles are dirty (hash needs to be recomputed) dirty: RefCell, } impl Floor { /// Internal constructor for `Floor` fn new(tiles: Box<[Tile; TILE_COUNT]>, player_start: Pos, seed: u64) -> Self { Self { tiles, player_start, seed, hash: RefCell::new(0), dirty: RefCell::new(true), } } /// Generates a dungeon `Floor` using wave function collapse. /// /// # Examples /// /// ```no_run /// use dungeon::Floor; /// /// let floor = Floor::generate(); /// ``` #[must_use] pub fn generate() -> Self { let seed = rand::random(); Self::generate_seeded(seed) } /// Genreates a dungeon `Floor` using wave function collapse provided with a seed. /// /// The provided seed is used for randomness in the wave function /// collapse algorithm. /// /// # Examples /// /// ```no_run /// use dungeon::Floor; /// /// /// here is our very seedy seed /// let seed = 2893249402u64; /// let floor = Floor::generate_seeded(seed); /// ``` #[must_use] pub fn generate_seeded(seed: u64) -> Self { let mut wfc = Wfc::new(seed, MAP_SIZE); wfc.initialize_states(); wfc.run(); // TODO unimplemented!() } /// Returns the start position of the player #[must_use] pub const fn player_start(&self) -> Pos { self.player_start } /// Returns the seed used to generate the map #[must_use] pub const fn seed(&self) -> u64 { self.seed } /// Returns a `Tile` on the dungeon grid at `Pos`. #[must_use] pub const fn get(&self, pos: Pos) -> Tile { let idx = pos.idx(); self.tiles[idx] } /// Returns a multable reference to a `Tile` on the dungeon grid at `Pos`. #[must_use] pub fn get_mut(&mut self, pos: Pos) -> &mut Tile { *self.dirty.get_mut() = true; let idx = pos.idx(); &mut self.tiles[idx] } /// Returns a reference to all tiles in the `Floor`. /// The size of this lise will always be `TILE_COUNT` long. #[must_use] pub const fn tiles(&self) -> &[Tile] { &*self.tiles } /// Returns a mutable reference to all tiles in the `Floor`. /// The size of this lise will always be `TILE_COUNT` long. #[must_use] pub fn tiles_mut(&mut self) -> &mut [Tile] { *self.dirty.get_mut() = true; &mut *self.tiles } /// Computes the hash of the tile map #[must_use] pub fn hash(&self) -> u64 { // initial (immutable) dirty check let dirty = self.dirty.borrow(); if !*dirty { return *self.hash.borrow(); } drop(dirty); // recompute hash let mut dirty = self.dirty.borrow_mut(); let mut hash = self.hash.borrow_mut(); let mut s = DefaultHasher::new(); self.tiles.hash(&mut s); *hash = s.finish(); *dirty = false; *hash } } impl Default for Floor { /// Returns a floor with a single set of walls on the map border fn default() -> Self { let player_start = const_pos!(1, 1); let mut tiles = Box::new([Tile::Air; TILE_COUNT]); let seed = 0u64; for pos in Pos::values() { if pos.is_border() { tiles[pos.idx()] = Tile::Wall; } } Self::new(tiles, player_start, seed) } }