Remove unreachable areas
This commit is contained in:
parent
24bc843e00
commit
5c0a48953a
17
README.md
17
README.md
|
@ -36,7 +36,7 @@ If you want to check how the maps look like, then:
|
||||||
* [ ] Area exit point
|
* [ ] Area exit point
|
||||||
* [x] Area starting point
|
* [x] Area starting point
|
||||||
* [x] Cellular automata
|
* [x] Cellular automata
|
||||||
* [ ] Cull unreachable areas
|
* [x] Cull unreachable areas
|
||||||
* [ ] Voronoi spawning
|
* [ ] Voronoi spawning
|
||||||
|
|
||||||
|
|
||||||
|
@ -61,6 +61,21 @@ let gen = CellularAutomataGen::new(80, 50);
|
||||||
let map = gen.generate_map(&mut rng)
|
let map = gen.generate_map(&mut rng)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Use MapBuilder for chaining map generator and modifiers
|
||||||
|
|
||||||
|
```rust
|
||||||
|
use mapgen::dungeon::{
|
||||||
|
MapBuilder,
|
||||||
|
map::{Map, Point, TileType},
|
||||||
|
cellular_automata::CellularAutomataGen,
|
||||||
|
starting_point::{AreaStartingPosition, XStart, YStart},
|
||||||
|
};
|
||||||
|
|
||||||
|
let map = MapBuilder::new(Box::new(CellularAutomataGen::new(80, 50)))
|
||||||
|
.with(AreaStartingPosition::new(XStart::CENTER, YStart::CENTER))
|
||||||
|
.build_map();
|
||||||
|
```
|
||||||
|
|
||||||
For more information check the [doc](https://docs.rs/mapgen)
|
For more information check the [doc](https://docs.rs/mapgen)
|
||||||
|
|
||||||
# License
|
# License
|
||||||
|
|
|
@ -25,6 +25,7 @@ use mapgen::dungeon::{
|
||||||
map::{Map, Point, TileType},
|
map::{Map, Point, TileType},
|
||||||
cellular_automata::CellularAutomataGen,
|
cellular_automata::CellularAutomataGen,
|
||||||
starting_point::{AreaStartingPosition, XStart, YStart},
|
starting_point::{AreaStartingPosition, XStart, YStart},
|
||||||
|
cull_unreachable::CullUnreachable,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
@ -83,6 +84,7 @@ fn init_camera(world: &mut World, transform: Transform, camera: Camera) -> Entit
|
||||||
fn init_map(world: &mut World) {
|
fn init_map(world: &mut World) {
|
||||||
let map = MapBuilder::new(Box::new(CellularAutomataGen::new(80, 50)))
|
let map = MapBuilder::new(Box::new(CellularAutomataGen::new(80, 50)))
|
||||||
.with(AreaStartingPosition::new(XStart::CENTER, YStart::CENTER))
|
.with(AreaStartingPosition::new(XStart::CENTER, YStart::CENTER))
|
||||||
|
.with(CullUnreachable::new())
|
||||||
.build_map();
|
.build_map();
|
||||||
world.insert(map);
|
world.insert(map);
|
||||||
}
|
}
|
||||||
|
|
71
src/dungeon/cull_unreachable.rs
Normal file
71
src/dungeon/cull_unreachable.rs
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
use rand::prelude::StdRng;
|
||||||
|
use super::MapModifier;
|
||||||
|
use super::map::{Map, TileType};
|
||||||
|
use super::dijkstra::DijkstraMap;
|
||||||
|
|
||||||
|
|
||||||
|
pub struct CullUnreachable {}
|
||||||
|
|
||||||
|
impl MapModifier for CullUnreachable {
|
||||||
|
fn modify_map(&self, _: &mut StdRng, map: &Map) -> Map {
|
||||||
|
self.build(map)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CullUnreachable {
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub fn new() -> Box<CullUnreachable> {
|
||||||
|
Box::new(CullUnreachable{})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn build(&self, map: &Map) -> Map {
|
||||||
|
let mut new_map = map.clone();
|
||||||
|
|
||||||
|
let dijkstra_map = DijkstraMap::new(map);
|
||||||
|
for (i, tile) in new_map.tiles.iter_mut().enumerate() {
|
||||||
|
if *tile == TileType::Floor {
|
||||||
|
let distance_to_start = dijkstra_map.tiles[i];
|
||||||
|
// We can't get to this tile - so we'll make it a wall
|
||||||
|
if distance_to_start == std::f32::MAX {
|
||||||
|
*tile = TileType::Wall;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
new_map
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ------------------------------------------------------------------------------------------------
|
||||||
|
/// Module unit tests
|
||||||
|
/// ------------------------------------------------------------------------------------------------
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use rand::prelude::*;
|
||||||
|
use super::*;
|
||||||
|
use super::MapModifier;
|
||||||
|
use crate::dungeon::map::{Point, Map};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_culling() {
|
||||||
|
let map_str = "
|
||||||
|
##########
|
||||||
|
# # #
|
||||||
|
##########
|
||||||
|
";
|
||||||
|
let mut map = Map::from_string(map_str);
|
||||||
|
map.starting_point = Some(Point::new(9, 1));
|
||||||
|
let expected_map_str = "
|
||||||
|
##########
|
||||||
|
#### #
|
||||||
|
##########
|
||||||
|
";
|
||||||
|
let expected_map = Map::from_string(expected_map_str);
|
||||||
|
|
||||||
|
|
||||||
|
let modifier = CullUnreachable::new();
|
||||||
|
let mut rng = StdRng::seed_from_u64(0);
|
||||||
|
let new_map = modifier.modify_map(&mut rng, &map);
|
||||||
|
|
||||||
|
assert_eq!(new_map.tiles, expected_map.tiles);
|
||||||
|
}
|
||||||
|
}
|
271
src/dungeon/dijkstra copy.rs
Normal file
271
src/dungeon/dijkstra copy.rs
Normal file
|
@ -0,0 +1,271 @@
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::f32::MAX;
|
||||||
|
|
||||||
|
/// Representation of a Dijkstra flow map.
|
||||||
|
/// map is a vector of floats, having a size equal to size_x * size_y (one per tile).
|
||||||
|
/// size_x and size_y are stored for overflow avoidance.
|
||||||
|
/// max_depth is the maximum number of iterations this search shall support.
|
||||||
|
pub struct DijkstraMap {
|
||||||
|
pub map: Vec<f32>,
|
||||||
|
size_x: usize,
|
||||||
|
size_y: usize,
|
||||||
|
max_depth: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DijkstraMap {
|
||||||
|
/// Construct a new Dijkstra map, ready to run. You must specify the map size, and link to an implementation
|
||||||
|
/// of a BaseMap trait that can generate exits lists. It then builds the map, giving you a result.
|
||||||
|
pub fn new<T>(
|
||||||
|
size_x: T,
|
||||||
|
size_y: T,
|
||||||
|
starts: &[usize],
|
||||||
|
map: &dyn BaseMap,
|
||||||
|
max_depth: f32,
|
||||||
|
) -> DijkstraMap
|
||||||
|
where
|
||||||
|
T: TryInto<usize>,
|
||||||
|
{
|
||||||
|
let sz_x: usize = size_x.try_into().ok().unwrap();
|
||||||
|
let sz_y: usize = size_y.try_into().ok().unwrap();
|
||||||
|
let result: Vec<f32> = vec![MAX; sz_x * sz_y];
|
||||||
|
let mut d = DijkstraMap {
|
||||||
|
map: result,
|
||||||
|
size_x: sz_x,
|
||||||
|
size_y: sz_y,
|
||||||
|
max_depth,
|
||||||
|
};
|
||||||
|
DijkstraMap::build(&mut d, starts, map);
|
||||||
|
d
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates an empty Dijkstra map node.
|
||||||
|
pub fn new_empty<T>(size_x: T, size_y: T, max_depth: f32) -> DijkstraMap
|
||||||
|
where
|
||||||
|
T: TryInto<usize>,
|
||||||
|
{
|
||||||
|
let sz_x: usize = size_x.try_into().ok().unwrap();
|
||||||
|
let sz_y: usize = size_y.try_into().ok().unwrap();
|
||||||
|
let result: Vec<f32> = vec![MAX; sz_x * sz_y];
|
||||||
|
DijkstraMap {
|
||||||
|
map: result,
|
||||||
|
size_x: sz_x,
|
||||||
|
size_y: sz_y,
|
||||||
|
max_depth,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Clears the Dijkstra map. Uses a parallel for each for performance.
|
||||||
|
#[cfg(feature = "threaded")]
|
||||||
|
pub fn clear(dm: &mut DijkstraMap) {
|
||||||
|
dm.map.par_iter_mut().for_each(|x| *x = MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "threaded"))]
|
||||||
|
pub fn clear(dm: &mut DijkstraMap) {
|
||||||
|
dm.map.iter_mut().for_each(|x| *x = MAX);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "threaded")]
|
||||||
|
fn build_helper(dm: &mut DijkstraMap, starts: &[usize], map: &dyn BaseMap) -> RunThreaded {
|
||||||
|
if starts.len() >= THREADED_REQUIRED_STARTS {
|
||||||
|
DijkstraMap::build_parallel(dm, starts, map);
|
||||||
|
return RunThreaded::True;
|
||||||
|
}
|
||||||
|
RunThreaded::False
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "threaded"))]
|
||||||
|
fn build_helper(_dm: &mut DijkstraMap, _starts: &[usize], _map: &dyn BaseMap) -> RunThreaded {
|
||||||
|
RunThreaded::False
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds the Dijkstra map: iterate from each starting point, to each exit provided by BaseMap's
|
||||||
|
/// exits implementation. Each step adds cost to the current depth, and is discarded if the new
|
||||||
|
/// depth is further than the current depth.
|
||||||
|
/// WARNING: Will give incorrect results when used with non-uniform exit costs. Much slower
|
||||||
|
/// algorithm required to support that.
|
||||||
|
/// Automatically branches to a parallel version if you provide more than 4 starting points
|
||||||
|
pub fn build(dm: &mut DijkstraMap, starts: &[usize], map: &dyn BaseMap) {
|
||||||
|
let threaded = DijkstraMap::build_helper(dm, starts, map);
|
||||||
|
if threaded == RunThreaded::True { return; }
|
||||||
|
let mapsize: usize = (dm.size_x * dm.size_y) as usize;
|
||||||
|
let mut open_list: VecDeque<(usize, f32)> = VecDeque::with_capacity(mapsize);
|
||||||
|
|
||||||
|
for start in starts {
|
||||||
|
open_list.push_back((*start, 0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some((tile_idx, depth)) = open_list.pop_front() {
|
||||||
|
let exits = map.get_available_exits(tile_idx);
|
||||||
|
for (new_idx, add_depth) in exits {
|
||||||
|
let new_depth = depth + add_depth;
|
||||||
|
let prev_depth = dm.map[new_idx];
|
||||||
|
if new_depth >= prev_depth { continue; }
|
||||||
|
if new_depth >= dm.max_depth { continue; }
|
||||||
|
dm.map[new_idx] = new_depth;
|
||||||
|
open_list.push_back((new_idx, new_depth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Implementation of Parallel Dijkstra.
|
||||||
|
#[cfg(feature = "threaded")]
|
||||||
|
fn build_parallel(dm: &mut DijkstraMap, starts: &[usize], map: &dyn BaseMap) {
|
||||||
|
let mapsize: usize = (dm.size_x * dm.size_y) as usize;
|
||||||
|
let mut layers: Vec<ParallelDm> = Vec::with_capacity(starts.len());
|
||||||
|
for start_chunk in starts.chunks(rayon::current_num_threads()) {
|
||||||
|
let mut layer = ParallelDm {
|
||||||
|
map: vec![MAX; mapsize],
|
||||||
|
max_depth: dm.max_depth,
|
||||||
|
starts: Vec::new(),
|
||||||
|
};
|
||||||
|
layer
|
||||||
|
.starts
|
||||||
|
.extend(start_chunk.iter().copied().map(|x| x as usize));
|
||||||
|
layers.push(layer);
|
||||||
|
}
|
||||||
|
|
||||||
|
let exits: Vec<SmallVec<[(usize, f32); 10]>> = (0..mapsize)
|
||||||
|
.map(|idx| map.get_available_exits(idx))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
// Run each map in parallel
|
||||||
|
layers.par_iter_mut().for_each(|l| {
|
||||||
|
let mut open_list: VecDeque<(usize, f32)> = VecDeque::with_capacity(mapsize);
|
||||||
|
|
||||||
|
for start in l.starts.iter().copied() {
|
||||||
|
open_list.push_back((start, 0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some((tile_idx, depth)) = open_list.pop_front() {
|
||||||
|
let exits = &exits[tile_idx];
|
||||||
|
for (new_idx, add_depth) in exits {
|
||||||
|
let new_idx = *new_idx;
|
||||||
|
let new_depth = depth + add_depth;
|
||||||
|
let prev_depth = l.map[new_idx];
|
||||||
|
if new_depth >= prev_depth { continue; }
|
||||||
|
if new_depth >= l.max_depth { continue; }
|
||||||
|
l.map[new_idx] = new_depth;
|
||||||
|
open_list.push_back((new_idx, new_depth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
// Recombine down to a single result
|
||||||
|
for l in layers {
|
||||||
|
for i in 0..mapsize {
|
||||||
|
dm.map[i] = f32::min(dm.map[i], l.map[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper for traversing maps as path-finding. Provides the index of the lowest available
|
||||||
|
/// exit from the specified position index, or None if there isn't one.
|
||||||
|
/// You would use this for pathing TOWARDS a starting node.
|
||||||
|
#[cfg(feature = "threaded")]
|
||||||
|
pub fn find_lowest_exit(dm: &DijkstraMap, position: usize, map: &dyn BaseMap) -> Option<usize> {
|
||||||
|
let mut exits = map.get_available_exits(position);
|
||||||
|
|
||||||
|
if exits.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
exits.par_sort_by(|a, b| {
|
||||||
|
dm.map[a.0 as usize]
|
||||||
|
.partial_cmp(&dm.map[b.0 as usize])
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(exits[0].0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "threaded"))]
|
||||||
|
pub fn find_lowest_exit(dm: &DijkstraMap, position: usize, map: &dyn BaseMap) -> Option<usize> {
|
||||||
|
let mut exits = map.get_available_exits(position);
|
||||||
|
|
||||||
|
if exits.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
exits.sort_by(|a, b| {
|
||||||
|
dm.map[a.0 as usize]
|
||||||
|
.partial_cmp(&dm.map[b.0 as usize])
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(exits[0].0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper for traversing maps as path-finding. Provides the index of the highest available
|
||||||
|
/// exit from the specified position index, or None if there isn't one.
|
||||||
|
/// You would use this for pathing AWAY from a starting node, for example if you are running
|
||||||
|
/// away.
|
||||||
|
#[cfg(feature = "threaded")]
|
||||||
|
pub fn find_highest_exit(
|
||||||
|
dm: &DijkstraMap,
|
||||||
|
position: usize,
|
||||||
|
map: &dyn BaseMap,
|
||||||
|
) -> Option<usize> {
|
||||||
|
let mut exits = map.get_available_exits(position);
|
||||||
|
|
||||||
|
if exits.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
exits.par_sort_by(|a, b| {
|
||||||
|
dm.map[b.0 as usize]
|
||||||
|
.partial_cmp(&dm.map[a.0 as usize])
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(exits[0].0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "threaded"))]
|
||||||
|
pub fn find_highest_exit(
|
||||||
|
dm: &DijkstraMap,
|
||||||
|
position: usize,
|
||||||
|
map: &dyn BaseMap,
|
||||||
|
) -> Option<usize> {
|
||||||
|
let mut exits = map.get_available_exits(position);
|
||||||
|
|
||||||
|
if exits.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
exits.sort_by(|a, b| {
|
||||||
|
dm.map[b.0 as usize]
|
||||||
|
.partial_cmp(&dm.map[a.0 as usize])
|
||||||
|
.unwrap()
|
||||||
|
});
|
||||||
|
|
||||||
|
Some(exits[0].0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use crate::prelude::*;
|
||||||
|
use bracket_algorithm_traits::prelude::*;
|
||||||
|
// 1 by 3 stripe of tiles
|
||||||
|
struct MiniMap;
|
||||||
|
impl BaseMap for MiniMap {
|
||||||
|
fn get_available_exits(&self, idx: usize) -> SmallVec<[(usize, f32); 10]> {
|
||||||
|
match idx {
|
||||||
|
0 => smallvec![(1, 1.)],
|
||||||
|
2 => smallvec![(1, 1.)],
|
||||||
|
_ => smallvec![(idx - 1, 1.), (idx + 1, 2.)],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_highest_exit() {
|
||||||
|
let map = MiniMap {};
|
||||||
|
let exits_map = DijkstraMap::new(3, 1, &[0], &map, 10.);
|
||||||
|
let target = DijkstraMap::find_highest_exit(&exits_map, 0, &map);
|
||||||
|
assert_eq!(target, Some(1));
|
||||||
|
let target = DijkstraMap::find_highest_exit(&exits_map, 1, &map);
|
||||||
|
assert_eq!(target, Some(2));
|
||||||
|
}
|
||||||
|
}
|
126
src/dungeon/dijkstra.rs
Normal file
126
src/dungeon/dijkstra.rs
Normal file
|
@ -0,0 +1,126 @@
|
||||||
|
//! Calculate Dijkstra influence map
|
||||||
|
//!
|
||||||
|
//! http://www.roguebasin.com/index.php?title=The_Incredible_Power_of_Dijkstra_Maps
|
||||||
|
//!
|
||||||
|
//! This algorithm calculates cost (distance) of moving from the given starting point
|
||||||
|
//! to the each point on the map. Point which are not reachable will get f32::MAX value.
|
||||||
|
//!
|
||||||
|
//! Example generator usage:
|
||||||
|
//! ---
|
||||||
|
//! use rand::prelude::*;
|
||||||
|
//! use mapgen::dungeon::{
|
||||||
|
//! MapModifier,
|
||||||
|
//! map::{Map, Point, TileType},
|
||||||
|
//! starting_point::{AreaStartingPosition, XStart, YStart}
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! let mut rng = StdRng::seed_from_u64(100);
|
||||||
|
//! let mut map = Map::new(80, 50);
|
||||||
|
//! map.set_tile(10, 10, TileType::Floor);
|
||||||
|
//! let modifier = AreaStartingPosition::new(XStart::LEFT, YStart::TOP);
|
||||||
|
//! let new_map = modifier.modify_map(&mut rng, &map);
|
||||||
|
//!
|
||||||
|
//! assert_eq!(new_map.starting_point, Some(Point::new(10, 10)));
|
||||||
|
//! ---
|
||||||
|
//!
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::f32::MAX;
|
||||||
|
use super::map::Map;
|
||||||
|
|
||||||
|
|
||||||
|
/// Representation of a Dijkstra flow map.
|
||||||
|
/// map is a vector of floats, having a size equal to size_x * size_y (one per tile).
|
||||||
|
/// size_x and size_y are stored for overflow avoidance.
|
||||||
|
/// max_depth is the maximum number of iterations this search shall support.
|
||||||
|
pub struct DijkstraMap {
|
||||||
|
pub tiles: Vec<f32>,
|
||||||
|
size_x: usize,
|
||||||
|
size_y: usize,
|
||||||
|
max_depth: f32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DijkstraMap {
|
||||||
|
/// Construct a new Dijkstra map, ready to run. You must specify the map size, and link to an implementation
|
||||||
|
/// of a BaseMap trait that can generate exits lists. It then builds the map, giving you a result.
|
||||||
|
pub fn new(map: &Map) -> DijkstraMap {
|
||||||
|
let len = map.width * map.height;
|
||||||
|
let tiles = vec![MAX; len];
|
||||||
|
let mut d = DijkstraMap {
|
||||||
|
tiles: tiles,
|
||||||
|
size_x: map.width,
|
||||||
|
size_y: map.height,
|
||||||
|
max_depth: len as f32,
|
||||||
|
};
|
||||||
|
d.build(map);
|
||||||
|
d
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds the Dijkstra map: iterate from each starting point, to each exit provided by BaseMap's
|
||||||
|
/// exits implementation. Each step adds cost to the current depth, and is discarded if the new
|
||||||
|
/// depth is further than the current depth.
|
||||||
|
/// WARNING: Will give incorrect results when used with non-uniform exit costs. Much slower
|
||||||
|
/// algorithm required to support that.
|
||||||
|
/// Automatically branches to a parallel version if you provide more than 4 starting points
|
||||||
|
fn build(self: &mut DijkstraMap, map: &Map) {
|
||||||
|
let mapsize: usize = (self.size_x * self.size_y) as usize;
|
||||||
|
let mut open_list: VecDeque<((usize, usize), f32)> = VecDeque::with_capacity(mapsize);
|
||||||
|
|
||||||
|
if let Some(pos) = map.starting_point {
|
||||||
|
open_list.push_back(((pos.x, pos.y), 0.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
while let Some(((x, y), depth)) = open_list.pop_front() {
|
||||||
|
let exits = map.get_available_exits(x, y);
|
||||||
|
for (x, y, add_depth) in exits {
|
||||||
|
let idx = self.xy_idx(x, y);
|
||||||
|
let new_depth = depth + add_depth;
|
||||||
|
let prev_depth = self.tiles[idx];
|
||||||
|
if new_depth >= prev_depth { continue; }
|
||||||
|
if new_depth >= self.max_depth { continue; }
|
||||||
|
self.tiles[idx] = depth;
|
||||||
|
open_list.push_back(((x, y), new_depth));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn xy_idx(&self, x: usize, y: usize) -> usize {
|
||||||
|
(y * self.size_x ) + x
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/// ------------------------------------------------------------------------------------------------
|
||||||
|
/// Module unit tests
|
||||||
|
/// ------------------------------------------------------------------------------------------------
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::dungeon::map::{Point, Map};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_culling() {
|
||||||
|
let map_str = "
|
||||||
|
##########
|
||||||
|
# # #
|
||||||
|
##########
|
||||||
|
";
|
||||||
|
let mut map = Map::from_string(map_str);
|
||||||
|
map.starting_point = Some(Point::new(9, 1));
|
||||||
|
let dm = DijkstraMap::new(&map);
|
||||||
|
|
||||||
|
assert_eq!(dm.size_x, 10);
|
||||||
|
assert_eq!(dm.size_y, 3);
|
||||||
|
for i in 0..10 {
|
||||||
|
assert_eq!(dm.tiles[i], MAX);
|
||||||
|
assert_eq!(dm.tiles[2*dm.size_x + i], MAX);
|
||||||
|
let idx = dm.size_x + i;
|
||||||
|
if i < 3 || i == 9 {
|
||||||
|
assert_eq!(dm.tiles[idx], MAX);
|
||||||
|
} else {
|
||||||
|
assert_eq!(dm.tiles[idx], (8 - i) as f32);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,11 +7,14 @@
|
||||||
//! specific game.
|
//! specific game.
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
|
||||||
/// Position on the map
|
/// Position on the map
|
||||||
#[derive(PartialEq, Copy, Clone, Debug, Eq, Hash)]
|
#[derive(PartialEq, Copy, Clone, Debug, Eq, Hash)]
|
||||||
pub struct Point {
|
pub struct Point {
|
||||||
x: usize,
|
pub x: usize,
|
||||||
y: usize
|
pub y: usize
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Point {
|
impl Point {
|
||||||
|
@ -58,12 +61,58 @@ impl Map {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Create map from given string
|
||||||
|
pub fn from_string(map_string: &str) -> Map {
|
||||||
|
let lines: Vec<&str> = map_string.split("\n")
|
||||||
|
.map(|l| l.trim())
|
||||||
|
.filter(|l| l.len() > 0)
|
||||||
|
.collect();
|
||||||
|
let cols = lines.iter().map(|l| l.len()).max().get_or_insert(1).to_owned();
|
||||||
|
let rows = lines.len();
|
||||||
|
let mut map = Map::new(cols, rows);
|
||||||
|
|
||||||
|
for i in 0..rows {
|
||||||
|
let line = lines[i].as_bytes();
|
||||||
|
for j in 0..line.len() {
|
||||||
|
if line[j] as char == ' ' {
|
||||||
|
map.set_tile(j, i, TileType::Floor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
map
|
||||||
|
}
|
||||||
|
|
||||||
/// Get TileType at the given location
|
/// 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]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get available exists from the given tile
|
||||||
|
pub fn get_available_exits(&self, x: usize, y: usize) -> Vec<(usize, usize, f32)> {
|
||||||
|
let mut exits = Vec::new();
|
||||||
|
|
||||||
|
// Cardinal directions
|
||||||
|
if self.is_exit_valid(x-1, y) { exits.push((x-1, y, 1.0)) };
|
||||||
|
if self.is_exit_valid(x+1, y) { exits.push((x+1, y, 1.0)) };
|
||||||
|
if self.is_exit_valid(x, y-1) { exits.push((x, y-1, 1.0)) };
|
||||||
|
if self.is_exit_valid(x, y+1) { exits.push((x, y+1, 1.0)) };
|
||||||
|
|
||||||
|
// Diagonals
|
||||||
|
if self.is_exit_valid(x-1, y-1) { exits.push((x-1, y-1, 1.45)); }
|
||||||
|
if self.is_exit_valid(x+1, y-1) { exits.push((x+1, y-1, 1.45)); }
|
||||||
|
if self.is_exit_valid(x-1, y+1) { exits.push((x-1, y+1, 1.45)); }
|
||||||
|
if self.is_exit_valid(x+1, y+1) { exits.push((x+1, y+1, 1.45)); }
|
||||||
|
|
||||||
|
exits
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if given tile can be accessed
|
||||||
|
fn is_exit_valid(&self, x:usize, y:usize) -> bool {
|
||||||
|
if x < 1 || x > self.width-1 || y < 1 || y > self.height-1 { return false; }
|
||||||
|
self.at(x, y) == TileType::Floor
|
||||||
|
}
|
||||||
|
|
||||||
/// Modify tile at the given location
|
/// Modify tile at the given location
|
||||||
pub fn set_tile(&mut self, x: usize, y: usize, tile: TileType) {
|
pub fn set_tile(&mut self, x: usize, y: usize, tile: TileType) {
|
||||||
let idx = y * self.width + x;
|
let idx = y * self.width + x;
|
||||||
|
@ -71,6 +120,19 @@ impl Map {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Map {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
for y in 0..self.height {
|
||||||
|
let bytes: Vec<u8> = (0..self.width)
|
||||||
|
.map(|x| if self.at(x, y) == TileType::Wall {'#'} else {' '} as u8)
|
||||||
|
.collect();
|
||||||
|
let line = String::from_utf8(bytes).expect("Can't convert map to string");
|
||||||
|
let _ = write!(f, "{}\n", line);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// ------------------------------------------------------------------------------------------------
|
/// ------------------------------------------------------------------------------------------------
|
||||||
/// Module unit tests
|
/// Module unit tests
|
||||||
/// ------------------------------------------------------------------------------------------------
|
/// ------------------------------------------------------------------------------------------------
|
||||||
|
@ -95,4 +157,40 @@ mod tests {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_from_string() {
|
||||||
|
let map_str = "
|
||||||
|
##########
|
||||||
|
# #
|
||||||
|
##########
|
||||||
|
";
|
||||||
|
let map = Map::from_string(map_str);
|
||||||
|
|
||||||
|
assert_eq!(map.width, 10);
|
||||||
|
assert_eq!(map.height, 3);
|
||||||
|
for i in 0..10 {
|
||||||
|
assert_eq!(map.at(i, 0), TileType::Wall);
|
||||||
|
assert_eq!(map.at(i, 2), TileType::Wall);
|
||||||
|
if i == 0 || i == 9 {
|
||||||
|
assert_eq!(map.at(i, 1), TileType::Wall);
|
||||||
|
} else {
|
||||||
|
assert_eq!(map.at(i, 1), TileType::Floor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_exists() {
|
||||||
|
let map_str = "
|
||||||
|
##########
|
||||||
|
# #
|
||||||
|
# #
|
||||||
|
##########
|
||||||
|
";
|
||||||
|
let map = Map::from_string(map_str);
|
||||||
|
let exists = map.get_available_exits(1, 1);
|
||||||
|
let expected_exists = vec![(2, 1, 1.0), (1, 2, 1.0), (2, 2, 1.45)];
|
||||||
|
assert_eq!(exists, expected_exists);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -27,7 +27,9 @@
|
||||||
|
|
||||||
pub mod map;
|
pub mod map;
|
||||||
pub mod cellular_automata;
|
pub mod cellular_automata;
|
||||||
|
pub mod cull_unreachable;
|
||||||
pub mod starting_point;
|
pub mod starting_point;
|
||||||
|
mod dijkstra;
|
||||||
|
|
||||||
use std::time::{SystemTime, UNIX_EPOCH};
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
use rand::prelude::*;
|
use rand::prelude::*;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user