drunkard implemented
This commit is contained in:
parent
764233c7cf
commit
a75f565cbb
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
/// ------------------------------------------------------------------------------------------------
|
||||
|
|
64
src/map.rs
64
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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue
Block a user