From 494037db77088b520909a78ec1d425de6fec27dc Mon Sep 17 00:00:00 2001 From: Guillaume Pasquet Date: Sun, 17 Nov 2019 02:05:17 +0000 Subject: Rustfmt FTW --- src/main.rs | 46 ++-- src/state.rs | 18 +- src/tiling.rs | 22 +- 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 + grid: Option, } 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> + grid: Vec>, } 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> { + pub fn raw_data(&self) -> &Vec> { &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 { - 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 { - 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, 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, - 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); -} - -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 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, 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 { - // 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::::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 { + 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 { + 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, 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, + 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); +} + +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 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, 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 { + // 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::::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(); + } +} -- cgit v1.2.3