diff options
author | Guillaume Pasquet <dev@etenil.net> | 2019-11-17 02:05:17 +0000 |
---|---|---|
committer | Guillaume Pasquet <dev@etenil.net> | 2019-11-17 02:05:17 +0000 |
commit | 494037db77088b520909a78ec1d425de6fec27dc (patch) | |
tree | 4185a0c3a950175278fdd89ee7e069eb5ddb78cc | |
parent | 9821aa00244ce3c32281e2f9fcf71bb35f308acd (diff) |
Rustfmt FTW
-rw-r--r-- | src/main.rs | 46 | ||||
-rw-r--r-- | src/state.rs | 18 | ||||
-rw-r--r-- | src/tiling.rs | 22 | ||||
-rw-r--r-- | src/world.rs | 842 |
4 files changed, 476 insertions, 452 deletions
diff --git a/src/main.rs b/src/main.rs index 3da2a33..2e45c58 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,28 +1,23 @@ -extern crate rand; extern crate pancurses; +extern crate rand; extern crate text_io; -mod state; mod entities; -mod world; +mod state; mod tiling; +mod world; -use entities::{Player, Character, Entity}; -use pancurses::{ - initscr, - endwin, - Input, - noecho -}; -use std::env; +use entities::{Character, Entity, Player}; +use pancurses::{endwin, initscr, noecho, Input}; use state::State; -use world::{Dungeon, LEFT, RIGHT, UP, DOWN}; +use std::env; +use world::{Dungeon, DOWN, LEFT, RIGHT, UP}; fn get_player_name() -> String { match env::var_os("USER") { Some(val) => val.into_string().unwrap(), - None => String::from("Kshar") + None => String::from("Kshar"), } } @@ -30,15 +25,12 @@ fn main() { let window = initscr(); let mut state = State::new( - Player::new( - get_player_name(), - String::from("Warrior"), - 30, - 10, - 10, - 20 + Player::new(get_player_name(), String::from("Warrior"), 30, 10, 10, 20), + Dungeon::new( + window.get_max_x() as usize, + (window.get_max_y() - 2) as usize, + 5, ), - Dungeon::new(window.get_max_x() as usize, (window.get_max_y() - 2) as usize, 5), ); state.init(); @@ -56,20 +48,22 @@ fn main() { // get input and execute it match window.getch() { - Some(Input::Character('?')) => { window.addstr("q: quit\n"); }, + Some(Input::Character('?')) => { + window.addstr("q: quit\n"); + } Some(Input::Character('j')) => { state.player.move_by(DOWN).unwrap(); // state.get_player_mut().move_by(DOWN).unwrap(); - }, + } Some(Input::Character('k')) => { state.get_player_mut().move_by(UP).unwrap(); - }, + } Some(Input::Character('h')) => { state.get_player_mut().move_by(LEFT).unwrap(); - }, + } Some(Input::Character('l')) => { state.get_player_mut().move_by(RIGHT).unwrap(); - }, + } Some(Input::Character('q')) => break, Some(_) => (), None => (), diff --git a/src/state.rs b/src/state.rs index 2f09d1e..b5ab6ca 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,14 +1,14 @@ use pancurses::Window; -use crate::tiling::{TileType, TileGrid, tile_to_str}; use crate::entities::{Character, Entity}; +use crate::tiling::{tile_to_str, TileGrid, TileType}; use crate::world::{Dungeon, Generatable, Level}; pub struct State { pub player: Character, dungeon: Dungeon, level: usize, - grid: Option<TileGrid> + grid: Option<TileGrid>, } pub fn draw_block(window: &Window, block: &TileType) { @@ -21,7 +21,7 @@ impl State { player, dungeon, level: 0, - grid: None + grid: None, } } @@ -51,8 +51,14 @@ impl State { } let dirt = entity.get_previous_location(); window.mv(dirt.1 as i32, dirt.0 as i32); - draw_block(window, self.grid.as_ref().unwrap().get_block_at(dirt.0, dirt.1)); - window.mv(entity.get_location().1 as i32, entity.get_location().0 as i32); + draw_block( + window, + self.grid.as_ref().unwrap().get_block_at(dirt.0, dirt.1), + ); + window.mv( + entity.get_location().1 as i32, + entity.get_location().0 as i32, + ); draw_block(window, entity.get_tiletype()); } @@ -73,4 +79,4 @@ impl State { pub fn get_player_mut(&mut self) -> &mut Character { &mut self.player } -}
\ No newline at end of file +} diff --git a/src/tiling.rs b/src/tiling.rs index 9bdb071..ef2b90b 100644 --- a/src/tiling.rs +++ b/src/tiling.rs @@ -1,13 +1,13 @@ extern crate pancurses; pub struct TileGrid { - grid: Vec<Vec<TileType>> + grid: Vec<Vec<TileType>>, } impl TileGrid { pub fn new(xsize: usize, ysize: usize) -> TileGrid { let mut grid = TileGrid { - grid: Vec::with_capacity(ysize) + grid: Vec::with_capacity(ysize), }; for _ in 0..ysize { @@ -27,13 +27,17 @@ impl TileGrid { /// Sets a tile if nothing lies underneath it. pub fn set_empty_tile(&mut self, x: usize, y: usize, tile: TileType) { - self.set_tile(x, y, match self.grid[y][x] { - TileType::Empty => tile, - _ => self.grid[y][x].clone() - }) + self.set_tile( + x, + y, + match self.grid[y][x] { + TileType::Empty => tile, + _ => self.grid[y][x].clone(), + }, + ) } - pub fn raw_data(& self) -> & Vec<Vec<TileType>> { + pub fn raw_data(&self) -> &Vec<Vec<TileType>> { &self.grid } @@ -50,7 +54,7 @@ pub fn tile_to_str(tile: &TileType) -> &str { TileType::StairsDown => ">", TileType::StairsUp => "<", TileType::Player => "@", - _ => "?" + _ => "?", } } @@ -66,5 +70,5 @@ pub enum TileType { StairsUp, StairsDown, Character, - Player + Player, } diff --git a/src/world.rs b/src/world.rs index b0668a2..baddf48 100644 --- a/src/world.rs +++ b/src/world.rs @@ -1,411 +1,431 @@ -use crate::entities::{Character, Enemy, Entity};
-use crate::tiling::{TileGrid, TileType, Tileable};
-use rand::Rng;
-use std::cmp::min;
-
-pub type Point = (usize, usize);
-pub type Movement = (i8, i8);
-
-pub enum Direction {
- North,
- South,
- East,
- West,
-}
-
-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<Point, String> {
- 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> {
- // TODO: Detect if the room would leave the grid.
- let endx = self.start.0 + self.width;
- let endy = self.start.1 + self.height;
-
- // Set the walls
- for x in self.start.0..=endx {
- grid.set_empty_tile(x, self.start.1, TileType::Wall);
- grid.set_empty_tile(x, endy, TileType::Wall);
- }
-
- for y in self.start.1..endy {
- grid.set_empty_tile(self.start.0, y, TileType::Wall);
- grid.set_empty_tile(endx, y, 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, TileType::Floor);
- }
- }
-
- Ok(())
- }
-}
-
-struct Corridor {
- start: Point,
- length: usize,
- direction: CorridorType,
-}
-
-impl Corridor {
- fn new(start: Point, length: usize, direction: CorridorType) -> Corridor {
- Corridor {
- start,
- length,
- direction,
- }
- }
-
- pub fn make(start: Point, end: Point) -> Result<Corridor, String> {
- if start.0 != end.0 && start.1 != end.1 {
- return Err(String::from("Start and end points must be aligned to for a corridor"));
- }
-
- let length = distance(start, end);
- if length < 1.0 {
- return Err(String::from("Can't create 0-length corridor"));
- }
-
- let dir = if start.0 == end.0 { CorridorType::Vertical } else { CorridorType::Horizontal };
- 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.round() as usize,
- dir
- ))
- }
-
- pub fn link(start: Point, end: Point) -> Result<Vec<Corridor>, 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, TileType::Wall);
- grid.set_tile(x, y, TileType::Floor);
- grid.set_empty_tile(x + 1, y, 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, TileType::Wall);
- grid.set_tile(x, y, TileType::Floor);
- grid.set_empty_tile(x, y + 1, 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<Room>,
- corridors: Vec<Corridor>,
- pub entities: Vec<Box<dyn Entity>>,
- entrance: Point,
- exit: Point,
-}
-
-pub struct Dungeon {
- xsize: usize,
- ysize: usize,
- depth: usize,
- pub levels: Vec<Level>,
-}
-
-pub trait Generatable {
- fn generate(&mut self);
-}
-
-fn hor_dist(point1: Point, point2: Point) -> f32 {
- point2.0 as f32 - point1.0 as f32
-}
-
-fn ver_dist(point1: Point, point2: Point) -> f32 {
- point2.1 as f32 - point1.1 as f32
-}
-
-/// The distance between 2 points
-fn distance(point1: Point, point2: Point) -> f32 {
- (hor_dist(point1, point2).powf(2.0) + ver_dist(point1, point2).powf(2.0)).sqrt()
-}
-
-impl Dungeon {
- pub fn new(xsize: usize, ysize: usize, depth: usize) -> Dungeon {
- Dungeon {
- xsize,
- ysize,
- depth,
- levels: vec![],
- }
- }
-}
-
-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.get_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.get_exit();
- self.levels.push(level);
- }
- }
-}
-
-impl Level {
- /// Creates a new level of horizontal size `xsize` and vertical size `ysize`.
- /// If start is Some<Point> 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<Point>) -> 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<TileGrid, String> {
- 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, TileType::StairsUp);
- grid.set_tile(self.exit.0, self.exit.1, TileType::StairsDown);
-
- Ok(grid)
- }
-
- pub fn get_start_point(&self) -> Point {
- if self.rooms.len() > 0 {
- return self.rooms[0].center;
- }
- return (0, 0);
- }
-
- pub fn get_entrance(&self) -> Point {
- self.entrance
- }
-
- pub fn get_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;
- }
- }
-
- return false;
- }
-
- fn room_distances(&self, point: Point) -> Vec<(usize, f32)> {
- let mut dists: Vec<(usize, f32)> = self
- .rooms
- .iter()
- .enumerate()
- .map(|(room_num, room): (usize, &Room)| -> (usize, f32) {
- (room_num, distance(point, room.center))
- })
- .collect();
- dists.sort_by(|(_, dista): &(usize, f32), (_, distb): &(usize, f32)| {
- dista.partial_cmp(&distb).unwrap()
- });
- dists
- }
-
- fn random_room(&self) -> Result<Room, String> {
- // 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 room in &self.rooms {
- // Find the nearest room.
- let distances = self.room_distances(room.center);
- let nearest_room = &self.rooms[distances[1].0];
-
- match Corridor::link(room.center, nearest_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
- self.entities.push(Box::<Character>::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,
- (
- room.start.0 + rng.gen_range(0, room.width),
- room.start.1 + rng.gen_range(0, room.height),
- ),
- )));
- }
- }
-}
-
-#[cfg(test)]
-mod tests {
- use super::*;
-
- #[test]
- fn test_generates_world() {
- let mut level = Level::new(128, 128, 1, None);
- level.generate();
- }
-}
+use crate::entities::{Character, Enemy, Entity}; +use crate::tiling::{TileGrid, TileType, Tileable}; +use rand::Rng; +use std::cmp::min; + +pub type Point = (usize, usize); +pub type Movement = (i8, i8); + +pub enum Direction { + North, + South, + East, + West, +} + +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<Point, String> { + 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> { + // TODO: Detect if the room would leave the grid. + let endx = self.start.0 + self.width; + let endy = self.start.1 + self.height; + + // Set the walls + for x in self.start.0..=endx { + grid.set_empty_tile(x, self.start.1, TileType::Wall); + grid.set_empty_tile(x, endy, TileType::Wall); + } + + for y in self.start.1..endy { + grid.set_empty_tile(self.start.0, y, TileType::Wall); + grid.set_empty_tile(endx, y, 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, TileType::Floor); + } + } + + Ok(()) + } +} + +struct Corridor { + start: Point, + length: usize, + direction: CorridorType, +} + +impl Corridor { + fn new(start: Point, length: usize, direction: CorridorType) -> Corridor { + Corridor { + start, + length, + direction, + } + } + + pub fn make(start: Point, end: Point) -> Result<Corridor, String> { + if start.0 != end.0 && start.1 != end.1 { + return Err(String::from( + "Start and end points must be aligned to for a corridor", + )); + } + + let length = distance(start, end); + if length < 1.0 { + return Err(String::from("Can't create 0-length corridor")); + } + + let dir = if start.0 == end.0 { + CorridorType::Vertical + } else { + CorridorType::Horizontal + }; + 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.round() as usize, dir)) + } + + pub fn link(start: Point, end: Point) -> Result<Vec<Corridor>, 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, TileType::Wall); + grid.set_tile(x, y, TileType::Floor); + grid.set_empty_tile(x + 1, y, 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, TileType::Wall); + grid.set_tile(x, y, TileType::Floor); + grid.set_empty_tile(x, y + 1, 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<Room>, + corridors: Vec<Corridor>, + pub entities: Vec<Box<dyn Entity>>, + entrance: Point, + exit: Point, +} + +pub struct Dungeon { + xsize: usize, + ysize: usize, + depth: usize, + pub levels: Vec<Level>, +} + +pub trait Generatable { + fn generate(&mut self); +} + +fn hor_dist(point1: Point, point2: Point) -> f32 { + point2.0 as f32 - point1.0 as f32 +} + +fn ver_dist(point1: Point, point2: Point) -> f32 { + point2.1 as f32 - point1.1 as f32 +} + +/// The distance between 2 points +fn distance(point1: Point, point2: Point) -> f32 { + (hor_dist(point1, point2).powf(2.0) + ver_dist(point1, point2).powf(2.0)).sqrt() +} + +impl Dungeon { + pub fn new(xsize: usize, ysize: usize, depth: usize) -> Dungeon { + Dungeon { + xsize, + ysize, + depth, + levels: vec![], + } + } +} + +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.get_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.get_exit(); + self.levels.push(level); + } + } +} + +impl Level { + /// Creates a new level of horizontal size `xsize` and vertical size `ysize`. + /// If start is Some<Point> 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<Point>) -> 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<TileGrid, String> { + 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, TileType::StairsUp); + grid.set_tile(self.exit.0, self.exit.1, TileType::StairsDown); + + Ok(grid) + } + + pub fn get_start_point(&self) -> Point { + if self.rooms.len() > 0 { + return self.rooms[0].center; + } + return (0, 0); + } + + pub fn get_entrance(&self) -> Point { + self.entrance + } + + pub fn get_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; + } + } + + return false; + } + + fn room_distances(&self, point: Point) -> Vec<(usize, f32)> { + let mut dists: Vec<(usize, f32)> = self + .rooms + .iter() + .enumerate() + .map(|(room_num, room): (usize, &Room)| -> (usize, f32) { + (room_num, distance(point, room.center)) + }) + .collect(); + dists.sort_by(|(_, dista): &(usize, f32), (_, distb): &(usize, f32)| { + dista.partial_cmp(&distb).unwrap() + }); + dists + } + + fn random_room(&self) -> Result<Room, String> { + // 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 room in &self.rooms { + // Find the nearest room. + let distances = self.room_distances(room.center); + let nearest_room = &self.rooms[distances[1].0]; + + match Corridor::link(room.center, nearest_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 + self.entities.push(Box::<Character>::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, + ( + room.start.0 + rng.gen_range(0, room.width), + room.start.1 + rng.gen_range(0, room.height), + ), + ))); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_generates_world() { + let mut level = Level::new(128, 128, 1, None); + level.generate(); + } +} |