drunkard implemented

This commit is contained in:
klangner 2020-09-16 11:42:59 +02:00
parent 764233c7cf
commit a75f565cbb
8 changed files with 185 additions and 122 deletions

View File

@ -16,7 +16,7 @@ Generate procedural maps for games. [Try it in the browser](https://klangner.git
* [x] BSP Rooms * [x] BSP Rooms
* [x] Cellular automata * [x] Cellular automata
* [ ] Diffusion-Limited Aggregation (DLA) * [ ] Diffusion-Limited Aggregation (DLA)
* [ ] Drunkard's walk * [x] Drunkard's walk
* [ ] Maze * [ ] Maze
* [ ] Prefabs * [ ] Prefabs
* [x] Simple rooms * [x] Simple rooms

View File

@ -11,6 +11,7 @@ use mapgen::{
cull_unreachable::CullUnreachable, cull_unreachable::CullUnreachable,
distant_exit::DistantExit, distant_exit::DistantExit,
rooms_corridors_nearest::NearestCorridors, rooms_corridors_nearest::NearestCorridors,
drunkard::DrunkardsWalkGen,
}, },
map::TileType, map::TileType,
}; };
@ -80,13 +81,29 @@ impl World {
tiles } 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 { pub fn new_random(width: u32, height: u32, seed: u32) -> World {
let mut rng = rand::thread_rng(); let mut rng = rand::thread_rng();
let px = rng.gen::<f32>(); let px = rng.gen::<f32>();
if px < 0.3333 { if px < 0.25 {
World::new_cellular_automata(width, height, seed) World::new_cellular_automata(width, height, seed)
} else if px < 0.6666 { } else if px < 0.5 {
World::new_simple_rooms(width, height, seed) World::new_simple_rooms(width, height, seed)
} else if px < 0.75 {
World::new_drunkard(width, height, seed)
} else { } else {
World::new_bsp_interior(width, height, seed) World::new_bsp_interior(width, height, seed)
} }

View File

@ -38,6 +38,7 @@
<a class="dropdown-item" id="cellular-automata-option">Cellular Automata</a> <a class="dropdown-item" id="cellular-automata-option">Cellular Automata</a>
<a class="dropdown-item" id="simple-rooms-option">Simple Rooms</a> <a class="dropdown-item" id="simple-rooms-option">Simple Rooms</a>
<a class="dropdown-item" id="bsp-interior-option">BSP Interior</a> <a class="dropdown-item" id="bsp-interior-option">BSP Interior</a>
<a class="dropdown-item" id="drunkard-option">Drunkard Walk</a>
<a class="dropdown-item" id="random-option">Random Generator</a> <a class="dropdown-item" id="random-option">Random Generator</a>
</div> </div>
</li> </li>

View File

@ -38,6 +38,12 @@ function newBspInterior() {
requestAnimationFrame(renderLoop); requestAnimationFrame(renderLoop);
} }
function newDrunkard() {
var seed = Date.now();
world = World.new_drunkard(width, height, seed);
requestAnimationFrame(renderLoop);
}
function newRandomGen() { function newRandomGen() {
var seed = Date.now(); var seed = Date.now();
world = World.new_random(width, height, seed); world = World.new_random(width, height, seed);
@ -108,4 +114,5 @@ newRandomGen();
document.getElementById('cellular-automata-option').addEventListener('click', newCellularAutomata); document.getElementById('cellular-automata-option').addEventListener('click', newCellularAutomata);
document.getElementById('simple-rooms-option').addEventListener('click', newSimpleRooms); document.getElementById('simple-rooms-option').addEventListener('click', newSimpleRooms);
document.getElementById('bsp-interior-option').addEventListener('click', newBspInterior); document.getElementById('bsp-interior-option').addEventListener('click', newBspInterior);
document.getElementById('drunkard-option').addEventListener('click', newDrunkard);
document.getElementById('random-option').addEventListener('click', newRandomGen); document.getElementById('random-option').addEventListener('click', newRandomGen);

View File

@ -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 /// Module unit tests
/// ------------------------------------------------------------------------------------------------ /// ------------------------------------------------------------------------------------------------

View File

@ -8,7 +8,7 @@
//! //!
use std::fmt; use std::fmt;
use super::geometry::{Point, Rect}; use super::geometry::{Point, Rect, usize_abs};
#[derive(PartialEq, Copy, Clone, Debug, Eq, Hash)] #[derive(PartialEq, Copy, Clone, Debug, Eq, Hash)]
@ -16,6 +16,10 @@ pub enum TileType {
Wall, Floor Wall, Floor
} }
#[derive(PartialEq, Copy, Clone)]
pub enum Symmetry { None, Horizontal, Vertical, Both }
/// Map data /// Map data
#[derive(Default, Clone)] #[derive(Default, Clone)]
pub struct Map { 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 { impl fmt::Display for Map {

View File

@ -1,121 +1,96 @@
use rltk::RandomNumberGenerator; //! Example generator usage:
use crate::map::TileType; //! ```
use crate::components::Position; //! use rand::prelude::*;
use super::{InitialMapBuilder, MetaMapBuilder, BuilderMap}; //! use mapgen::map_builder::{
use super::common::*; //! 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)] #[derive(PartialEq, Copy, Clone)]
#[allow(dead_code)]
pub enum DrunkSpawnMode { StartingPoint, Random } pub enum DrunkSpawnMode { StartingPoint, Random }
pub struct DrunkardSettings { pub struct DrunkardsWalkGen {
pub spawn_mode : DrunkSpawnMode, spawn_mode : DrunkSpawnMode,
pub drunken_lifetime : i32, drunken_lifetime : i32,
pub floor_percent: f32, floor_percent: f32,
pub brush_size: i32, brush_size: usize,
pub symmetry: Symmetry symmetry: Symmetry
} }
pub struct DrunkardsWalkBuilder { impl MapGenerator for DrunkardsWalkGen {
settings : DrunkardSettings fn generate_map(&self, width: usize, height: usize, rng: &mut StdRng) -> Map {
} self.build(rng, width, height)
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 DrunkardsWalkBuilder { impl DrunkardsWalkGen {
#[allow(dead_code)] pub fn new( spawn_mode: DrunkSpawnMode,
pub fn new(settings: DrunkardSettings) -> DrunkardsWalkBuilder { drunken_lifetime: i32,
DrunkardsWalkBuilder{ floor_percent: f32,
settings brush_size: usize,
} symmetry: Symmetry) -> Box<DrunkardsWalkGen>
} {
Box::new(DrunkardsWalkGen{
#[allow(dead_code)] spawn_mode,
pub fn open_area() -> Box<DrunkardsWalkBuilder> { drunken_lifetime,
Box::new(DrunkardsWalkBuilder{ floor_percent,
settings : DrunkardSettings{ brush_size,
spawn_mode: DrunkSpawnMode::StartingPoint, symmetry
drunken_lifetime: 400,
floor_percent: 0.5,
brush_size: 1,
symmetry: Symmetry::None
}
}) })
} }
#[allow(dead_code)] pub fn open_area() -> Box<DrunkardsWalkGen> {
pub fn open_halls() -> Box<DrunkardsWalkBuilder> { DrunkardsWalkGen::new(DrunkSpawnMode::StartingPoint, 400, 0.5, 1, Symmetry::None)
Box::new(DrunkardsWalkBuilder{
settings : DrunkardSettings{
spawn_mode: DrunkSpawnMode::Random,
drunken_lifetime: 400,
floor_percent: 0.5,
brush_size: 1,
symmetry: Symmetry::None
},
})
} }
#[allow(dead_code)] pub fn open_halls() -> Box<DrunkardsWalkGen> {
pub fn winding_passages() -> Box<DrunkardsWalkBuilder> { DrunkardsWalkGen::new(DrunkSpawnMode::Random, 400, 0.5, 1, Symmetry::None)
Box::new(DrunkardsWalkBuilder{
settings : DrunkardSettings{
spawn_mode: DrunkSpawnMode::Random,
drunken_lifetime: 100,
floor_percent: 0.4,
brush_size: 1,
symmetry: Symmetry::None
},
})
} }
#[allow(dead_code)] pub fn winding_passages() -> Box<DrunkardsWalkGen> {
pub fn fat_passages() -> Box<DrunkardsWalkBuilder> { DrunkardsWalkGen::new(DrunkSpawnMode::Random, 400, 0.4, 1, Symmetry::None)
Box::new(DrunkardsWalkBuilder{
settings : DrunkardSettings{
spawn_mode: DrunkSpawnMode::Random,
drunken_lifetime: 100,
floor_percent: 0.4,
brush_size: 2,
symmetry: Symmetry::None
},
})
} }
#[allow(dead_code)] pub fn fat_passages() -> Box<DrunkardsWalkGen> {
pub fn fearful_symmetry() -> Box<DrunkardsWalkBuilder> { DrunkardsWalkGen::new(DrunkSpawnMode::Random, 400, 0.4, 2, Symmetry::None)
Box::new(DrunkardsWalkBuilder{ }
settings : DrunkardSettings{
spawn_mode: DrunkSpawnMode::Random, pub fn fearful_symmetry() -> Box<DrunkardsWalkGen> {
drunken_lifetime: 100, DrunkardsWalkGen::new(DrunkSpawnMode::Random, 400, 0.4, 1, Symmetry::Both)
floor_percent: 0.4,
brush_size: 1,
symmetry: 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 // Set a central starting point
let starting_position = Position{ x: build_data.map.width / 2, y: build_data.map.height / 2 }; let starting_position = Point::new( map.width / 2, map.height / 2 );
let start_idx = build_data.map.xy_idx(starting_position.x, starting_position.y); map.set_tile(starting_position.x, starting_position.y, TileType::Floor);
build_data.map.tiles[start_idx] = TileType::Floor;
let total_tiles = build_data.map.width * build_data.map.height; let total_tiles = map.width * map.height;
let desired_floor_tiles = (self.settings.floor_percent * total_tiles as f32) as usize; let desired_floor_tiles = (self.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 mut floor_tile_count = map.tiles.iter().filter(|a| **a == TileType::Floor).count();
let mut digger_count = 0; let mut digger_count = 0;
while floor_tile_count < desired_floor_tiles { while floor_tile_count < desired_floor_tiles {
let mut did_something = false;
let mut drunk_x; let mut drunk_x;
let mut drunk_y; let mut drunk_y;
match self.settings.spawn_mode { match self.spawn_mode {
DrunkSpawnMode::StartingPoint => { DrunkSpawnMode::StartingPoint => {
drunk_x = starting_position.x; drunk_x = starting_position.x;
drunk_y = starting_position.y; drunk_y = starting_position.y;
@ -125,49 +100,38 @@ impl DrunkardsWalkBuilder {
drunk_x = starting_position.x; drunk_x = starting_position.x;
drunk_y = starting_position.y; drunk_y = starting_position.y;
} else { } else {
drunk_x = rng.roll_dice(1, build_data.map.width - 3) + 1; drunk_x = rng.roll_dice(1, map.width - 3) + 1;
drunk_y = rng.roll_dice(1, build_data.map.height - 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 { while drunk_life > 0 {
let drunk_idx = build_data.map.xy_idx(drunk_x, drunk_y); map.set_tile(drunk_x, drunk_y, TileType::Wall);
if build_data.map.tiles[drunk_idx] == TileType::Wall { map.paint(self.symmetry, self.brush_size, drunk_x, drunk_y);
did_something = true; // map.exit_point = Some(Point::new(drunk_x, drunk_y));
}
paint(&mut build_data.map, self.settings.symmetry, self.settings.brush_size, drunk_x, drunk_y);
build_data.map.tiles[drunk_idx] = TileType::DownStairs;
let stagger_direction = rng.roll_dice(1, 4); let stagger_direction = rng.roll_dice(1, 4);
match stagger_direction { match stagger_direction {
1 => { if drunk_x > 2 { drunk_x -= 1; } } 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; } } 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; drunk_life -= 1;
} }
if did_something {
build_data.take_snapshot();
}
digger_count += 1; digger_count += 1;
for t in build_data.map.tiles.iter_mut() { // for t in map.tiles.iter_mut() {
if *t == TileType::DownStairs { // if *t == TileType::DownStairs {
*t = TileType::Floor; // *t = TileType::Floor;
} // }
} // }
floor_tile_count = build_data.map.tiles.iter().filter(|a| **a == TileType::Floor).count(); floor_tile_count = map.tiles.iter().filter(|a| **a == TileType::Floor).count();
} }
}
}
impl MetaMapBuilder for DrunkardsWalkBuilder { map
#[allow(dead_code)]
fn build_map(&mut self, rng: &mut rltk::RandomNumberGenerator, build_data : &mut BuilderMap) {
self.build(rng, build_data);
} }
} }

View File

@ -33,7 +33,7 @@ pub mod bsp_rooms;
pub mod cellular_automata; pub mod cellular_automata;
pub mod cull_unreachable; pub mod cull_unreachable;
pub mod distant_exit; pub mod distant_exit;
// pub mod drunkard; pub mod drunkard;
pub mod simple_rooms; pub mod simple_rooms;
pub mod rooms_corridors_nearest; pub mod rooms_corridors_nearest;
pub mod starting_point; pub mod starting_point;