use crate::dungeon::{DungeonGrid, DungeonType, TileType};
use crate::grid_coord_2d::{GetCoordinateBounds2D, GridCoord2D};
use rand::prelude::IndexedRandom;
use rand::rngs::StdRng;
use rand::{RngExt, SeedableRng};
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum DungeonGenerationStep {
PlaceFloor { coord: GridCoord2D },
StampRoom {
center: GridCoord2D,
half_width: usize,
half_height: usize,
},
PlaceWall { coord: GridCoord2D },
SetExit { coord: GridCoord2D },
Complete,
}
pub trait DungeonGenerationVisitor {
fn on_step(&mut self, step: &DungeonGenerationStep);
}
#[derive(Default)]
pub struct VecDungeonGenerationVisitor {
steps: Vec<DungeonGenerationStep>,
}
impl VecDungeonGenerationVisitor {
pub fn into_steps(self) -> Vec<DungeonGenerationStep> {
self.steps
}
}
impl DungeonGenerationVisitor for VecDungeonGenerationVisitor {
fn on_step(&mut self, step: &DungeonGenerationStep) {
self.steps.push(step.clone());
}
}
struct NoOpVisitor;
impl DungeonGenerationVisitor for NoOpVisitor {
#[inline]
fn on_step(&mut self, _step: &DungeonGenerationStep) {
}
}
pub struct DungeonGenerationSteps {
inner: std::vec::IntoIter<DungeonGenerationStep>,
}
impl DungeonGenerationSteps {
pub fn new(steps: Vec<DungeonGenerationStep>) -> Self {
Self {
inner: steps.into_iter(),
}
}
}
impl Iterator for DungeonGenerationSteps {
type Item = DungeonGenerationStep;
fn next(&mut self) -> Option<Self::Item> {
self.inner.next()
}
}
pub trait DungeonGenerator {
fn new_random() -> Self
where
Self: Sized;
fn new_from_seed(seed: u64) -> Self
where
Self: Sized;
fn generate(&self, width: usize, height: usize, floor_count: usize) -> DungeonGrid;
fn generate_steps(
&self,
width: usize,
height: usize,
floor_count: usize,
) -> DungeonGenerationSteps {
let _ = self.generate(width, height, floor_count);
DungeonGenerationSteps::new(vec![DungeonGenerationStep::Complete])
}
fn dungeon_type(&self) -> DungeonType;
fn name(&self) -> &'static str {
self.dungeon_type().name()
}
fn description(&self) -> &'static str {
self.dungeon_type().description()
}
}
pub struct DungeonWalkGenerator {
rng_seed: u64,
dungeon_type: DungeonType,
winding_hall_probability: u8,
long_walk_min: usize,
long_walk_max: usize,
}
struct WalkContext<'a, V> {
rng: &'a mut StdRng,
grid: &'a mut DungeonGrid,
visitor: &'a mut V,
width: usize,
height: usize,
last_floor: GridCoord2D,
}
impl DungeonWalkGenerator {
pub fn new_random(dungeon_type: DungeonType) -> Self {
Self {
rng_seed: rand::random(),
dungeon_type,
winding_hall_probability: 50, long_walk_min: 9, long_walk_max: 18, }
}
pub fn new_from_seed(dungeon_type: DungeonType, seed: u64) -> Self {
let rng_seed = if seed == 0 { rand::random() } else { seed };
Self {
rng_seed,
dungeon_type,
winding_hall_probability: 50,
long_walk_min: 9,
long_walk_max: 18,
}
}
pub fn with_winding_probability(mut self, probability: u8) -> Self {
self.winding_hall_probability = probability.min(99);
self
}
pub fn with_long_walk_range(mut self, min: usize, max_exclusive: usize) -> Self {
let min = min.max(1); let max_exclusive = max_exclusive.max(min + 1); self.long_walk_min = min;
self.long_walk_max = max_exclusive;
self
}
pub fn generate(&self, width: usize, height: usize, floor_count: usize) -> DungeonGrid {
self.generate_internal(width, height, floor_count, &mut NoOpVisitor, false)
}
pub fn generate_steps(
&self,
width: usize,
height: usize,
floor_count: usize,
) -> DungeonGenerationSteps {
let mut visitor = VecDungeonGenerationVisitor::default();
let _ = self.generate_internal(width, height, floor_count, &mut visitor, true);
DungeonGenerationSteps::new(visitor.into_steps())
}
fn generate_internal<V: DungeonGenerationVisitor>(
&self,
width: usize,
height: usize,
floor_count: usize,
visitor: &mut V,
emit_wall_steps: bool,
) -> DungeonGrid {
let mut grid = DungeonGrid::new(width, height);
let mut rng = StdRng::seed_from_u64(self.rng_seed);
if width == 0 || height == 0 || floor_count == 0 {
visitor.on_step(&DungeonGenerationStep::Complete);
return grid;
}
let max_possible_floor = width.saturating_mul(height).saturating_mul(9) / 10; let target_floor_count = floor_count.min(max_possible_floor);
let mut walker_pos = GridCoord2D::new(width / 2, height / 2);
let mut last_floor_pos = walker_pos;
grid.set(walker_pos, TileType::Floor);
visitor.on_step(&DungeonGenerationStep::PlaceFloor { coord: walker_pos });
let mut iterations_since_progress = 0;
let max_stalled_iterations = target_floor_count
.max(width.saturating_mul(height) / 10)
.max(1000);
while grid.floor_count() < target_floor_count {
let floor_count_before = grid.floor_count();
iterations_since_progress += 1;
if iterations_since_progress > max_stalled_iterations {
break;
}
match self.dungeon_type {
DungeonType::Caverns => {
walker_pos = self.take_step(&mut rng, walker_pos, width, height);
if !grid.is_floor(walker_pos) {
grid.set(walker_pos, TileType::Floor);
visitor.on_step(&DungeonGenerationStep::PlaceFloor { coord: walker_pos });
last_floor_pos = walker_pos;
}
}
DungeonType::Rooms | DungeonType::Winding => {
let mut ctx = WalkContext {
rng: &mut rng,
grid: &mut grid,
visitor,
width,
height,
last_floor: last_floor_pos,
};
walker_pos = self.take_long_walk(&mut ctx, walker_pos, target_floor_count);
let should_stamp_room = if self.dungeon_type == DungeonType::Rooms {
true
} else {
let roll = ctx.rng.random_range(0..100);
roll > self.winding_hall_probability
};
if should_stamp_room && ctx.grid.floor_count() < target_floor_count {
self.stamp_room(&mut ctx, walker_pos, target_floor_count);
}
last_floor_pos = ctx.last_floor;
}
}
if grid.floor_count() > floor_count_before {
iterations_since_progress = 0;
}
}
grid.place_walls();
if emit_wall_steps {
for y in 0..height {
for x in 0..width {
let coord = GridCoord2D::new(x, y);
if grid.get(coord).unwrap().is_wall() {
visitor.on_step(&DungeonGenerationStep::PlaceWall { coord });
}
}
}
}
grid.set_exit(last_floor_pos);
visitor.on_step(&DungeonGenerationStep::SetExit {
coord: last_floor_pos,
});
grid.compute_edge_masks();
visitor.on_step(&DungeonGenerationStep::Complete);
grid
}
fn take_step(
&self,
rng: &mut StdRng,
pos: GridCoord2D,
width: usize,
height: usize,
) -> GridCoord2D {
let directions: [(isize, isize); 4] = [
(0, -1), (1, 0), (0, 1), (-1, 0), ];
let &(dx, dy) = directions.choose(rng).unwrap();
let new_x = (pos.x as isize + dx).max(0).min(width as isize - 1) as usize;
let new_y = (pos.y as isize + dy).max(0).min(height as isize - 1) as usize;
GridCoord2D::new(new_x, new_y)
}
fn take_long_walk<V: DungeonGenerationVisitor>(
&self,
ctx: &mut WalkContext<V>,
start: GridCoord2D,
target_floor_count: usize,
) -> GridCoord2D {
let walk_length = ctx.rng.random_range(self.long_walk_min..self.long_walk_max);
let directions: [(isize, isize); 4] = [
(0, -1), (1, 0), (0, 1), (-1, 0), ];
let &(dx, dy) = directions.choose(ctx.rng).unwrap();
let mut pos = start;
for _ in 0..walk_length {
if ctx.grid.floor_count() >= target_floor_count {
break;
}
let new_x = (pos.x as isize + dx).max(0).min(ctx.width as isize - 1) as usize;
let new_y = (pos.y as isize + dy).max(0).min(ctx.height as isize - 1) as usize;
pos = GridCoord2D::new(new_x, new_y);
if !ctx.grid.is_floor(pos) {
ctx.grid.set(pos, TileType::Floor);
ctx.visitor
.on_step(&DungeonGenerationStep::PlaceFloor { coord: pos });
ctx.last_floor = pos;
}
}
pos
}
fn stamp_room<V: DungeonGenerationVisitor>(
&self,
ctx: &mut WalkContext<V>,
center: GridCoord2D,
target_floor_count: usize,
) {
let half_width = ctx.rng.random_range(1..5); let half_height = ctx.rng.random_range(1..5);
ctx.visitor.on_step(&DungeonGenerationStep::StampRoom {
center,
half_width,
half_height,
});
let min_x = center.x.saturating_sub(half_width);
let max_x = (center.x + half_width).min(ctx.grid.width() - 1);
let min_y = center.y.saturating_sub(half_height);
let max_y = (center.y + half_height).min(ctx.grid.height() - 1);
for y in min_y..=max_y {
for x in min_x..=max_x {
if ctx.grid.floor_count() >= target_floor_count {
return;
}
let coord = GridCoord2D::new(x, y);
if !ctx.grid.is_floor(coord) {
ctx.grid.set(coord, TileType::Floor);
ctx.visitor
.on_step(&DungeonGenerationStep::PlaceFloor { coord });
ctx.last_floor = coord;
}
}
}
}
}
impl DungeonGenerator for DungeonWalkGenerator {
fn new_random() -> Self {
Self::new_random(DungeonType::Caverns)
}
fn new_from_seed(seed: u64) -> Self {
Self::new_from_seed(DungeonType::Caverns, seed)
}
fn generate(&self, width: usize, height: usize, floor_count: usize) -> DungeonGrid {
DungeonWalkGenerator::generate(self, width, height, floor_count)
}
fn generate_steps(
&self,
width: usize,
height: usize,
floor_count: usize,
) -> DungeonGenerationSteps {
DungeonWalkGenerator::generate_steps(self, width, height, floor_count)
}
fn dungeon_type(&self) -> DungeonType {
self.dungeon_type
}
}