From 3805372cf2f1719e210eb49b954cb7a1768393ac Mon Sep 17 00:00:00 2001 From: klangner Date: Mon, 31 Aug 2020 22:03:48 +0200 Subject: [PATCH] Cellular automata generator --- Cargo.toml | 2 +- demo/Cargo.toml | 1 + demo/src/main.rs | 15 ++++- src/dungeon/cellular_automata.rs | 103 +++++++++++++++++++++++++++++++ src/dungeon/map.rs | 6 ++ src/dungeon/mod.rs | 45 ++++++++++++++ 6 files changed, 168 insertions(+), 4 deletions(-) create mode 100644 src/dungeon/cellular_automata.rs diff --git a/Cargo.toml b/Cargo.toml index ef2e8fa..57e84f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,4 +11,4 @@ documentation = "https://docs.rs/mapgen" edition = "2018" [dependencies] -log = { version = "0.4.8", features = ["serde"] } +rand = "0.7" diff --git a/demo/Cargo.toml b/demo/Cargo.toml index f329aea..ec41db8 100644 --- a/demo/Cargo.toml +++ b/demo/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Krzysztof Langner "] edition = "2018" [dependencies] +rand = "0.7" amethyst = {version = "0.15", features = ["tiles", "no-slow-safety-checks"]} log = { version = "0.4.8", features = ["serde"] } mapgen = {path=".."} diff --git a/demo/src/main.rs b/demo/src/main.rs index 83d7d92..f1b936a 100644 --- a/demo/src/main.rs +++ b/demo/src/main.rs @@ -1,3 +1,4 @@ +use rand::prelude::*; use amethyst::{ assets::{AssetStorage, Loader}, core::{ @@ -20,7 +21,9 @@ use amethyst::{ winit, }; use mapgen::dungeon::{ + MapGenerator, map::{Map, TileType}, + cellular_automata::CellularAutomataGen, }; @@ -63,16 +66,22 @@ fn init_camera(world: &mut World, transform: Transform, camera: Camera) -> Entit .build() } +fn init_map(world: &mut World) { + let gen = CellularAutomataGen::new(80, 50); + let mut rng = StdRng::seed_from_u64(0); + let map = gen.generate_map(&mut rng); + world.insert(map); +} + struct PlayState; impl SimpleState for PlayState { fn on_start(&mut self, data: StateData<'_, GameData<'_, '_>>) { - let world = data.world; + let mut world = data.world; // Create map - let map = Map::new(80, 50); - world.insert(map); + init_map(&mut world); let map_sprite_sheet_handle = load_tiles_sprite_sheet(world, "texture/cp437_20x20.png", "texture/cp437_20x20.ron"); diff --git a/src/dungeon/cellular_automata.rs b/src/dungeon/cellular_automata.rs new file mode 100644 index 0000000..6646f99 --- /dev/null +++ b/src/dungeon/cellular_automata.rs @@ -0,0 +1,103 @@ +use rand::prelude::*; +use super::{MapGenerator, MapModifier}; +use super::map::{Map, TileType}; + + +pub struct CellularAutomataGen { + width: usize, + height: usize +} + +impl MapGenerator for CellularAutomataGen { + fn generate_map(&self, rng : &mut StdRng) -> Map { + self.build(rng) + } +} + +impl CellularAutomataGen { + pub fn new(width: usize, height: usize) -> CellularAutomataGen { + CellularAutomataGen {width, height} + } + + /// Generate map + fn build(&self, rng : &mut StdRng) -> Map { + let mut map = Map::new(self.width, self.height); + // First we completely randomize the map, setting 55% of it to be floor. + for y in 1..self.height-1 { + for x in 1..self.width-1 { + let roll = rng.next_u32() % 100; + if roll > 55 { map.set_tile(x, y, TileType::Floor) } + else { map.set_tile(x, y, TileType::Wall) } + } + } + + // Now we iteratively apply cellular automata rules + for _ in 0..15 { + map = apply_iteration(&map); + } + + map + } + +} + +impl MapModifier for CellularAutomataGen { + fn modify_map(&self, _rng: &mut StdRng, map : &Map) -> Map { + apply_iteration(map) + } +} + + +fn apply_iteration(map: &Map) -> Map { + let mut new_map = map.clone(); + + for y in 1..map.height-1 { + for x in 1..map.width-1 { + let idxs = [ + (x-1, y-1), (x, y-1), (x+1, y-1), + (x-1, y), (x+1, y), + (x-1, y+1), (x, y+1), (x+1, y+1)]; + let neighbors = idxs.iter() + .filter(|(x, y)| map.at(*x, *y) == TileType::Wall) + .count(); + + if neighbors > 4 || neighbors == 0 { + new_map.set_tile(x, y, TileType::Wall) + } + else { + new_map.set_tile(x, y, TileType::Floor); + } + } + } + + new_map +} + +/// ------------------------------------------------------------------------------------------------ +/// Module unit tests +/// ------------------------------------------------------------------------------------------------ +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_iteration_wal() { + let map = Map::new(3, 3); + let new_map = apply_iteration(&map); + assert_eq!(new_map.at(1, 1), TileType::Wall); + } + + + #[test] + fn test_iteration_floor() { + let mut map = Map::new(3, 3); + for i in 0..3 { + for j in 0..2 { + map.set_tile(i, j, TileType::Floor); + } + } + let new_map = apply_iteration(&map); + assert_eq!(new_map.at(1, 1), TileType::Floor); + } + +} \ No newline at end of file diff --git a/src/dungeon/map.rs b/src/dungeon/map.rs index 2a0260f..1a52c20 100644 --- a/src/dungeon/map.rs +++ b/src/dungeon/map.rs @@ -22,11 +22,17 @@ impl Map { } } + /// Get TileType at the given location pub fn at(&self, x: usize, y: usize) -> TileType { let idx = y * self.width + x; self.tiles[idx] } + /// Modify tile at the given location + pub fn set_tile(&mut self, x: usize, y: usize, tile: TileType) { + let idx = y * self.width + x; + self.tiles[idx] = tile; + } } /// ------------------------------------------------------------------------------------------------ diff --git a/src/dungeon/mod.rs b/src/dungeon/mod.rs index 1d7f53b..f1b7a06 100644 --- a/src/dungeon/mod.rs +++ b/src/dungeon/mod.rs @@ -1 +1,46 @@ pub mod map; +pub mod cellular_automata; + +use rand::prelude::*; +use map::Map; + + +pub trait MapGenerator { + fn generate_map(&self, rng: &mut StdRng) -> Map; +} + +pub trait MapModifier { + fn modify_map(&self, rng: &mut StdRng, map: &Map) -> Map; +} + +pub struct MapBuilder { + generator: Box, + modifiers: Vec>, + rng: StdRng, +} + +impl MapBuilder { + pub fn new(generator : Box) -> MapBuilder { + MapBuilder { + generator, + modifiers: Vec::new(), + rng: StdRng::seed_from_u64(0) + } + } + + pub fn with(&mut self, modifier : Box) { + self.modifiers.push(modifier); + } + + pub fn build_map(&mut self) -> Map { + let mut map = self.generator.generate_map(&mut self.rng); + + // Build additional layers in turn + for modifier in self.modifiers.iter() { + modifier.modify_map(&mut self.rng, &mut map); + } + + map + } +} +