diff --git a/src/main.rs b/src/main.rs index a1435c9..829f514 100644 --- a/src/main.rs +++ b/src/main.rs @@ -14,6 +14,7 @@ mod rect; pub use rect::Rect; mod gamelog; mod gui; +pub mod map_builders; mod saveload_system; mod spawner; mod visibility_system; @@ -141,31 +142,31 @@ impl State { } // Build new map - let worldmap; + let mut builder; let current_depth; + let player_start; { let mut worldmap_resource = self.ecs.write_resource::(); current_depth = worldmap_resource.depth; let mut rng = self.ecs.write_resource::(); - *worldmap_resource = Map::new_map_rooms_and_corridors(&mut rng, current_depth + 1); - worldmap = worldmap_resource.clone(); + builder = map_builders::random_builder(current_depth + 1); + builder.build_map(&mut rng); + *worldmap_resource = builder.get_map(); + player_start = builder.get_starting_pos(); } // Spawn things in rooms - for room in worldmap.rooms.iter().skip(1) { - spawner::spawn_room(&mut self.ecs, room, current_depth + 1); - } + builder.spawn_entities(&mut self.ecs); // Place the player and update resources - let (player_x, player_y) = worldmap.rooms[0].centre(); let mut player_position = self.ecs.write_resource::(); - *player_position = Point::new(player_x, player_y); + *player_position = Point::new(player_start.x, player_start.y); let mut position_components = self.ecs.write_storage::(); let player_entity = self.ecs.fetch::(); let player_pos_comp = position_components.get_mut(*player_entity); if let Some(player_pos_comp) = player_pos_comp { - player_pos_comp.x = player_x; - player_pos_comp.y = player_y; + player_pos_comp.x = player_start.x; + player_pos_comp.y = player_start.y; } // Dirtify viewshed @@ -195,21 +196,22 @@ impl State { } // Build a new map and place the player - let worldmap; + let mut builder; + let player_start; { let mut worldmap_resource = self.ecs.write_resource::(); let mut rng = self.ecs.write_resource::(); - *worldmap_resource = Map::new_map_rooms_and_corridors(&mut rng, 1); - worldmap = worldmap_resource.clone(); + builder = map_builders::random_builder(1); + builder.build_map(&mut rng); + *worldmap_resource = builder.get_map(); + player_start = builder.get_starting_pos(); } // Spawn bad guys - for room in worldmap.rooms.iter().skip(1) { - spawner::spawn_room(&mut self.ecs, room, 1); - } + builder.spawn_entities(&mut self.ecs); // Place the player and update resources - let (player_x, player_y) = worldmap.rooms[0].centre(); + let (player_x, player_y) = (player_start.x, player_start.y); let player_entity = spawner::player(&mut self.ecs, player_x, player_y, "Player".to_string()); let mut player_position = self.ecs.write_resource::(); *player_position = Point::new(player_x, player_y); @@ -524,23 +526,23 @@ fn main() -> rltk::BError { gs.ecs.register::(); gs.ecs.insert(SimpleMarkerAllocator::::new()); - // Create RNG. + // Create seed. let mut rng = rltk::RandomNumberGenerator::new(); // Use seed to generate the map. - let map = Map::new_map_rooms_and_corridors(&mut rng, 1); + let mut builder = map_builders::random_builder(1); + builder.build_map(&mut rng); + let player_start = builder.get_starting_pos(); + let map = builder.get_map(); // Insert seed into the ECS. gs.ecs.insert(rng); - let (player_x, player_y) = map.rooms[0].centre(); let player_name = "wanderer".to_string(); - let player_entity = spawner::player(&mut gs.ecs, player_x, player_y, player_name); + let player_entity = spawner::player(&mut gs.ecs, player_start.x, player_start.y, player_name); - for room in map.rooms.iter().skip(1) { - spawner::spawn_room(&mut gs.ecs, room, 1); - } + builder.spawn_entities(&mut gs.ecs); gs.ecs.insert(map); - gs.ecs.insert(Point::new(player_x, player_y)); + gs.ecs.insert(Point::new(player_start.x, player_start.y)); gs.ecs.insert(player_entity); gamelog::setup_log(); diff --git a/src/map.rs b/src/map.rs index 038e768..31d528a 100644 --- a/src/map.rs +++ b/src/map.rs @@ -1,8 +1,6 @@ -use super::Rect; -use rltk::{Algorithm2D, BaseMap, Point, RandomNumberGenerator, Rltk, RGB}; +use rltk::{Algorithm2D, BaseMap, Point, Rltk, RGB}; use serde::{Deserialize, Serialize}; use specs::prelude::*; -use std::cmp::{max, min}; use std::collections::HashSet; use std::ops::{Add, Mul}; @@ -15,13 +13,11 @@ pub enum TileType { pub const MAPWIDTH: usize = 80; pub const MAPHEIGHT: usize = 43; -const MAX_OFFSET: u8 = 32; pub const MAPCOUNT: usize = MAPHEIGHT * MAPWIDTH; #[derive(Default, Serialize, Deserialize, Clone)] pub struct Map { pub tiles: Vec, - pub rooms: Vec, pub width: i32, pub height: i32, pub revealed_tiles: Vec, @@ -45,30 +41,22 @@ impl Map { (y as usize) * (self.width as usize) + (x as usize) } - fn apply_room_to_map(&mut self, room: &Rect) { - for y in room.y1 + 1..=room.y2 { - for x in room.x1 + 1..=room.x2 { - let idx = self.xy_idx(x, y); - self.tiles[idx] = TileType::Floor; - } - } - } - - fn apply_horizontal_tunnel(&mut self, x1: i32, x2: i32, y: i32) { - for x in min(x1, x2)..=max(x1, x2) { - let idx = self.xy_idx(x, y); - if idx > 0 && idx < (self.width as usize) * (self.height as usize) { - self.tiles[idx as usize] = TileType::Floor; - } - } - } - - fn apply_vertical_tunnel(&mut self, y1: i32, y2: i32, x: i32) { - for y in min(y1, y2)..=max(y1, y2) { - let idx = self.xy_idx(x, y); - if idx > 0 && idx < (self.width as usize) * (self.height as usize) { - self.tiles[idx as usize] = TileType::Floor; - } + pub fn new(new_depth: i32) -> Map { + Map { + tiles: vec![TileType::Wall; MAPCOUNT], + width: MAPWIDTH as i32, + height: MAPHEIGHT as i32, + revealed_tiles: vec![false; MAPCOUNT], + visible_tiles: vec![false; MAPCOUNT], + lit_tiles: vec![true; MAPCOUNT], // NYI: Light sources. Once those exist, we can set this to false. + telepath_tiles: vec![false; MAPCOUNT], + red_offset: vec![0; MAPCOUNT], + green_offset: vec![0; MAPCOUNT], + blue_offset: vec![0; MAPCOUNT], + blocked: vec![false; MAPCOUNT], + depth: new_depth, + bloodstains: HashSet::new(), + tile_content: vec![Vec::new(); MAPCOUNT], } } @@ -92,81 +80,6 @@ impl Map { content.clear(); } } - - /// Makes a procgen map out of rooms and corridors, and returns the rooms and the map. - pub fn new_map_rooms_and_corridors(rng: &mut RandomNumberGenerator, new_depth: i32) -> Map { - let mut map = Map { - tiles: vec![TileType::Wall; MAPCOUNT], - rooms: Vec::new(), - width: MAPWIDTH as i32, - height: MAPHEIGHT as i32, - revealed_tiles: vec![false; MAPCOUNT], - visible_tiles: vec![false; MAPCOUNT], - lit_tiles: vec![true; MAPCOUNT], // NYI: Light sources. Once those exist, we can set this to false. - telepath_tiles: vec![false; MAPCOUNT], - red_offset: vec![0; MAPCOUNT], - green_offset: vec![0; MAPCOUNT], - blue_offset: vec![0; MAPCOUNT], - blocked: vec![false; MAPCOUNT], - depth: new_depth, - bloodstains: HashSet::new(), - tile_content: vec![Vec::new(); MAPCOUNT], - }; - - const MAX_ROOMS: i32 = 30; - const MIN_SIZE: i32 = 6; - const MAX_SIZE: i32 = 10; - - for idx in 0..map.red_offset.len() { - let roll = rng.roll_dice(1, MAX_OFFSET as i32); - map.red_offset[idx] = roll as u8; - } - for idx in 0..map.green_offset.len() { - let roll = rng.roll_dice(1, MAX_OFFSET as i32); - map.green_offset[idx] = roll as u8; - } - for idx in 0..map.blue_offset.len() { - let roll = rng.roll_dice(1, MAX_OFFSET as i32); - map.blue_offset[idx] = roll as u8; - } - - for _i in 0..MAX_ROOMS { - let w = rng.range(MIN_SIZE, MAX_SIZE); - let h = rng.range(MIN_SIZE, MAX_SIZE); - let x = rng.roll_dice(1, map.width - w - 1) - 1; - let y = rng.roll_dice(1, map.height - h - 1) - 1; - let new_room = Rect::new(x, y, w, h); - let mut ok = true; - for other_room in map.rooms.iter() { - if new_room.intersect(other_room) { - ok = false; - } - } - if ok { - map.apply_room_to_map(&new_room); - - if !map.rooms.is_empty() { - let (new_x, new_y) = new_room.centre(); - let (prev_x, prev_y) = map.rooms[map.rooms.len() - 1].centre(); - if rng.range(0, 2) == 1 { - map.apply_horizontal_tunnel(prev_x, new_x, prev_y); - map.apply_vertical_tunnel(prev_y, new_y, new_x); - } else { - map.apply_vertical_tunnel(prev_y, new_y, prev_x); - map.apply_horizontal_tunnel(prev_x, new_x, new_y); - } - } - - map.rooms.push(new_room); - } - } - - let stairs_position = map.rooms[map.rooms.len() - 1].centre(); - let stairs_idx = map.xy_idx(stairs_position.0, stairs_position.1); - map.tiles[stairs_idx] = TileType::DownStair; - - map - } } impl Algorithm2D for Map { diff --git a/src/map_builders/common.rs b/src/map_builders/common.rs new file mode 100644 index 0000000..b3d314b --- /dev/null +++ b/src/map_builders/common.rs @@ -0,0 +1,29 @@ +use super::{Map, Rect, TileType}; +use std::cmp::{max, min}; + +pub fn apply_room_to_map(map: &mut Map, room: &Rect) { + for y in room.y1 + 1..=room.y2 { + for x in room.x1 + 1..=room.x2 { + let idx = map.xy_idx(x, y); + map.tiles[idx] = TileType::Floor; + } + } +} + +pub fn apply_horizontal_tunnel(map: &mut Map, x1: i32, x2: i32, y: i32) { + for x in min(x1, x2)..=max(x1, x2) { + let idx = map.xy_idx(x, y); + if idx > 0 && idx < (map.width as usize) * (map.height as usize) { + map.tiles[idx as usize] = TileType::Floor; + } + } +} + +pub fn apply_vertical_tunnel(map: &mut Map, y1: i32, y2: i32, x: i32) { + for y in min(y1, y2)..=max(y1, y2) { + let idx = map.xy_idx(x, y); + if idx > 0 && idx < (map.width as usize) * (map.height as usize) { + map.tiles[idx as usize] = TileType::Floor; + } + } +} diff --git a/src/map_builders/mod.rs b/src/map_builders/mod.rs new file mode 100644 index 0000000..03653f2 --- /dev/null +++ b/src/map_builders/mod.rs @@ -0,0 +1,18 @@ +use super::{spawner, Map, Position, Rect, TileType}; +mod simple_map; +use simple_map::SimpleMapBuilder; +mod common; +use common::*; +use rltk::RandomNumberGenerator; +use specs::prelude::*; + +pub trait MapBuilder { + fn build_map(&mut self, rng: &mut RandomNumberGenerator); + fn spawn_entities(&mut self, ecs: &mut World); + fn get_map(&mut self) -> Map; + fn get_starting_pos(&mut self) -> Position; +} + +pub fn random_builder(new_depth: i32) -> Box { + return Box::new(SimpleMapBuilder::new(new_depth)); +} diff --git a/src/map_builders/simple_map.rs b/src/map_builders/simple_map.rs new file mode 100644 index 0000000..2151e52 --- /dev/null +++ b/src/map_builders/simple_map.rs @@ -0,0 +1,102 @@ +use super::{ + apply_horizontal_tunnel, apply_room_to_map, apply_vertical_tunnel, spawner, Map, MapBuilder, Position, Rect, + TileType, +}; +use rltk::RandomNumberGenerator; +use specs::prelude::*; + +pub struct SimpleMapBuilder { + map: Map, + starting_position: Position, + depth: i32, + rooms: Vec, +} + +impl MapBuilder for SimpleMapBuilder { + fn get_map(&mut self) -> Map { + return self.map.clone(); + } + + fn get_starting_pos(&mut self) -> Position { + return self.starting_position.clone(); + } + + fn build_map(&mut self, rng: &mut RandomNumberGenerator) { + return self.rooms_and_corridors(rng); + } + + fn spawn_entities(&mut self, ecs: &mut World) { + for room in self.rooms.iter().skip(1) { + return spawner::spawn_room(ecs, room, self.depth); + } + } +} + +impl SimpleMapBuilder { + pub fn new(new_depth: i32) -> SimpleMapBuilder { + SimpleMapBuilder { + map: Map::new(new_depth), + starting_position: Position { x: 0, y: 0 }, + depth: new_depth, + rooms: Vec::new(), + } + } + + fn rooms_and_corridors(&mut self, rng: &mut RandomNumberGenerator) { + const MAX_ROOMS: i32 = 30; + const MIN_SIZE: i32 = 6; + const MAX_SIZE: i32 = 10; + const MAX_OFFSET: u8 = 32; + + for idx in 0..self.map.red_offset.len() { + let roll = rng.roll_dice(1, MAX_OFFSET as i32); + self.map.red_offset[idx] = roll as u8; + } + for idx in 0..self.map.green_offset.len() { + let roll = rng.roll_dice(1, MAX_OFFSET as i32); + self.map.green_offset[idx] = roll as u8; + } + for idx in 0..self.map.blue_offset.len() { + let roll = rng.roll_dice(1, MAX_OFFSET as i32); + self.map.blue_offset[idx] = roll as u8; + } + + for _i in 0..MAX_ROOMS { + let w = rng.range(MIN_SIZE, MAX_SIZE); + let h = rng.range(MIN_SIZE, MAX_SIZE); + let x = rng.roll_dice(1, self.map.width - w - 1) - 1; + let y = rng.roll_dice(1, self.map.height - h - 1) - 1; + let new_room = Rect::new(x, y, w, h); + let mut ok = true; + for other_room in self.rooms.iter() { + if new_room.intersect(other_room) { + ok = false + } + } + if ok { + apply_room_to_map(&mut self.map, &new_room); + + if !self.rooms.is_empty() { + let (new_x, new_y) = new_room.centre(); + let (prev_x, prev_y) = self.rooms[self.rooms.len() - 1].centre(); + if rng.range(0, 2) == 1 { + apply_horizontal_tunnel(&mut self.map, prev_x, new_x, prev_y); + apply_vertical_tunnel(&mut self.map, prev_y, new_y, new_x); + } else { + apply_vertical_tunnel(&mut self.map, prev_y, new_y, prev_x); + apply_horizontal_tunnel(&mut self.map, prev_x, new_x, new_y); + } + } + + self.rooms.push(new_room); + } + } + + let stairs_position = self.rooms[self.rooms.len() - 1].centre(); + let stairs_idx = self.map.xy_idx(stairs_position.0, stairs_position.1); + self.map.tiles[stairs_idx] = TileType::DownStair; + + let start_pos = self.rooms[0].centre(); + self.starting_position = Position { x: start_pos.0, y: start_pos.1 }; + } +}