Cellular automata generator
This commit is contained in:
parent
8770d8ab77
commit
3805372cf2
|
@ -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"
|
||||||
|
|
|
@ -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=".."}
|
||||||
|
|
|
@ -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");
|
||||||
|
|
103
src/dungeon/cellular_automata.rs
Normal file
103
src/dungeon/cellular_automata.rs
Normal 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// ------------------------------------------------------------------------------------------------
|
/// ------------------------------------------------------------------------------------------------
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user