1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
|
//! The `map` module contains structures of the dungeon game map
//! including the current `Floor`, and map `Tile`.
use rand::{Rng, rngs::SmallRng};
use strum::IntoEnumIterator;
use strum_macros::EnumIter;
use std::{
cell::RefCell,
fmt::{Display, Write},
hash::{DefaultHasher, Hash, Hasher},
};
use crate::pos::Pos;
/// `MAP_SIZE` is the size of the size of the dungeon grid.
pub const MAP_SIZE: u16 = 48;
/// `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,
/// `Room` represents empty walkable space for a rectangular room
Room,
/// `Hallway` represents empty walkable space for a hallway
Hallway,
/// `Stairs` represents stairs to another floor
Stairs,
}
impl Tile {
/// Returns a list of all possible tiles
pub fn values() -> impl Iterator<Item = Self> {
Self::iter()
}
/// Returns if the tile is a wall
#[must_use]
pub const fn is_wall(self) -> bool {
matches!(self, Self::Wall)
}
/// Returns if the tile is walkable
#[must_use]
pub const fn is_walkable(self) -> bool {
matches!(self, Self::Room | Self::Hallway | Self::Stairs)
}
// Index by u16
}
impl Display for Tile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let char = match self {
Self::Wall => '#',
Self::Room => '.',
Self::Hallway => ',',
Self::Stairs => '>',
};
f.write_char(char)
}
}
/// 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(Debug, Clone)]
pub struct Floor {
/// The dungeon grid
tiles: Box<[Tile; TILE_COUNT]>,
/// The position the player starts at
player_start: Pos,
/// The computed hash of the tile map
hash: RefCell<u64>,
/// If the tiles are dirty (hash needs to be recomputed)
dirty: RefCell<bool>,
}
impl Floor {
/// Construct a floor from its components
pub const fn new(tiles: Box<[Tile; TILE_COUNT]>, player_start: Pos) -> Self {
Self {
tiles,
player_start,
hash: RefCell::new(0),
dirty: RefCell::new(true),
}
}
/// Returns the start position of the player
#[must_use]
pub const fn player_start(&self) -> Pos {
self.player_start
}
/// 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
}
/// Returns the neighbors of a tile inside the floor, checking
/// that the neighbor positions are the same tile type as in `pos`.
pub fn neighbors(&self, pos: &Pos) -> impl Iterator<Item = Pos> {
pos.neighbors().filter(|p| self.get(*p).is_walkable())
}
/// Computes the hash of the tile map
#[must_use]
pub fn hash(&self) -> u64 {
// initial (immutable) dirty check
if !*self.dirty.borrow() {
return *self.hash.borrow();
}
// 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
}
/// Returns a random open (no wall) position
#[must_use]
pub fn random_walkable_pos(&self, rng: &mut SmallRng) -> Pos {
loop {
let pos = rng.random();
if !self.get(pos).is_walkable() {
continue;
}
break pos;
}
}
}
impl Display for Floor {
/// Display the floor as a string for debugging
///
/// # Examples
/// ```no_run
/// use dungeon::Dungeon;
/// let dungeon = Dungeon::random();
/// let floor = &dungeon.floor;
/// println!("{floor}");
/// ```
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for pos in Pos::values() {
// If it's the player start, show 'P'
if self.player_start == pos {
f.write_char('P')?;
continue;
}
// Otherwise, show the tile character
let tile = self.get(pos);
tile.fmt(f)?;
// Newline at the end of each row
if pos.xy().0 == MAP_SIZE - 1 {
f.write_char('\n')?;
}
}
Ok(())
}
}
/// Tests
#[cfg(test)]
mod tests {
use crate::Dungeon;
// Test floor printing
#[test]
fn test_floor_display() {
let dungeon = Dungeon::random();
let floor = &dungeon.floor;
// Print the display for visual inspection
println!("{floor}");
}
}
|