aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGuillaume Pasquet <dev@etenil.net>2019-11-17 02:05:17 +0000
committerGuillaume Pasquet <dev@etenil.net>2019-11-17 02:05:17 +0000
commit494037db77088b520909a78ec1d425de6fec27dc (patch)
tree4185a0c3a950175278fdd89ee7e069eb5ddb78cc
parent9821aa00244ce3c32281e2f9fcf71bb35f308acd (diff)
Rustfmt FTW
-rw-r--r--src/main.rs46
-rw-r--r--src/state.rs18
-rw-r--r--src/tiling.rs22
-rw-r--r--src/world.rs842
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();
+ }
+}