Cellular-automata based cave level generator
This commit is contained in:
parent
29c80ac8ba
commit
d001b0a017
6 changed files with 162 additions and 3 deletions
|
@ -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
|
||||||
|
|
85
src/level_gen/cave_automata.rs
Normal file
85
src/level_gen/cave_automata.rs
Normal 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(¶ms.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
17
src/level_gen/display.rs
Normal 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
3
src/level_gen/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
pub mod cave_automata;
|
||||||
|
pub mod display;
|
||||||
|
pub mod util;
|
31
src/level_gen/util.rs
Normal file
31
src/level_gen/util.rs
Normal 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
|
||||||
|
}
|
21
src/main.rs
21
src/main.rs
|
@ -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);
|
||||||
|
|
Loading…
Reference in a new issue