summaryrefslogtreecommitdiff
path: root/src/dungeon/mod.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/dungeon/mod.rs')
-rw-r--r--src/dungeon/mod.rs156
1 files changed, 156 insertions, 0 deletions
diff --git a/src/dungeon/mod.rs b/src/dungeon/mod.rs
new file mode 100644
index 0000000..d3227a5
--- /dev/null
+++ b/src/dungeon/mod.rs
@@ -0,0 +1,156 @@
+//! Turn-based dungeons: the map, field of view, derelict-ship generation,
+//! and the rules of a crawl. Drawing lives in `scenes::derelict`.
+
+pub mod crawl;
+pub mod fov;
+pub mod ship;
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
+pub struct Pos {
+ pub x: i32,
+ pub y: i32,
+}
+
+impl Pos {
+ pub const fn new(x: i32, y: i32) -> Pos {
+ Pos { x, y }
+ }
+
+ pub const fn offset(self, dx: i32, dy: i32) -> Pos {
+ Pos::new(self.x + dx, self.y + dy)
+ }
+
+ /// Steps from here to `other` when diagonal moves are allowed.
+ pub fn steps_to(self, other: Pos) -> i32 {
+ (self.x - other.x).abs().max((self.y - other.y).abs())
+ }
+
+ /// The eight cells around this one.
+ pub fn neighbours(self) -> impl Iterator<Item = Pos> {
+ const DIRS: [(i32, i32); 8] =
+ [(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)];
+ DIRS.into_iter().map(move |(dx, dy)| self.offset(dx, dy))
+ }
+}
+
+#[derive(Clone, Copy, Debug, PartialEq, Eq)]
+pub enum Tile {
+ /// Outside the ship.
+ Void,
+ /// The outer skin of the ship.
+ Hull,
+ /// An inside wall.
+ Wall,
+ Floor,
+ /// Floor strewn with wreckage. Walkable; it just looks different.
+ Debris,
+ Door {
+ open: bool,
+ },
+ /// The way out: the airlock your own ship is docked to.
+ Airlock,
+}
+
+impl Tile {
+ /// Whether you can step onto it. Closed doors open when bumped instead,
+ /// and stepping into the airlock leaves the ship.
+ pub fn walkable(self) -> bool {
+ matches!(self, Tile::Floor | Tile::Debris | Tile::Door { open: true })
+ }
+
+ pub fn blocks_sight(self) -> bool {
+ matches!(
+ self,
+ Tile::Void | Tile::Hull | Tile::Wall | Tile::Door { open: false } | Tile::Airlock
+ )
+ }
+}
+
+/// A named area of the map, shown to the player while they stand in it.
+pub struct Room {
+ pub name: &'static str,
+}
+
+pub struct Map {
+ pub width: i32,
+ pub height: i32,
+ tiles: Vec<Tile>,
+ room_of: Vec<Option<u16>>,
+ pub rooms: Vec<Room>,
+}
+
+impl Map {
+ pub fn new(width: i32, height: i32, fill: Tile) -> Map {
+ let cells = (width * height) as usize;
+ Map {
+ width,
+ height,
+ tiles: vec![fill; cells],
+ room_of: vec![None; cells],
+ rooms: Vec::new(),
+ }
+ }
+
+ pub fn contains(&self, p: Pos) -> bool {
+ p.x >= 0 && p.y >= 0 && p.x < self.width && p.y < self.height
+ }
+
+ /// Index into a per-cell `Vec` laid out like the map. Panics off the map.
+ pub fn index(&self, p: Pos) -> usize {
+ assert!(self.contains(p), "{p:?} is off the map");
+ (p.y * self.width + p.x) as usize
+ }
+
+ /// The tile at `p`; everything off the map is `Void`.
+ pub fn tile(&self, p: Pos) -> Tile {
+ if self.contains(p) { self.tiles[self.index(p)] } else { Tile::Void }
+ }
+
+ pub fn set(&mut self, p: Pos, tile: Tile) {
+ let i = self.index(p);
+ self.tiles[i] = tile;
+ }
+
+ pub fn room_at(&self, p: Pos) -> Option<&Room> {
+ if !self.contains(p) {
+ return None;
+ }
+ self.room_of[self.index(p)].map(|r| &self.rooms[usize::from(r)])
+ }
+
+ pub fn add_room(&mut self, name: &'static str) -> u16 {
+ self.rooms.push(Room { name });
+ (self.rooms.len() - 1) as u16
+ }
+
+ pub fn set_room(&mut self, p: Pos, room: Option<u16>) {
+ let i = self.index(p);
+ self.room_of[i] = room;
+ }
+
+ pub fn positions(&self) -> impl Iterator<Item = Pos> + use<> {
+ let (w, h) = (self.width, self.height);
+ (0..h).flat_map(move |y| (0..w).map(move |x| Pos::new(x, y)))
+ }
+
+ /// Steps from `from` to every cell reachable through walkable tiles and
+ /// closed doors, moving diagonally too; `None` where unreachable.
+ pub fn distances_from(&self, from: Pos) -> Vec<Option<u32>> {
+ let mut dist = vec![None; self.tiles.len()];
+ let mut queue = std::collections::VecDeque::from([from]);
+ dist[self.index(from)] = Some(0);
+ while let Some(p) = queue.pop_front() {
+ let d = dist[self.index(p)].unwrap();
+ for n in p.neighbours() {
+ let tile = self.tile(n);
+ if (tile.walkable() || tile == (Tile::Door { open: false }))
+ && dist[self.index(n)].is_none()
+ {
+ dist[self.index(n)] = Some(d + 1);
+ queue.push_back(n);
+ }
+ }
+ }
+ dist
+ }
+}