Cellular-automata based cave level generator

This commit is contained in:
Griffin Smith 2019-07-20 01:40:09 -04:00
parent 29c80ac8ba
commit d001b0a017
6 changed files with 162 additions and 3 deletions

View file

@ -12,3 +12,11 @@ args:
subcommands: subcommands:
- debug: - debug:
about: Writes debug information to the terminal and exits about: Writes debug information to the terminal and exits
- generate-level:
about: Generate a level and print it to the screen
args:
- generator:
long: generator
value_name: GEN
help: Select which generator to use
takes_value: true

View file

@ -0,0 +1,85 @@
use crate::level_gen::util::rand_initialize;
use crate::types::Dimensions;
use rand::Rng;
pub struct Params {
chance_to_start_alive: f64,
dimensions: Dimensions,
birth_limit: i32,
death_limit: i32,
steps: usize,
}
impl Default for Params {
fn default() -> Self {
Params {
chance_to_start_alive: 0.45,
dimensions: Dimensions { w: 80, h: 20 },
birth_limit: 4,
death_limit: 3,
steps: 2,
}
}
}
pub fn generate<R: Rng + ?Sized>(
params: &Params,
rand: &mut R,
) -> Vec<Vec<bool>> {
let mut cells =
rand_initialize(&params.dimensions, rand, params.chance_to_start_alive);
for _ in 0..params.steps {
step_automata(&mut cells, params);
}
cells
}
fn step_automata(cells: &mut Vec<Vec<bool>>, params: &Params) {
let orig_cells = (*cells).clone();
for x in 0..(params.dimensions.h as usize) {
for y in 0..(params.dimensions.w as usize) {
let nbs = num_alive_neighbors(&orig_cells, x as i32, y as i32);
if orig_cells[x][y] {
if nbs < params.death_limit {
cells[x][y] = false;
} else {
cells[x][y] = true;
}
} else {
if nbs > params.birth_limit {
cells[x][y] = true;
} else {
cells[x][y] = false;
}
}
}
}
}
const COUNT_EDGES_AS_NEIGHBORS: bool = true;
fn num_alive_neighbors(cells: &Vec<Vec<bool>>, x: i32, y: i32) -> i32 {
let mut count = 0;
for i in -1..2 {
for j in -1..2 {
if i == 0 && j == 0 {
continue;
}
let neighbor_x = x + i;
let neighbor_y = y + j;
if COUNT_EDGES_AS_NEIGHBORS
&& (neighbor_x < 0
|| neighbor_y < 0
|| neighbor_x >= (cells.len() as i32)
|| neighbor_y >= (cells[0].len()) as i32)
{
count += 1;
} else if cells[neighbor_x as usize][neighbor_y as usize] {
count += 1;
}
}
}
count
}

17
src/level_gen/display.rs Normal file
View file

@ -0,0 +1,17 @@
use std::io::{self, Write};
pub fn print_generated_level<W>(
level: &Vec<Vec<bool>>,
out: &mut W,
) -> io::Result<()>
where
W: Write,
{
for row in level {
for cell in row {
write!(out, "{}", if *cell { "X" } else { " " })?;
}
write!(out, "\n")?;
}
Ok(())
}

3
src/level_gen/mod.rs Normal file
View file

@ -0,0 +1,3 @@
pub mod cave_automata;
pub mod display;
pub mod util;

31
src/level_gen/util.rs Normal file
View file

@ -0,0 +1,31 @@
use crate::types::Dimensions;
use rand::{distributions, Rng};
pub fn falses(dims: &Dimensions) -> Vec<Vec<bool>> {
let mut ret = Vec::with_capacity(dims.h as usize);
for _ in 0..dims.h {
let mut row = Vec::with_capacity(dims.w as usize);
for _ in 0..dims.w {
row.push(false);
}
ret.push(row);
}
ret
}
pub fn rand_initialize<R: Rng + ?Sized>(
dims: &Dimensions,
rng: &mut R,
alive_chance: f64,
) -> Vec<Vec<bool>> {
let distrib = distributions::Bernoulli::new(alive_chance).unwrap();
let mut ret = Vec::with_capacity(dims.h as usize);
for _ in 0..dims.h {
let mut row = Vec::with_capacity(dims.w as usize);
for _ in 0..dims.w {
row.push(rng.sample(distrib));
}
ret.push(row);
}
ret
}

View file

@ -35,12 +35,15 @@ mod types;
mod entities; mod entities;
mod display; mod display;
mod game; mod game;
mod level_gen;
mod messages; mod messages;
mod settings; mod settings;
use clap::App; use clap::App;
use game::Game; use game::Game;
use prettytable::format::consts::FORMAT_BOX_CHARS; use prettytable::format::consts::FORMAT_BOX_CHARS;
use rand::rngs::SmallRng;
use rand::SeedableRng;
use settings::Settings; use settings::Settings;
use backtrace::Backtrace; use backtrace::Backtrace;
@ -73,14 +76,12 @@ fn main() {
let settings = Settings::load().unwrap(); let settings = Settings::load().unwrap();
settings.logging.init_log(); settings.logging.init_log();
let stdout = io::stdout(); let stdout = io::stdout();
let stdout = stdout.lock(); let mut stdout = stdout.lock();
let stdin = io::stdin(); let stdin = io::stdin();
let stdin = stdin.lock(); let stdin = stdin.lock();
let termsize = termion::terminal_size().ok(); let termsize = termion::terminal_size().ok();
// let termwidth = termsize.map(|(w, _)| w - 2).unwrap_or(70);
// let termheight = termsize.map(|(_, h)| h - 2).unwrap_or(40);
let (termwidth, termheight) = termsize.unwrap_or((70, 40)); let (termwidth, termheight) = termsize.unwrap_or((70, 40));
match matches.subcommand() { match matches.subcommand() {
@ -94,6 +95,20 @@ fn main() {
table.set_format(*FORMAT_BOX_CHARS); table.set_format(*FORMAT_BOX_CHARS);
table.printstd(); table.printstd();
} }
("generate-level", params) => {
let params = params.unwrap();
let mut rand = SmallRng::from_entropy();
let level = match params.value_of("generator") {
None => panic!("Must supply a generator with --generator"),
Some("cave_automata") => level_gen::cave_automata::generate(
&Default::default(),
&mut rand,
),
Some(gen) => panic!("Unrecognized generator: {}", gen),
};
level_gen::display::print_generated_level(&level, &mut stdout)
.unwrap();
}
_ => { _ => {
let stdout = stdout.into_raw_mode().unwrap(); let stdout = stdout.into_raw_mode().unwrap();
init(settings, stdout, stdin, termwidth, termheight); init(settings, stdout, stdin, termwidth, termheight);