From a75f565cbb34b1fc9f9b92b3a75c0b5ad817813e Mon Sep 17 00:00:00 2001 From: klangner Date: Wed, 16 Sep 2020 11:42:59 +0200 Subject: [PATCH] drunkard implemented --- README.md | 2 +- demo/src/lib.rs | 21 +++- demo/www/index.html | 1 + demo/www/index.js | 7 ++ src/geometry.rs | 12 +++ src/map.rs | 64 +++++++++++- src/map_builder/drunkard.rs | 198 +++++++++++++++--------------------- src/map_builder/mod.rs | 2 +- 8 files changed, 185 insertions(+), 122 deletions(-) diff --git a/README.md b/README.md index 0320b6e..5609245 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ Generate procedural maps for games. [Try it in the browser](https://klangner.git * [x] BSP Rooms * [x] Cellular automata * [ ] Diffusion-Limited Aggregation (DLA) - * [ ] Drunkard's walk + * [x] Drunkard's walk * [ ] Maze * [ ] Prefabs * [x] Simple rooms diff --git a/demo/src/lib.rs b/demo/src/lib.rs index d48f1cd..774c3b3 100644 --- a/demo/src/lib.rs +++ b/demo/src/lib.rs @@ -11,6 +11,7 @@ use mapgen::{ cull_unreachable::CullUnreachable, distant_exit::DistantExit, rooms_corridors_nearest::NearestCorridors, + drunkard::DrunkardsWalkGen, }, map::TileType, }; @@ -80,13 +81,29 @@ impl World { tiles } } + pub fn new_drunkard(width: u32, height: u32, seed: u32) -> World { + World::print_map_info(format!("Drunkard with the seed: {}", seed)); + let mut rng = StdRng::seed_from_u64(seed as u64); + let map = MapBuilder::new(DrunkardsWalkGen::open_halls()) + .build_map_with_rng(width as usize, height as usize, &mut rng); + let tiles = (0..map.tiles.len()) + .map(|i| if map.tiles[i] == TileType::Floor {Cell::Floor} else {Cell::Wall}) + .collect(); + World { + width, + height, + tiles } + } + pub fn new_random(width: u32, height: u32, seed: u32) -> World { let mut rng = rand::thread_rng(); let px = rng.gen::(); - if px < 0.3333 { + if px < 0.25 { World::new_cellular_automata(width, height, seed) - } else if px < 0.6666 { + } else if px < 0.5 { World::new_simple_rooms(width, height, seed) + } else if px < 0.75 { + World::new_drunkard(width, height, seed) } else { World::new_bsp_interior(width, height, seed) } diff --git a/demo/www/index.html b/demo/www/index.html index 2426dee..017e877 100644 --- a/demo/www/index.html +++ b/demo/www/index.html @@ -38,6 +38,7 @@ Cellular Automata Simple Rooms BSP Interior + Drunkard Walk Random Generator diff --git a/demo/www/index.js b/demo/www/index.js index c350350..61fe903 100644 --- a/demo/www/index.js +++ b/demo/www/index.js @@ -38,6 +38,12 @@ function newBspInterior() { requestAnimationFrame(renderLoop); } +function newDrunkard() { + var seed = Date.now(); + world = World.new_drunkard(width, height, seed); + requestAnimationFrame(renderLoop); +} + function newRandomGen() { var seed = Date.now(); world = World.new_random(width, height, seed); @@ -108,4 +114,5 @@ newRandomGen(); document.getElementById('cellular-automata-option').addEventListener('click', newCellularAutomata); document.getElementById('simple-rooms-option').addEventListener('click', newSimpleRooms); document.getElementById('bsp-interior-option').addEventListener('click', newBspInterior); +document.getElementById('drunkard-option').addEventListener('click', newDrunkard); document.getElementById('random-option').addEventListener('click', newRandomGen); diff --git a/src/geometry.rs b/src/geometry.rs index 86295b2..2beb41c 100644 --- a/src/geometry.rs +++ b/src/geometry.rs @@ -71,6 +71,18 @@ impl Rect { } } +/// Calculate abs value between 2 usize values +/// Example: +/// ``` +/// use mapgen::geometry::usize_abs; +/// +/// assert_eq!(usize_abs(5, 3), 2); +/// assert_eq!(usize_abs(3, 5), 2); +/// ``` +pub fn usize_abs(x: usize, y: usize) -> usize { + if x >= y {x - y} else {y - x} +} + /// ------------------------------------------------------------------------------------------------ /// Module unit tests /// ------------------------------------------------------------------------------------------------ diff --git a/src/map.rs b/src/map.rs index bd57b29..149dad3 100644 --- a/src/map.rs +++ b/src/map.rs @@ -8,7 +8,7 @@ //! use std::fmt; -use super::geometry::{Point, Rect}; +use super::geometry::{Point, Rect, usize_abs}; #[derive(PartialEq, Copy, Clone, Debug, Eq, Hash)] @@ -16,6 +16,10 @@ pub enum TileType { Wall, Floor } +#[derive(PartialEq, Copy, Clone)] +pub enum Symmetry { None, Horizontal, Vertical, Both } + + /// Map data #[derive(Default, Clone)] pub struct Map { @@ -140,6 +144,64 @@ impl Map { } } } + + pub fn paint(&mut self, mode: Symmetry, brush_size: usize, x: usize, y: usize) { + match mode { + Symmetry::None => self.apply_paint(brush_size, x, y), + Symmetry::Horizontal => { + let center_x = self.width / 2; + if x == center_x { + self.apply_paint(brush_size, x, y); + } else { + let dist_x = usize_abs(center_x, x); + self.apply_paint(brush_size, center_x + dist_x, y); + self.apply_paint(brush_size, center_x - dist_x, y); + } + } + Symmetry::Vertical => { + let center_y = self.height / 2; + if y == center_y { + self.apply_paint(brush_size, x, y); + } else { + let dist_y = usize_abs(center_y, y); + self.apply_paint(brush_size, x, center_y + dist_y); + self.apply_paint(brush_size, x, center_y - dist_y); + } + } + Symmetry::Both => { + let center_x = self.width / 2; + let center_y = self.height / 2; + if x == center_x && y == center_y { + self.apply_paint(brush_size, x, y); + } else { + let dist_x = usize_abs(center_x, x); + self.apply_paint(brush_size, center_x + dist_x, y); + self.apply_paint(brush_size, center_x - dist_x, y); + let dist_y = usize_abs(center_y, y); + self.apply_paint(brush_size, x, center_y + dist_y); + self.apply_paint(brush_size, x, center_y - dist_y); + } + } + } + } + + fn apply_paint(&mut self, brush_size: usize, x: usize, y: usize) { + match brush_size { + 1 => { + self.set_tile(x, y, TileType::Floor); + } + _ => { + let half_brush_size = brush_size / 2; + for brush_y in y-half_brush_size .. y+half_brush_size { + for brush_x in x-half_brush_size .. x+half_brush_size { + if brush_x > 1 && brush_x < self.width-1 && brush_y > 1 && brush_y < self.height-1 { + self.set_tile(brush_x, brush_y, TileType::Floor); + } + } + } + } + } + } } impl fmt::Display for Map { diff --git a/src/map_builder/drunkard.rs b/src/map_builder/drunkard.rs index 06c19f1..7215a78 100644 --- a/src/map_builder/drunkard.rs +++ b/src/map_builder/drunkard.rs @@ -1,121 +1,96 @@ -use rltk::RandomNumberGenerator; -use crate::map::TileType; -use crate::components::Position; -use super::{InitialMapBuilder, MetaMapBuilder, BuilderMap}; -use super::common::*; +//! Example generator usage: +//! ``` +//! use rand::prelude::*; +//! use mapgen::map_builder::{ +//! MapGenerator, +//! drunkard::DrunkardsWalkGen +//! }; +//! +//! let mut rng = StdRng::seed_from_u64(100); +//! let gen = DrunkardsWalkGen::open_area(); +//! let map = gen.generate_map(80, 50, &mut rng); +//! +//! assert_eq!(map.width, 80); +//! assert_eq!(map.height, 50); +//! ``` +//! + +use rand::prelude::*; +use super::MapGenerator; +use crate::{ + map::{Map, Symmetry, TileType}, + geometry::Point, + random::Rng +}; #[derive(PartialEq, Copy, Clone)] -#[allow(dead_code)] pub enum DrunkSpawnMode { StartingPoint, Random } -pub struct DrunkardSettings { - pub spawn_mode : DrunkSpawnMode, - pub drunken_lifetime : i32, - pub floor_percent: f32, - pub brush_size: i32, - pub symmetry: Symmetry +pub struct DrunkardsWalkGen { + spawn_mode : DrunkSpawnMode, + drunken_lifetime : i32, + floor_percent: f32, + brush_size: usize, + symmetry: Symmetry } -pub struct DrunkardsWalkBuilder { - settings : DrunkardSettings -} - -impl InitialMapBuilder for DrunkardsWalkBuilder { - #[allow(dead_code)] - fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data : &mut BuilderMap) { - self.build(rng, build_data); +impl MapGenerator for DrunkardsWalkGen { + fn generate_map(&self, width: usize, height: usize, rng: &mut StdRng) -> Map { + self.build(rng, width, height) } } -impl DrunkardsWalkBuilder { - #[allow(dead_code)] - pub fn new(settings: DrunkardSettings) -> DrunkardsWalkBuilder { - DrunkardsWalkBuilder{ - settings - } - } - - #[allow(dead_code)] - pub fn open_area() -> Box { - Box::new(DrunkardsWalkBuilder{ - settings : DrunkardSettings{ - spawn_mode: DrunkSpawnMode::StartingPoint, - drunken_lifetime: 400, - floor_percent: 0.5, - brush_size: 1, - symmetry: Symmetry::None - } +impl DrunkardsWalkGen { + pub fn new( spawn_mode: DrunkSpawnMode, + drunken_lifetime: i32, + floor_percent: f32, + brush_size: usize, + symmetry: Symmetry) -> Box + { + Box::new(DrunkardsWalkGen{ + spawn_mode, + drunken_lifetime, + floor_percent, + brush_size, + symmetry }) } - #[allow(dead_code)] - pub fn open_halls() -> Box { - Box::new(DrunkardsWalkBuilder{ - settings : DrunkardSettings{ - spawn_mode: DrunkSpawnMode::Random, - drunken_lifetime: 400, - floor_percent: 0.5, - brush_size: 1, - symmetry: Symmetry::None - }, - }) + pub fn open_area() -> Box { + DrunkardsWalkGen::new(DrunkSpawnMode::StartingPoint, 400, 0.5, 1, Symmetry::None) } - #[allow(dead_code)] - pub fn winding_passages() -> Box { - Box::new(DrunkardsWalkBuilder{ - settings : DrunkardSettings{ - spawn_mode: DrunkSpawnMode::Random, - drunken_lifetime: 100, - floor_percent: 0.4, - brush_size: 1, - symmetry: Symmetry::None - }, - }) + pub fn open_halls() -> Box { + DrunkardsWalkGen::new(DrunkSpawnMode::Random, 400, 0.5, 1, Symmetry::None) } - #[allow(dead_code)] - pub fn fat_passages() -> Box { - Box::new(DrunkardsWalkBuilder{ - settings : DrunkardSettings{ - spawn_mode: DrunkSpawnMode::Random, - drunken_lifetime: 100, - floor_percent: 0.4, - brush_size: 2, - symmetry: Symmetry::None - }, - }) + pub fn winding_passages() -> Box { + DrunkardsWalkGen::new(DrunkSpawnMode::Random, 400, 0.4, 1, Symmetry::None) } - #[allow(dead_code)] - pub fn fearful_symmetry() -> Box { - Box::new(DrunkardsWalkBuilder{ - settings : DrunkardSettings{ - spawn_mode: DrunkSpawnMode::Random, - drunken_lifetime: 100, - floor_percent: 0.4, - brush_size: 1, - symmetry: Symmetry::Both - }, - }) + pub fn fat_passages() -> Box { + DrunkardsWalkGen::new(DrunkSpawnMode::Random, 400, 0.4, 2, Symmetry::None) + } + + pub fn fearful_symmetry() -> Box { + DrunkardsWalkGen::new(DrunkSpawnMode::Random, 400, 0.4, 1, Symmetry::Both) } - fn build(&mut self, rng : &mut RandomNumberGenerator, build_data : &mut BuilderMap) { + fn build(&self, rng: &mut StdRng, width: usize, height: usize) -> Map { + let mut map = Map::new(width, height); // Set a central starting point - let starting_position = Position{ x: build_data.map.width / 2, y: build_data.map.height / 2 }; - let start_idx = build_data.map.xy_idx(starting_position.x, starting_position.y); - build_data.map.tiles[start_idx] = TileType::Floor; + let starting_position = Point::new( map.width / 2, map.height / 2 ); + map.set_tile(starting_position.x, starting_position.y, TileType::Floor); - let total_tiles = build_data.map.width * build_data.map.height; - let desired_floor_tiles = (self.settings.floor_percent * total_tiles as f32) as usize; - let mut floor_tile_count = build_data.map.tiles.iter().filter(|a| **a == TileType::Floor).count(); + let total_tiles = map.width * map.height; + let desired_floor_tiles = (self.floor_percent * total_tiles as f32) as usize; + let mut floor_tile_count = map.tiles.iter().filter(|a| **a == TileType::Floor).count(); let mut digger_count = 0; while floor_tile_count < desired_floor_tiles { - let mut did_something = false; let mut drunk_x; let mut drunk_y; - match self.settings.spawn_mode { + match self.spawn_mode { DrunkSpawnMode::StartingPoint => { drunk_x = starting_position.x; drunk_y = starting_position.y; @@ -125,49 +100,38 @@ impl DrunkardsWalkBuilder { drunk_x = starting_position.x; drunk_y = starting_position.y; } else { - drunk_x = rng.roll_dice(1, build_data.map.width - 3) + 1; - drunk_y = rng.roll_dice(1, build_data.map.height - 3) + 1; + drunk_x = rng.roll_dice(1, map.width - 3) + 1; + drunk_y = rng.roll_dice(1, map.height - 3) + 1; } } } - let mut drunk_life = self.settings.drunken_lifetime; + let mut drunk_life = self.drunken_lifetime; while drunk_life > 0 { - let drunk_idx = build_data.map.xy_idx(drunk_x, drunk_y); - if build_data.map.tiles[drunk_idx] == TileType::Wall { - did_something = true; - } - paint(&mut build_data.map, self.settings.symmetry, self.settings.brush_size, drunk_x, drunk_y); - build_data.map.tiles[drunk_idx] = TileType::DownStairs; + map.set_tile(drunk_x, drunk_y, TileType::Wall); + map.paint(self.symmetry, self.brush_size, drunk_x, drunk_y); + // map.exit_point = Some(Point::new(drunk_x, drunk_y)); let stagger_direction = rng.roll_dice(1, 4); match stagger_direction { 1 => { if drunk_x > 2 { drunk_x -= 1; } } - 2 => { if drunk_x < build_data.map.width-2 { drunk_x += 1; } } + 2 => { if drunk_x < map.width-2 { drunk_x += 1; } } 3 => { if drunk_y > 2 { drunk_y -=1; } } - _ => { if drunk_y < build_data.map.height-2 { drunk_y += 1; } } + _ => { if drunk_y < map.height-2 { drunk_y += 1; } } } drunk_life -= 1; } - if did_something { - build_data.take_snapshot(); - } digger_count += 1; - for t in build_data.map.tiles.iter_mut() { - if *t == TileType::DownStairs { - *t = TileType::Floor; - } - } - floor_tile_count = build_data.map.tiles.iter().filter(|a| **a == TileType::Floor).count(); + // for t in map.tiles.iter_mut() { + // if *t == TileType::DownStairs { + // *t = TileType::Floor; + // } + // } + floor_tile_count = map.tiles.iter().filter(|a| **a == TileType::Floor).count(); } - } -} -impl MetaMapBuilder for DrunkardsWalkBuilder { - #[allow(dead_code)] - fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data : &mut BuilderMap) { - self.build(rng, build_data); + map } } \ No newline at end of file diff --git a/src/map_builder/mod.rs b/src/map_builder/mod.rs index 42f67f6..bbc9d0f 100644 --- a/src/map_builder/mod.rs +++ b/src/map_builder/mod.rs @@ -33,7 +33,7 @@ pub mod bsp_rooms; pub mod cellular_automata; pub mod cull_unreachable; pub mod distant_exit; -// pub mod drunkard; +pub mod drunkard; pub mod simple_rooms; pub mod rooms_corridors_nearest; pub mod starting_point;