diff --git a/src/constants.rs b/src/constants.rs index ff6ca78..9a4bc16 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,11 +1,12 @@ use std::{collections::HashMap, ops::RangeInclusive}; -use crate::monster::MonsterTypes; +use crate::{monster::MonsterTypes, room::RoomType}; + /// the number of rooms in vertical direction -pub const ROOMS_VERTICAL: usize = 7; +pub const ROOMS_HORIZONAL: usize = 8; /// the number of rooms in horizontal direction -pub const ROOMS_HORIZONTAL: usize = 7; +pub const ROOMS_VERTICAL: usize = 7; /// the width of a room in the grid of rooms (number of characters) pub const ROOM_WIDTH: usize = 9; @@ -13,7 +14,7 @@ pub const ROOM_WIDTH: usize = 9; pub const ROOM_HEIGHT: usize = 6; /// How many levels does the dungeon have? -pub const LEVELS: usize = 3; +pub const LEVELS: usize = 2; /// length of a game frame in ms pub const FRAME_LENGTH: u64 = 100; @@ -24,33 +25,89 @@ pub const MIN_WIDTH: u16 = 120; pub const MIN_HEIGHT: u16 = LEVEL_HEIGHT as u16; /// the calculated width of a level -pub const LEVEL_WIDTH: usize = 1 + ROOMS_VERTICAL * ROOM_WIDTH; +pub const LEVEL_WIDTH: usize = 1 + ROOMS_HORIZONAL * ROOM_WIDTH; /// the calculated height of a level -pub const LEVEL_HEIGHT: usize = 1 + ROOMS_HORIZONTAL * ROOM_HEIGHT; +pub const LEVEL_HEIGHT: usize = 1 + ROOMS_VERTICAL * ROOM_HEIGHT; pub fn get_monsters_per_level() -> Vec>> { - let tmp =[ + let tmp = [ // level 1 vec![(MonsterTypes::Rat, 50), (MonsterTypes::Spider, 50)], // level 2 vec![(MonsterTypes::Rat, 50), (MonsterTypes::Snake, 50)], // level 3 - vec![(MonsterTypes::Orc, 34), (MonsterTypes::Skeleton, 33), (MonsterTypes::Snake, 33)], + vec![ + (MonsterTypes::Orc, 34), + (MonsterTypes::Skeleton, 33), + (MonsterTypes::Snake, 33), + ], ]; - if tmp.len() != LEVELS { - panic!("Only {} monster sets defined for {} levels!", tmp.len(), LEVELS); + if tmp.len() < LEVELS { + panic!( + "Only {} monster sets defined for {} levels!", + tmp.len(), + LEVELS + ); } let mut result: Vec>> = vec![]; for (idx, level) in tmp.iter().enumerate() { let mut sum = 0; let mut map: HashMap> = HashMap::new(); for monster in level { - map.insert(monster.0, RangeInclusive::new(sum+1, sum+monster.1)); + map.insert(monster.0, RangeInclusive::new(sum + 1, sum + monster.1)); sum += monster.1; - } if sum != 100 { - panic!("all percentages must add to 100 (was {}) per level, error in level {}!", sum, idx+1); + panic!( + "all percentages must add to 100 (was {}) per level, error in level {}!", + sum, + idx + 1 + ); + } + result.push(map); + } + result +} + +pub fn get_room_type_per_level() -> Vec>> { + let tmp = [ + // level 1 + vec![ + (RoomType::EmptyRoom, 75), + (RoomType::MonsterRoom, 5), + (RoomType::BasicRoom, 20), + ], + // level 2 + vec![ + (RoomType::EmptyRoom, 50), + (RoomType::BasicRoom, 25), + (RoomType::MonsterRoom, 12), + (RoomType::ArtifactRoom, 13), + ], + // level 3 + vec![(RoomType::EmptyRoom, 50), (RoomType::MonsterRoom, 50)], + ]; + if tmp.len() < LEVELS { + panic!( + "Only {} room sets defined for {} levels!", + tmp.len(), + LEVELS + ); + } + let mut result: Vec>> = vec![]; + for (idx, level) in tmp.iter().enumerate() { + let mut sum = 0; + let mut map: HashMap> = HashMap::new(); + for room in level { + map.insert(room.0, RangeInclusive::new(sum + 1, sum + room.1)); + sum += room.1; + } + if sum != 100 { + panic!( + "all percentages must add to 100 (was {}) per level, error in level {}!", + sum, + idx + 1 + ); } result.push(map); } diff --git a/src/level_generator.rs b/src/level_generator.rs index 0df65ed..15715e9 100644 --- a/src/level_generator.rs +++ b/src/level_generator.rs @@ -1,393 +1,256 @@ -use std::cmp::{max, min}; -use std::ops::Range; - use petgraph::algo::min_spanning_tree; use petgraph::data::*; use petgraph::graph::Graph; use petgraph::graph::UnGraph; -use rand::prelude::SliceRandom; -use rand::rngs::ThreadRng; +use std::cmp::max; +use std::cmp::min; +use std::ops::Range; + use rand::Rng; +use rand::{rngs::ThreadRng, seq::SliceRandom}; -use crate::artifacts::{Artifact, Chest, Potion}; -use crate::constants::{ - get_monsters_per_level, ROOMS_HORIZONTAL, ROOMS_VERTICAL, ROOM_HEIGHT, ROOM_WIDTH, +use crate::constants::ROOM_HEIGHT; +use crate::constants::ROOM_WIDTH; +use crate::room::Connection; +use crate::{ + constants::{ + get_room_type_per_level, LEVEL_HEIGHT, LEVEL_WIDTH, ROOMS_HORIZONAL, ROOMS_VERTICAL, + }, + level::{Level, StructureElement}, + room::{Room, RoomType}, }; -use crate::level::{Level, StructureElement}; -use crate::monster::{create_monster_by_type, Monster, Orc, Rat, Snake}; -use crate::position::Position; - -#[derive(PartialEq, Copy, Clone)] -enum RoomType { - Start, - End, - StairUp, - StairDown, - BasicRoom, - ArtifactRoom, - MonsterRoom, - EmptyRoom, -} - -#[derive(Copy, Clone)] -struct ConnectionInfo { - offset: usize, - distance: usize, -} - -#[derive(Copy, Clone)] -struct Room { - pub kind: RoomType, - pub offset_x: usize, - pub offset_y: usize, - pub width: usize, - pub height: usize, - pub connection_down: Option, - pub connection_right: Option, -} - -impl Room { - fn new() -> Self { - Self { - kind: RoomType::EmptyRoom, - offset_x: 0, - offset_y: 0, - width: 0, - height: 0, - connection_down: None, - connection_right: None, - } - } - /// change the size and position of a room randomly within its bounds - fn random(&mut self, rng: &mut ThreadRng) { - let width = rng.gen_range(3..6); - let height = rng.gen_range(3..5); - self.width = width; - self.height = height; - self.offset_x = rng.gen_range(0..(ROOM_WIDTH - width)); - self.offset_y = rng.gen_range(0..(ROOM_HEIGHT - height)); - } - fn get_x_range(&self) -> Range { - self.offset_x..self.offset_x + self.width - } - fn get_y_range(&self) -> Range { - self.offset_y..self.offset_y + self.height - } -} pub struct LevelGenerator { - rooms: [[Room; ROOMS_HORIZONTAL]; ROOMS_VERTICAL], level: usize, + rooms: [[Room; ROOMS_VERTICAL]; ROOMS_HORIZONAL], +} +enum Direction { + Horizontal, + Vertical, } impl LevelGenerator { pub fn generate(level: usize, first: bool, last: bool) -> Self { let mut rng = rand::thread_rng(); - let mut rooms = [[Room::new(); ROOMS_HORIZONTAL]; ROOMS_VERTICAL]; - let mut graph = UnGraph::<(usize, usize), u16>::default(); + let mut rooms = [[Room::new(&mut rng); ROOMS_VERTICAL]; ROOMS_HORIZONAL]; - // trick the room_connectable function into failing on the first iteration - rooms[0][0].kind = RoomType::BasicRoom; + /* + Fill grid with unconnected rooms + */ - while !LevelGenerator::rooms_connectable(&rooms) { - let mut room_types: Vec = - Vec::with_capacity(ROOMS_HORIZONTAL * ROOMS_VERTICAL); - // level 0 contains a start room, all others contain a stair up - if first { - room_types.push(RoomType::Start); - } else { - room_types.push(RoomType::StairUp); - } - // level 24 (the last) contains an end room, all others a stair down - if last { - room_types.push(RoomType::End); - } else { - room_types.push(RoomType::StairDown); - } - room_types.push(RoomType::MonsterRoom); - room_types.push(RoomType::ArtifactRoom); - // generate a random set of rooms and shuffle them - for _ in room_types.len()..ROOMS_HORIZONTAL * ROOMS_VERTICAL { - match rng.gen_range(1..=100) { - // TODO tune room type distribution - 1..=33 => room_types.push(RoomType::EmptyRoom), - 34..=66 => room_types.push(RoomType::ArtifactRoom), - 67..=90 => room_types.push(RoomType::MonsterRoom), - _ => room_types.push(RoomType::BasicRoom), - } - } - room_types.shuffle(&mut rng); + let mut rooms_to_place: Vec = Vec::with_capacity(ROOMS_VERTICAL * ROOMS_HORIZONAL); - graph.clear(); - // place the rooms in the array an add nodes to the graph for every non empty room - for (c, rs) in rooms.iter_mut().enumerate().take(ROOMS_VERTICAL) { - for (r, room) in rs.iter_mut().enumerate().take(ROOMS_HORIZONTAL) { - room.kind = room_types.pop().unwrap(); - if room.kind != RoomType::EmptyRoom { - room.random(&mut rng); - graph.add_node((c, r)); + let mut start_room = Room::new(&mut rng); + if first { + start_room.kind = RoomType::Start; + } else { + start_room.kind = RoomType::StairUp; + } + rooms_to_place.push(start_room); + let mut end_room = Room::new(&mut rng); + if last { + end_room.kind = RoomType::End; + } else { + end_room.kind = RoomType::StairDown; + } + rooms_to_place.push(end_room); + for _ in 2..ROOMS_HORIZONAL * ROOMS_VERTICAL { + let mut room = Room::new(&mut rng); + room.kind = LevelGenerator::select_room_type(level, &mut rng); + if room.kind != RoomType::EmptyRoom { + rooms_to_place.push(room); + } + } + let mut room_row = rng.gen_range(1..ROOMS_VERTICAL); + let mut room_col = rng.gen_range(1..ROOMS_HORIZONAL); + rooms[room_col][room_row] = rooms_to_place.pop().unwrap(); + while let Some(room) = rooms_to_place.pop() { + let mut directions_to_try = vec![Direction::Horizontal, Direction::Vertical]; + directions_to_try.shuffle(&mut rng); + while !directions_to_try.is_empty() { + match directions_to_try.pop().unwrap() { + Direction::Horizontal => { + let mut free_cols: Vec = vec![]; + for col in 0..ROOMS_HORIZONAL { + if rooms[col][room_row].kind == RoomType::EmptyRoom { + free_cols.push(col); + } + } + if free_cols.is_empty() { + continue; + } + free_cols.shuffle(&mut rng); + room_col = *free_cols.first().unwrap(); + rooms[room_col][room_row] = room; + break; + } + Direction::Vertical => { + let mut free_rows: Vec = vec![]; + for row in 0..ROOMS_VERTICAL { + if rooms[room_col][row].kind == RoomType::EmptyRoom { + free_rows.push(row); + } + } + if free_rows.is_empty() { + continue; + } + free_rows.shuffle(&mut rng); + room_row = *free_rows.first().unwrap(); + rooms[room_col][room_row] = room; + break; } } } } - // add edges to the graph connecting each room to all of its neighbours (max 4 of them) - for c in 0..ROOMS_VERTICAL { - for r in 0..ROOMS_HORIZONTAL { - if rooms[c][r].kind == RoomType::EmptyRoom { + + println!(" 0 1 2 3 4 5 6 7"); + for r in 0..ROOMS_VERTICAL { + print!("{} ", r); + for c in 0..ROOMS_HORIZONAL { + match rooms[c][r].kind { + RoomType::Start => print!("S "), + RoomType::End => print!("E "), + RoomType::StairUp => print!("< "), + RoomType::StairDown => print!("> "), + RoomType::BasicRoom => print!("B "), + RoomType::ArtifactRoom => print!("A "), + RoomType::MonsterRoom => print!("M "), + RoomType::EmptyRoom => print!(" "), + }; + } + println!(); + } + + /* + Construct a graph from the unconnected rooms and make a minum spanning tree of it + */ + let mut graph = UnGraph::<(usize, usize), u16>::default(); + // collect nodes + for col in 0..ROOMS_HORIZONAL { + for row in 0..ROOMS_VERTICAL { + if rooms[col][row].kind != RoomType::EmptyRoom { + graph.add_node((col, row)); + } + } + } + // collect edges between nodes, each right and down till we find the next room + for col in 0..ROOMS_HORIZONAL { + for row in 0..ROOMS_VERTICAL { + if rooms[col][row].kind == RoomType::EmptyRoom { continue; } - let src_index = graph.node_indices().find(|i| graph[*i] == (c, r)).unwrap(); - for r_1 in r + 1..ROOMS_HORIZONTAL { - if rooms[c][r_1].kind != RoomType::EmptyRoom { - let tgt_index = graph - .node_indices() - .find(|i| graph[*i] == (c, r_1)) - .unwrap(); - // todo use random weight for edge - graph.add_edge(src_index, tgt_index, 1); - break; + if let Some(src_index) = graph.node_indices().find(|i| graph[*i] == (col, row)) { + for col_1 in col + 1..ROOMS_HORIZONAL { + if rooms[col_1][row].kind != RoomType::EmptyRoom { + if let Some(tgt_index) = + graph.node_indices().find(|i| graph[*i] == (col_1, row)) + { + graph.add_edge(src_index, tgt_index, 1); + break; + } + } } - } - for (c_1, rs) in rooms.iter().enumerate().take(ROOMS_VERTICAL).skip(c + 1) { - if rs[r].kind != RoomType::EmptyRoom { - let tgt_index = graph - .node_indices() - .find(|i| graph[*i] == (c_1, r)) - .unwrap(); - // todo use random weight for edge - graph.add_edge(src_index, tgt_index, 1); - break; + for row_1 in row + 1..ROOMS_VERTICAL { + if rooms[col][row_1].kind != RoomType::EmptyRoom { + if let Some(tgt_index) = + graph.node_indices().find(|i| graph[*i] == (col, row_1)) + { + graph.add_edge(src_index, tgt_index, 1); + break; + } + } } } } } - - // calculate a minimum spanning tree let mst: Graph<(usize, usize), u16, petgraph::Undirected> = Graph::from_elements(min_spanning_tree(&graph)); for edge in mst.raw_edges() { - let src = mst[edge.source()]; - let tgt = mst[edge.target()]; + // the tuples are (col, row) + let (src_node_col, src_node_row) = mst[edge.source()]; + let (tgt_node_col, tgt_node_row) = mst[edge.target()]; + // println!("MST Edge from {:?} to {:?}", (src_node_col, src_node_row), (tgt_node_col, tgt_node_row)); - let src_room = rooms[src.0][src.1]; - let mut tgt_room = rooms[tgt.0][tgt.1]; - - // cols are the same, either up or down - if src.0 == tgt.0 { - let range = - LevelGenerator::range_overlap(src_room.get_x_range(), tgt_room.get_x_range()); - let position: usize = if range.is_empty() { - range.start - } else { - rng.gen_range(range) - }; - if src.1 < tgt.1 { - // src to tgt - rooms[src.0][src.1].connection_down = Some(ConnectionInfo { - offset: position, - distance: tgt.1 - src.1, - }); - } else { - // tgt to src - tgt_room.connection_down = Some(ConnectionInfo { - offset: position, - distance: src.1 - tgt.1, - }); - } - } - // rows are the same, either left or right - if src.1 == tgt.1 { - let range = - LevelGenerator::range_overlap(src_room.get_y_range(), tgt_room.get_y_range()); - let mut position: usize; - if range.is_empty() { - position = range.start; - } else { - position = rng.gen_range(range); - } - if src.1 == 0 && position == 0 { - position = 1; - } - if src.0 < tgt.0 { - // src to tgt - rooms[src.0][src.1].connection_right = Some(ConnectionInfo { - offset: position, - distance: tgt.0 - src.0, - }); - } else { - // tgt to src - tgt_room.connection_right = Some(ConnectionInfo { - offset: position, - distance: src.1 - tgt.1, - }); - } + if src_node_col == tgt_node_col { + // println!("Down"); + let start_col = src_node_col * ROOM_WIDTH+ROOM_WIDTH/2; + let start_row = src_node_row * ROOM_HEIGHT+ROOM_HEIGHT; + let end_col = tgt_node_col * ROOM_WIDTH+ROOM_WIDTH/2; + let end_row = tgt_node_row * ROOM_HEIGHT; + rooms[src_node_col][src_node_row].connection_down = Some(Connection { + start_pos: (start_col, start_row), + end_pos: (end_col, end_row), + }); + } else { + // println!("Right"); + let start_col = src_node_col * ROOM_WIDTH+ROOM_WIDTH; + let start_row = src_node_row * ROOM_HEIGHT+ROOM_HEIGHT/2; + let end_col = tgt_node_col * ROOM_WIDTH; + let end_row = tgt_node_row * ROOM_HEIGHT+ROOM_HEIGHT/2; + rooms[src_node_col][src_node_row].connection_right = Some(Connection { + start_pos: (start_col, start_row), + end_pos: (end_col, end_row), + }); } } - LevelGenerator { rooms, level } + LevelGenerator { level, rooms } } - fn range_overlap(r1: Range, r2: Range) -> Range { - max(r1.start, r2.start)..min(r1.end, r2.end) - } - - /// Verifies that for a given matrix of rooms each room has at least one other room in the - /// same row or column. - fn rooms_connectable(rooms: &[[Room; ROOMS_HORIZONTAL]; ROOMS_VERTICAL]) -> bool { - for c in 0..ROOMS_VERTICAL { - for r in 0..ROOMS_HORIZONTAL { - if rooms[c][r].kind != RoomType::EmptyRoom { - let mut connected = 0; - for room in rooms.iter().take(ROOMS_VERTICAL) { - if room[r].kind != RoomType::EmptyRoom { - connected += 1; - } - } - for r1 in 0..ROOMS_HORIZONTAL { - if rooms[c][r1].kind != RoomType::EmptyRoom { - connected += 1; - } - } - if connected <= 2 { - return false; - } - } - } - } - true - } - - fn select_monster(position: Position, rng: &mut ThreadRng) -> Box { - let level = position.get_level(); + fn select_room_type(level: usize, rng: &mut ThreadRng) -> RoomType { let value = rng.gen_range(1..=100); - let t = get_monsters_per_level(); + let t = get_room_type_per_level(); if level < t.len() { for (mtype, range) in &t[level] { if range.contains(&value) { - return create_monster_by_type(mtype, position); + return *mtype; } } } - match rng.gen_range(1..=100) { - 1..=30 => Box::new(Orc::new_with_position(position)), - 31..=60 => Box::new(Snake::new_with_position(position)), - _ => Box::new(Rat::new_with_position(position)), - } + panic!("no room selectable!"); } - pub fn render(&self) -> Level { - let mut rng = rand::thread_rng(); - let mut structure = [[StructureElement::Wall; 1 + ROOMS_HORIZONTAL * ROOM_HEIGHT]; - 1 + ROOMS_VERTICAL * ROOM_WIDTH]; - let mut artifacts: Vec> = Vec::with_capacity(10); - let mut enemies: Vec> = Vec::with_capacity(10); - let mut start_x: usize = 0; - let mut start_y: usize = 0; - let mut end_x: usize = 0; - let mut end_y: usize = 0; - for c in 0..ROOMS_VERTICAL { - for r in 0..ROOMS_HORIZONTAL { - let top = 1 + r * ROOM_HEIGHT; - let left = 1 + c * ROOM_WIDTH; - let room = self.rooms[c][r]; - for x in 0..room.width { - for y in 0..room.height { - structure[left + room.offset_x + x][top + room.offset_y + y] = - StructureElement::Floor; - } + pub fn render(&mut self) -> Level { + let mut structure = [[StructureElement::Wall; LEVEL_HEIGHT]; LEVEL_WIDTH]; + let mut start_pos = (0, 0); + let mut end_pos = (0, 0); + for col in 0..ROOMS_HORIZONAL { + for row in 0..ROOMS_VERTICAL { + let position = self.rooms[col][row].render(&mut structure, col, row); + if self.rooms[col][row].kind == RoomType::Start + || self.rooms[col][row].kind == RoomType::StairUp + { + start_pos = position; } - if room.kind == RoomType::ArtifactRoom { - let t_x = left + room.offset_x + rng.gen_range(0..room.width); - let t_y = top + room.offset_y + rng.gen_range(0..room.height); - // TODO randomize artifacts - match rng.gen_range(1..=100) { - 1..=50 => { - artifacts - .push(Box::new(Chest::new(Position::new(self.level, t_x, t_y)))); - } - _ => { - artifacts - .push(Box::new(Potion::new(Position::new(self.level, t_x, t_y)))); - } - }; - } - if room.kind == RoomType::MonsterRoom { - let t_x = left + room.offset_x + rng.gen_range(0..room.width); - let t_y = top + room.offset_y + rng.gen_range(0..room.height); - // TODO randomize enemies here - enemies.push(LevelGenerator::select_monster( - Position::new(self.level, t_x, t_y), - &mut rng, - )); - } - - if room.kind == RoomType::End || room.kind == RoomType::StairDown { - end_x = left + room.offset_x + rng.gen_range(0..room.width); - end_y = top + room.offset_y + rng.gen_range(0..room.height); - } - if room.kind == RoomType::StairDown { - structure[end_x][end_y] = StructureElement::StairDown; - } - if room.kind == RoomType::End { - structure[end_x][end_y] = StructureElement::End; - } - - if room.kind == RoomType::Start || room.kind == RoomType::StairUp { - start_x = left + room.offset_x + rng.gen_range(0..room.width); - start_y = top + room.offset_y + rng.gen_range(0..room.height); - } - if room.kind == RoomType::StairUp { - structure[start_x][start_y] = StructureElement::StairUp; - } - if room.kind == RoomType::Start { - structure[start_x][start_y] = StructureElement::Start; + if self.rooms[col][row].kind == RoomType::End + || self.rooms[col][row].kind == RoomType::StairDown + { + end_pos = position; } } } - // - for c in 0..ROOMS_VERTICAL { - for r in 0..ROOMS_HORIZONTAL { - let src_room = self.rooms[c][r]; - if let Some(x_conn) = src_room.connection_down { - let tgt_room = self.rooms[c][r + x_conn.distance]; - let top = 1 + r * ROOM_HEIGHT + src_room.offset_y; - let left = 1 + c * ROOM_WIDTH + x_conn.offset; - let bottom = 1 - + (r + x_conn.distance) * ROOM_HEIGHT - + tgt_room.offset_y - + tgt_room.height; - for i in top..bottom { - if structure[left][i] == StructureElement::Wall { - structure[left][i] = StructureElement::Floor; - } - } + for col in 0..ROOMS_HORIZONAL { + for row in 0..ROOMS_VERTICAL { + if let Some(connection) = self.rooms[col][row].connection_down { + // println!("down"); + connection.render(&mut structure); } - if let Some(y_conn) = src_room.connection_right { - let tgt_room = self.rooms[c + y_conn.distance][r]; - - let top = 1 + r * ROOM_HEIGHT + src_room.offset_y + y_conn.offset - 1; - let left = 1 + c * ROOM_WIDTH + src_room.offset_x; - let right = - 1 + (c + y_conn.distance) * ROOM_WIDTH + tgt_room.offset_x + tgt_room.width; - for room in structure.iter_mut().take(right).skip(left) { - if room[top] == StructureElement::Wall { - room[top] = StructureElement::Floor; - } - } + if let Some(connection) = self.rooms[col][row].connection_right { + // println!("right"); + connection.render(&mut structure); } } } Level { level: self.level, structure, - discovered: [[false; 1 + ROOMS_HORIZONTAL * ROOM_HEIGHT]; - 1 + ROOMS_VERTICAL * ROOM_WIDTH], - monsters: enemies, - artifacts, - start: (start_x, start_y), - end: (end_x, end_y), + discovered: [[false; LEVEL_HEIGHT]; LEVEL_WIDTH], + monsters: vec![], + artifacts: vec![], + start: start_pos, + end: end_pos, rng: rand::thread_rng(), } } @@ -395,11 +258,15 @@ impl LevelGenerator { #[test] fn test_level_gen() { - let level = LevelGenerator::generate(23, false, false).render(); - assert_eq!(level.level, 23); - assert_ne!(level.start, (0, 0)); - assert_ne!(level.end, (0, 0)); - assert_ne!(level.start, level.end); - assert_ne!(level.monsters.len(), 0); - assert_ne!(level.artifacts.len(), 0); + for _ in 0..1000 { + LevelGenerator::generate(0, true, false).render(); + } +} + +#[test] +fn test_level_gen_respects_level() { + let level = LevelGenerator::generate(0, true, false).render(); + assert_eq!(0, level.level); + let level = LevelGenerator::generate(1, true, false).render(); + assert_eq!(1, level.level); } diff --git a/src/main.rs b/src/main.rs index e1e64eb..213db9e 100644 --- a/src/main.rs +++ b/src/main.rs @@ -36,6 +36,7 @@ mod level_widget; mod monster; mod player; mod position; +mod room; // fn main() -> Result<()> { diff --git a/src/room.rs b/src/room.rs new file mode 100644 index 0000000..231248e --- /dev/null +++ b/src/room.rs @@ -0,0 +1,229 @@ +use std::{ + cmp::{max, min}, + ops::Range, +}; + +use crate::{ + constants::{LEVEL_HEIGHT, LEVEL_WIDTH, ROOM_HEIGHT, ROOM_WIDTH}, + level::StructureElement, +}; +use rand::rngs::ThreadRng; +use rand::Rng; + +#[derive(PartialEq, Copy, Clone, Eq, Hash, Debug)] +pub enum RoomType { + Start, + End, + StairUp, + StairDown, + BasicRoom, + ArtifactRoom, + MonsterRoom, + EmptyRoom, +} + +#[derive(Copy, Clone, Debug)] +pub struct Connection { + pub start_pos: (usize, usize), + pub end_pos: (usize, usize), +} + +impl Connection { + pub fn render(&self, tgt: &mut [[StructureElement; LEVEL_HEIGHT]; LEVEL_WIDTH]) { + // the tuples are (col, row) + println!("{:?} -> {:?}",self.start_pos, self.end_pos); + let d_col = max(self.start_pos.0, self.end_pos.0) - min(self.start_pos.0, self.end_pos.0); + + let d_row = max(self.start_pos.1, self.end_pos.1) - min(self.start_pos.1, self.end_pos.1); + + if d_col == 0 { + for row in self.start_pos.1..=self.end_pos.1 { + tgt[self.end_pos.0][row] = StructureElement::Floor; + } + } + if d_row == 0 { + for col in self.start_pos.0..=self.end_pos.0 { + tgt[col][self.end_pos.1] = StructureElement::Floor; + } + + } + } +} + +#[derive(Copy, Clone, Debug)] +pub struct Room { + pub kind: RoomType, + pub offset_x: usize, + pub offset_y: usize, + pub width: usize, + pub height: usize, + pub special: (usize, usize), + pub connection_down: Option, + pub connection_right: Option, +} + +impl Room { + pub fn new(rng: &mut ThreadRng) -> Self { + let width = ROOM_WIDTH-1; //rng.gen_range(3..ROOM_WIDTH); + let height = ROOM_HEIGHT-1; //rng.gen_range(3..ROOM_HEIGHT); + let offset_x = 0; //rng.gen_range(0..(ROOM_WIDTH - width)); + let offset_y = 0; //rng.gen_range(0..(ROOM_HEIGHT - height)); + let sx = offset_x + rng.gen_range(1..width-1); + let sy = offset_y + rng.gen_range(1..height-1); + + Self { + kind: RoomType::EmptyRoom, + offset_x, + offset_y, + width, + height, + special: (sx, sy), + connection_down: None, + connection_right: None, + } + } + + pub fn get_x_range(&self) -> Range { + self.offset_x..self.offset_x + self.width + } + + pub fn get_y_range(&self) -> Range { + self.offset_y..self.offset_y + self.height + } + + pub fn render( + &self, + tgt: &mut [[StructureElement; LEVEL_HEIGHT]; LEVEL_WIDTH], + col: usize, + row: usize, + ) -> (usize, usize) { + let top = 1 + row * ROOM_HEIGHT; + let left = 1 + col * ROOM_WIDTH; + for col in tgt.iter_mut().skip(left).take(ROOM_WIDTH) { + //left..left + ROOM_WIDTH { + for row_element in col.iter_mut().skip(top).take(ROOM_HEIGHT) { + // top..top + ROOM_HEIGHT { + *row_element = StructureElement::Wall; + } + } + if self.kind == RoomType::EmptyRoom { + return (0, 0); + } + // render floor elements + for x in 0..self.width { + for y in 0..self.height { + tgt[left + self.offset_x + x][top + self.offset_y + y] = StructureElement::Floor; + } + } + + match self.kind { + RoomType::Start => { + tgt[left + self.special.0][top + self.special.1] = StructureElement::Start; + (left + self.special.0, top + self.special.1) + } + RoomType::End => { + tgt[left + self.special.0][top + self.special.1] = StructureElement::End; + (left + self.special.0, top + self.special.1) + } + RoomType::StairUp => { + tgt[left + self.special.0][top + self.special.1] = StructureElement::StairUp; + (left + self.special.0, top + self.special.1) + } + RoomType::StairDown => { + tgt[left + self.special.0][top + self.special.1] = StructureElement::StairDown; + (left + self.special.0, top + self.special.1) + } + RoomType::BasicRoom => (0, 0), + RoomType::ArtifactRoom => (0, 0), + RoomType::MonsterRoom => (0, 0), + RoomType::EmptyRoom => (0, 0), + } + } +} + +#[test] +fn test_room_creation() { + let mut rng = rand::thread_rng(); + let room = Room::new(&mut rng); + assert_eq!(room.kind, RoomType::EmptyRoom); + for _ in 0..1000 { + let room = Room::new(&mut rng); + assert!((3..=ROOM_WIDTH).contains(&room.width)); + } +} + +#[cfg(test)] +fn all_wall( + tgt: &mut [[StructureElement; LEVEL_HEIGHT]; LEVEL_WIDTH], + col: usize, + row: usize, +) -> bool { + let top = 1 + row * ROOM_HEIGHT; + let left = 1 + col * ROOM_WIDTH; + for x in left..left + ROOM_WIDTH { + for y in top..top + ROOM_HEIGHT { + if tgt[x][y] != StructureElement::Wall { + return false; + } + } + } + true +} + +#[cfg(test)] +fn has_structure_element( + tgt: &mut [[StructureElement; LEVEL_HEIGHT]; LEVEL_WIDTH], + col: usize, + row: usize, + element: StructureElement, +) -> bool { + let top = 1 + row * ROOM_HEIGHT; + let left = 1 + col * ROOM_WIDTH; + for x in left..left + ROOM_WIDTH { + for y in top..top + ROOM_HEIGHT { + if tgt[x][y] == element { + return true; + } + } + } + false +} + +#[test] +fn test_room_render_empty() { + let mut rng = rand::thread_rng(); + let room = Room::new(&mut rng); + assert_eq!(room.kind, RoomType::EmptyRoom); + let mut structure = [[StructureElement::Floor; LEVEL_HEIGHT]; LEVEL_WIDTH]; + + room.render(&mut structure, 0, 0); + assert!(all_wall(&mut structure, 0, 0)); +} + +#[test] +fn test_room_render_basic() { + let mut rng = rand::thread_rng(); + let mut room = Room::new(&mut rng); + room.kind = RoomType::BasicRoom; + let mut structure = [[StructureElement::Floor; LEVEL_HEIGHT]; LEVEL_WIDTH]; + + room.render(&mut structure, 1, 3); + assert!(!all_wall(&mut structure, 1, 3)); +} + +#[test] +fn test_room_render_start() { + let mut rng = rand::thread_rng(); + let mut room = Room::new(&mut rng); + room.kind = RoomType::Start; + let mut structure = [[StructureElement::Floor; LEVEL_HEIGHT]; LEVEL_WIDTH]; + + room.render(&mut structure, 1, 3); + assert!(!all_wall(&mut structure, 1, 3)); + assert!(has_structure_element( + &mut structure, + 1, + 3, + StructureElement::Start + )); +}