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] Cellular automata
* [ ] Diffusion-Limited Aggregation (DLA)
* [ ] Drunkard's walk
* [x] Drunkard's walk
* [ ] Maze
* [ ] Prefabs
* [x] Simple rooms

View File

@ -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::<f32>();
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)
}

View File

@ -38,6 +38,7 @@
<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="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>
</div>
</li>

View File

@ -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);

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
/// ------------------------------------------------------------------------------------------------

View File

@ -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 {

View File

@ -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<DrunkardsWalkBuilder> {
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<DrunkardsWalkGen>
{
Box::new(DrunkardsWalkGen{
spawn_mode,
drunken_lifetime,
floor_percent,
brush_size,
symmetry
})
}
#[allow(dead_code)]
pub fn open_halls() -> Box<DrunkardsWalkBuilder> {
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> {
DrunkardsWalkGen::new(DrunkSpawnMode::StartingPoint, 400, 0.5, 1, Symmetry::None)
}
#[allow(dead_code)]
pub fn winding_passages() -> Box<DrunkardsWalkBuilder> {
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> {
DrunkardsWalkGen::new(DrunkSpawnMode::Random, 400, 0.5, 1, Symmetry::None)
}
#[allow(dead_code)]
pub fn fat_passages() -> Box<DrunkardsWalkBuilder> {
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> {
DrunkardsWalkGen::new(DrunkSpawnMode::Random, 400, 0.4, 1, Symmetry::None)
}
#[allow(dead_code)]
pub fn fearful_symmetry() -> Box<DrunkardsWalkBuilder> {
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> {
DrunkardsWalkGen::new(DrunkSpawnMode::Random, 400, 0.4, 2, Symmetry::None)
}
fn build(&mut self, rng : &mut RandomNumberGenerator, build_data : &mut BuilderMap) {
pub fn fearful_symmetry() -> Box<DrunkardsWalkGen> {
DrunkardsWalkGen::new(DrunkSpawnMode::Random, 400, 0.4, 1, Symmetry::Both)
}
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
}
}

View File

@ -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;