Cellular automata generator

This commit is contained in:
klangner 2020-08-31 22:03:48 +02:00
parent 8770d8ab77
commit 3805372cf2
6 changed files with 168 additions and 4 deletions

View File

@ -11,4 +11,4 @@ documentation = "https://docs.rs/mapgen"
edition = "2018" edition = "2018"
[dependencies] [dependencies]
log = { version = "0.4.8", features = ["serde"] } rand = "0.7"

View File

@ -5,6 +5,7 @@ authors = ["Krzysztof Langner <klangner@gmail.com>"]
edition = "2018" edition = "2018"
[dependencies] [dependencies]
rand = "0.7"
amethyst = {version = "0.15", features = ["tiles", "no-slow-safety-checks"]} amethyst = {version = "0.15", features = ["tiles", "no-slow-safety-checks"]}
log = { version = "0.4.8", features = ["serde"] } log = { version = "0.4.8", features = ["serde"] }
mapgen = {path=".."} mapgen = {path=".."}

View File

@ -1,3 +1,4 @@
use rand::prelude::*;
use amethyst::{ use amethyst::{
assets::{AssetStorage, Loader}, assets::{AssetStorage, Loader},
core::{ core::{
@ -20,7 +21,9 @@ use amethyst::{
winit, winit,
}; };
use mapgen::dungeon::{ use mapgen::dungeon::{
MapGenerator,
map::{Map, TileType}, map::{Map, TileType},
cellular_automata::CellularAutomataGen,
}; };
@ -63,16 +66,22 @@ fn init_camera(world: &mut World, transform: Transform, camera: Camera) -> Entit
.build() .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; struct PlayState;
impl SimpleState for PlayState { impl SimpleState for PlayState {
fn on_start(&mut self, data: StateData<'_, GameData<'_, '_>>) { fn on_start(&mut self, data: StateData<'_, GameData<'_, '_>>) {
let world = data.world; let mut world = data.world;
// Create map // Create map
let map = Map::new(80, 50); init_map(&mut world);
world.insert(map);
let map_sprite_sheet_handle = let map_sprite_sheet_handle =
load_tiles_sprite_sheet(world, "texture/cp437_20x20.png", "texture/cp437_20x20.ron"); load_tiles_sprite_sheet(world, "texture/cp437_20x20.png", "texture/cp437_20x20.ron");

View File

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

View File

@ -22,11 +22,17 @@ impl Map {
} }
} }
/// Get TileType at the given location
pub fn at(&self, x: usize, y: usize) -> TileType { pub fn at(&self, x: usize, y: usize) -> TileType {
let idx = y * self.width + x; let idx = y * self.width + x;
self.tiles[idx] 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;
}
} }
/// ------------------------------------------------------------------------------------------------ /// ------------------------------------------------------------------------------------------------

View File

@ -1 +1,46 @@
pub mod map; 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<dyn MapGenerator>,
modifiers: Vec<Box<dyn MapModifier>>,
rng: StdRng,
}
impl MapBuilder {
pub fn new(generator : Box<dyn MapGenerator>) -> MapBuilder {
MapBuilder {
generator,
modifiers: Vec::new(),
rng: StdRng::seed_from_u64(0)
}
}
pub fn with(&mut self, modifier : Box<dyn MapModifier>) {
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
}
}