use crate::entities::{Character, Enemy, Entity}; use crate::tiling::{Tile, TileGrid, TileType, Tileable}; use rand::Rng; use std::cmp::{min, PartialEq}; use std::fmt; pub type Point = (usize, usize); pub type Movement = (i8, i8); #[derive(PartialEq)] enum CorridorType { Horizontal, Vertical, } pub const LEFT: Movement = (-1, 0); pub const RIGHT: Movement = (1, 0); pub const UP: Movement = (0, -1); pub const DOWN: Movement = (0, 1); pub fn apply_movement(point: Point, movement: Movement) -> Result { let x = point.0 as i32 + movement.0 as i32; let y = point.1 as i32 + movement.1 as i32; if x < 0 || y < 0 { return Err(String::from("Can't move point off screen")); } Ok((x as usize, y as usize)) } struct Room { start: Point, center: Point, width: usize, height: usize, } impl Room { fn new(start: Point, width: usize, height: usize) -> Room { Room { start, width, height, center: ( start.0 + (width as f32 / 2.0).floor() as usize, start.1 + (height as f32 / 2.0).floor() as usize, ), } } } impl Tileable for Room { fn tile(&self, grid: &mut TileGrid) -> Result<(), String> { let endx = self.start.0 + self.width; let endy = self.start.1 + self.height; if endx >= grid.xsize() || endy > grid.ysize() { return Err(String::from("Room outside of grid bounds")); } // Set the walls for x in self.start.0..=endx { grid.set_empty_tile(x, self.start.1, Tile::from(TileType::Wall)); grid.set_empty_tile(x, endy, Tile::from(TileType::Wall)); } for y in self.start.1..endy { grid.set_empty_tile(self.start.0, y, Tile::from(TileType::Wall)); grid.set_empty_tile(endx, y, Tile::from(TileType::Wall)); } // Fill the room for x in (self.start.0 + 1)..endx { for y in (self.start.1 + 1)..endy { grid.set_tile(x, y, Tile::from(TileType::Floor)); } } Ok(()) } } #[derive(PartialEq)] struct Corridor { start: Point, length: usize, direction: CorridorType, } impl fmt::Debug for Corridor { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!( f, "{} corridor from ({},{}) of length {}", match self.direction { CorridorType::Horizontal => "Horizontal", CorridorType::Vertical => "Vertical", }, self.start.0, self.start.1, self.length ) } } impl Corridor { fn new(start: Point, length: usize, direction: CorridorType) -> Corridor { Corridor { start, length, direction, } } pub fn make(start: Point, end: Point) -> Result { if start.0 != end.0 && start.1 != end.1 { return Err(String::from( "Start and end points must be aligned to make a corridor", )); } let (dir, length) = if start.0 == end.0 { ( CorridorType::Vertical, start.1.max(end.1) - start.1.min(end.1), ) } else { ( CorridorType::Horizontal, start.0.max(end.0) - start.0.min(end.0), ) }; if length == 0 { return Err(String::from("Can't create 0-length corridor")); } let origin = match dir { CorridorType::Horizontal => { if start.0 < end.0 { start } else { end } } CorridorType::Vertical => { if start.1 < end.1 { start } else { end } } }; Ok(Corridor::new(origin, length, dir)) } pub fn link(start: Point, end: Point) -> Result, String> { if start.0 == end.0 || start.1 == end.1 { return Ok(vec![Corridor::make(start, end)?]); } let mut rng = rand::thread_rng(); let start_hor = rng.gen_bool(0.5); let angle_point = if start_hor { (end.0, start.1) } else { (start.0, end.1) }; Ok(vec![ Corridor::make(start, angle_point)?, Corridor::make(angle_point, end)?, ]) } fn tile_vertical(&self, grid: &mut TileGrid) { let x = self.start.0; let endy = self.start.1 + self.length; for y in self.start.1..endy { grid.set_empty_tile(x - 1, y, Tile::from(TileType::Wall)); grid.set_tile(x, y, Tile::from(TileType::Floor)); grid.set_empty_tile(x + 1, y, Tile::from(TileType::Wall)); } // Wall ends grid.set_empty_tile(x - 1, self.start.1, Tile::from(TileType::Wall)); grid.set_empty_tile(x, self.start.1, Tile::from(TileType::Wall)); grid.set_empty_tile(x + 1, self.start.1, Tile::from(TileType::Wall)); grid.set_empty_tile(x - 1, endy, Tile::from(TileType::Wall)); grid.set_empty_tile(x, endy, Tile::from(TileType::Wall)); grid.set_empty_tile(x + 1, endy, Tile::from(TileType::Wall)); } fn tile_horizontal(&self, grid: &mut TileGrid) { let y = self.start.1; let endx = self.start.0 + self.length; for x in self.start.0..endx { grid.set_empty_tile(x, y - 1, Tile::from(TileType::Wall)); grid.set_tile(x, y, Tile::from(TileType::Floor)); grid.set_empty_tile(x, y + 1, Tile::from(TileType::Wall)); } // Wall ends grid.set_empty_tile(self.start.0, y - 1, Tile::from(TileType::Wall)); grid.set_empty_tile(self.start.0, y, Tile::from(TileType::Wall)); grid.set_empty_tile(self.start.0, y + 1, Tile::from(TileType::Wall)); grid.set_empty_tile(endx, y - 1, Tile::from(TileType::Wall)); grid.set_empty_tile(endx, y, Tile::from(TileType::Wall)); grid.set_empty_tile(endx, y + 1, Tile::from(TileType::Wall)); } } impl Tileable for Corridor { fn tile(&self, grid: &mut TileGrid) -> Result<(), String> { // TODO: ensure the corridor isn't leaving the grid. match self.direction { CorridorType::Horizontal => self.tile_horizontal(grid), CorridorType::Vertical => self.tile_vertical(grid), } Ok(()) } } pub struct Level { xsize: usize, ysize: usize, depth: usize, rooms: Vec, corridors: Vec, pub entities: Vec>, entrance: Point, exit: Point, } pub struct Dungeon { xsize: usize, ysize: usize, depth: usize, pub levels: Vec, } pub trait Generatable { fn generate(&mut self); } impl Dungeon { pub fn new(xsize: usize, ysize: usize, depth: usize) -> Dungeon { Dungeon { xsize, ysize, depth, levels: vec![], } } pub fn xsize(&self) -> usize { self.xsize } pub fn ysize(&self) -> usize { self.ysize } pub fn depth(&self) -> usize { self.depth } } impl Generatable for Dungeon { fn generate(&mut self) { let mut level = Level::new(self.xsize, self.ysize, 1, None); level.generate(); let mut next_entrance = level.exit(); self.levels.push(level); for d in 1..self.depth { level = Level::new(self.xsize, self.ysize, d + 1, Some(next_entrance)); level.generate(); next_entrance = level.exit(); self.levels.push(level); } } } impl Level { /// Creates a new level of horizontal size `xsize` and vertical size `ysize`. /// If start is Some then a room will be created at that point to link /// with an upper room. pub fn new(xsize: usize, ysize: usize, depth: usize, start: Option) -> Level { Level { xsize, ysize, rooms: vec![], corridors: vec![], entities: vec![], entrance: match start { Some(st) => st, None => (0, 0), }, exit: (0, 0), depth, } } pub fn to_tilegrid(&self) -> Result { let mut grid = TileGrid::new(self.xsize, self.ysize); for room in &self.rooms { room.tile(&mut grid)?; } for corridor in &self.corridors { corridor.tile(&mut grid)?; } grid.set_tile( self.entrance.0, self.entrance.1, Tile::from(TileType::StairsUp), ); grid.set_tile(self.exit.0, self.exit.1, Tile::from(TileType::StairsDown)); Ok(grid) } pub fn start_point(&self) -> Point { if !self.rooms.is_empty() { return self.rooms[0].center; } (0, 0) } // pub fn entrance(&self) -> Point { // self.entrance // } pub fn exit(&self) -> Point { self.exit } fn overlaps(&self, start: Point, width: usize, height: usize, padding: usize) -> bool { for room in &self.rooms { if room.start.0 < start.0 + width + padding && room.start.0 + room.width + padding > start.0 && room.start.1 < start.1 + height + padding && room.start.1 + room.height + padding > start.1 { return true; } } false } fn random_room(&self) -> Result { // TODO: Detect when not enough space is left to allocate a room. let mut rng = rand::thread_rng(); let room_width = rng.gen_range(4, 12); let room_height = rng.gen_range(4, 12); // TODO: Find a way to write a lambda to generate the start point. let mut start: Point = ( rng.gen_range(0, self.xsize - room_width), rng.gen_range(0, self.ysize - room_height), ); while self.overlaps(start, room_width, room_height, 2) { start = ( rng.gen_range(0, self.xsize - room_width), rng.gen_range(0, self.ysize - room_height), ); } Ok(Room::new(start, room_width, room_height)) } fn centered_room(&self, center: Point) -> Room { let mut rng = rand::thread_rng(); let room_width: usize = rng.gen_range(3, min(min(12, (self.xsize - center.0) * 2), center.0 * 2)); let room_height: usize = rng.gen_range(3, min(min(12, (self.ysize - center.1) * 2), center.1 * 2)); let start = ( (center.0 as f32 - (room_width as f32 / 2f32)).floor() as usize, (center.1 as f32 - (room_height as f32 / 2f32)).floor() as usize, ); Room::new(start, room_width, room_height) } } impl Generatable for Level { fn generate(&mut self) { let mut rng = rand::thread_rng(); let room_number = rng.gen_range(3, 5); if self.entrance != (0, 0) { self.rooms.push(self.centered_room(self.entrance)); } // Generate rooms for _ in self.rooms.len()..room_number { self.rooms.push(self.random_room().unwrap()); } // Generate corridors for (i, room) in self.rooms.iter().enumerate() { // Find the nearest room. let next_room = if i == self.rooms.len() - 1 { &self.rooms[0] } else { &self.rooms[i + 1] }; match Corridor::link(room.center, next_room.center) { Ok(mut cor) => self.corridors.append(&mut cor), Err(e) => println!("{}", e), }; } // Create entrance and exit if self.entrance == (0, 0) { self.entrance = self.rooms[0].center; } self.exit = self.rooms.last().unwrap().center; // Populate the level let num_enemies: usize = (self.rooms.len() as f32 * self.depth as f32 * 0.5) as usize; for _ in 0..num_enemies { // Pick a room let mut rng = rand::thread_rng(); let room = &self.rooms[rng.gen_range(0, self.rooms.len() - 1)]; // Create the enemy let enemy_coords = ( room.start.0 + rng.gen_range(0, room.width - 1) + 1, room.start.1 + rng.gen_range(0, room.height - 1) + 1, ); self.entities.push(Box::::new(Enemy::new( String::from("snake"), 2 * self.depth as i32, (2.0 * self.depth as f32 * 0.6).round() as i32, (20.0 * self.depth as f32 * 0.2).max(80.0).round() as i32, 0, enemy_coords, "s", ))); } } } #[cfg(test)] mod tests { use super::*; #[test] fn test_make_corridor_detects_horizontal() { let start = (0, 0); let end = (5, 0); let corridor = Corridor::make(start, end).unwrap(); assert_eq!(corridor, Corridor::new(start, 5, CorridorType::Horizontal)); } #[test] fn test_make_corridor_detects_vertical() { let start = (0, 0); let end = (0, 5); let corridor = Corridor::make(start, end).unwrap(); assert_eq!(corridor, Corridor::new(start, 5, CorridorType::Vertical)); } #[test] #[should_panic] fn test_make_corridor_with_overlapping_points_should_panic() { Corridor::make((0, 0), (0, 0)).unwrap(); } #[test] #[should_panic] fn test_make_corridor_with_misaligned_points_should_panic() { Corridor::make((3, 3), (5, 5)).unwrap(); } #[test] fn test_link_corridors_returns_a_vec_of_corridors() { let cor = Corridor::link((0, 0), (5, 5)).unwrap(); let exp_horz = vec![ Corridor::new((0, 0), 5, CorridorType::Horizontal), Corridor::new((5, 0), 5, CorridorType::Vertical), ]; let exp_vert = vec![ Corridor::new((0, 0), 5, CorridorType::Vertical), Corridor::new((0, 5), 5, CorridorType::Horizontal), ]; match cor[0].direction { CorridorType::Horizontal => assert_eq!(cor, exp_horz), CorridorType::Vertical => assert_eq!(cor, exp_vert), }; } #[test] fn test_link_corridors_returns_a_vec_of_corridors_on_reversed_diagonal_points() { let cor = Corridor::link((5, 5), (0, 0)).unwrap(); let exp_horz = vec![ Corridor::new((0, 5), 5, CorridorType::Horizontal), Corridor::new((0, 0), 5, CorridorType::Vertical), ]; let exp_vert = vec![ Corridor::new((5, 0), 5, CorridorType::Vertical), Corridor::new((0, 0), 5, CorridorType::Horizontal), ]; match cor[0].direction { CorridorType::Horizontal => assert_eq!(cor, exp_horz), CorridorType::Vertical => assert_eq!(cor, exp_vert), }; } #[test] fn test_link_corridors_returns_a_vec_of_corridors_on_reversed_vertical_points() { let cor = Corridor::link((0, 5), (5, 0)).unwrap(); let exp_horz = vec![ Corridor::new((0, 5), 5, CorridorType::Horizontal), Corridor::new((5, 0), 5, CorridorType::Vertical), ]; let exp_vert = vec![ Corridor::new((0, 0), 5, CorridorType::Vertical), Corridor::new((0, 0), 5, CorridorType::Horizontal), ]; match cor[0].direction { CorridorType::Horizontal => assert_eq!(cor, exp_horz), CorridorType::Vertical => assert_eq!(cor, exp_vert), }; } #[test] fn test_link_corridors_returns_a_vec_of_corridors_on_reversed_horizontal_points() { let cor = Corridor::link((5, 0), (0, 5)).unwrap(); let exp_horz = vec![ Corridor::new((0, 0), 5, CorridorType::Horizontal), Corridor::new((0, 0), 5, CorridorType::Vertical), ]; let exp_vert = vec![ Corridor::new((5, 0), 5, CorridorType::Vertical), Corridor::new((0, 5), 5, CorridorType::Horizontal), ]; match cor[0].direction { CorridorType::Horizontal => assert_eq!(cor, exp_horz), CorridorType::Vertical => assert_eq!(cor, exp_vert), }; } #[test] fn test_link_corridors_with_horizontal_aligned_points_returns_one_corridor() { let cor = Corridor::link((0, 0), (5, 0)).unwrap(); assert_eq!(cor.len(), 1); assert_eq!(cor[0], Corridor::new((0, 0), 5, CorridorType::Horizontal)); } #[test] fn test_link_corridors_with_vertical_aligned_points_returns_one_corridor() { let cor = Corridor::link((0, 0), (0, 5)).unwrap(); assert_eq!(cor.len(), 1); assert_eq!(cor[0], Corridor::new((0, 0), 5, CorridorType::Vertical)); } }