Added staring position modifier

This commit is contained in:
klangner 2020-09-02 12:01:16 +02:00
parent 51e0d585df
commit 31e503faac
7 changed files with 189 additions and 19 deletions

View File

@ -34,7 +34,7 @@ If you want to check how the maps look like, then:
* [ ] Wave Function Collapse * [ ] Wave Function Collapse
* Map modifiers (filters) * Map modifiers (filters)
* [ ] Area exit point * [ ] Area exit point
* [ ] Area starting point * [x] Area starting point
* [x] Cellular automata * [x] Cellular automata
* [ ] Cull unreachable areas * [ ] Cull unreachable areas
* [ ] Voronoi spawning * [ ] Voronoi spawning

View File

@ -1,6 +1,6 @@
[package] [package]
name = "mapgen-demo" name = "mapgen-demo"
version = "0.1.0" version = "0.1.1"
authors = ["Krzysztof Langner <klangner@gmail.com>"] authors = ["Krzysztof Langner <klangner@gmail.com>"]
edition = "2018" edition = "2018"

View File

@ -1,4 +1,3 @@
use rand::prelude::*;
use amethyst::{ use amethyst::{
assets::{AssetStorage, Loader}, assets::{AssetStorage, Loader},
core::{ core::{
@ -14,6 +13,7 @@ use amethyst::{
sprite::{SpriteSheet, SpriteSheetFormat, SpriteSheetHandle}, sprite::{SpriteSheet, SpriteSheetFormat, SpriteSheetHandle},
types::DefaultBackend, types::DefaultBackend,
RenderFlat2D, RenderToWindow, RenderingBundle, Texture, RenderFlat2D, RenderToWindow, RenderingBundle, Texture,
palette::Srgba,
}, },
tiles::{MortonEncoder, RenderTiles2D, Tile, TileMap}, tiles::{MortonEncoder, RenderTiles2D, Tile, TileMap},
utils::application_root_dir, utils::application_root_dir,
@ -21,9 +21,10 @@ use amethyst::{
winit, winit,
}; };
use mapgen::dungeon::{ use mapgen::dungeon::{
MapGenerator, MapBuilder,
map::{Map, TileType}, map::{Map, Point, TileType},
cellular_automata::CellularAutomataGen, cellular_automata::CellularAutomataGen,
starting_point::{AreaStartingPosition, XStart, YStart},
}; };
@ -33,12 +34,25 @@ struct MapTiles ;
impl Tile for MapTiles { impl Tile for MapTiles {
fn sprite(&self, p: Point3<u32>, world: &World) -> Option<usize> { fn sprite(&self, p: Point3<u32>, world: &World) -> Option<usize> {
let map = world.read_resource::<Map>(); let map = world.read_resource::<Map>();
if map.at(p.x as usize, p.y as usize) == TileType::Wall { let player_pos = Point::new(p.x as usize, p.y as usize);
if map.starting_point == Some(player_pos) {
Some(64)
} else if map.at(p.x as usize, p.y as usize) == TileType::Wall {
Some(35) Some(35)
} else { } else {
Some(46) Some(46)
} }
} }
fn tint(&self, p: Point3<u32>, world: &World) -> Srgba {
let map = world.read_resource::<Map>();
let player_pos = Point::new(p.x as usize, p.y as usize);
if map.starting_point == Some(player_pos) {
Srgba::new(1.0, 1.0, 0.0, 1.0)
} else {
Srgba::new(1.0, 1.0, 1.0, 1.0)
}
}
} }
fn load_tiles_sprite_sheet(world: &mut World, png_path: &str, ron_path: &str) -> SpriteSheetHandle { fn load_tiles_sprite_sheet(world: &mut World, png_path: &str, ron_path: &str) -> SpriteSheetHandle {
@ -67,9 +81,9 @@ fn init_camera(world: &mut World, transform: Transform, camera: Camera) -> Entit
} }
fn init_map(world: &mut World) { fn init_map(world: &mut World) {
let gen = CellularAutomataGen::new(80, 50); let map = MapBuilder::new(Box::new(CellularAutomataGen::new(80, 50)))
let mut rng = StdRng::seed_from_u64(0); .with(AreaStartingPosition::new(XStart::CENTER, YStart::CENTER))
let map = gen.generate_map(&mut rng); .build_map();
world.insert(map); world.insert(map);
} }

View File

@ -23,7 +23,6 @@
//! ``` //! ```
//! //!
use rand::prelude::*; use rand::prelude::*;
use super::{MapGenerator, MapModifier}; use super::{MapGenerator, MapModifier};
use super::map::{Map, TileType}; use super::map::{Map, TileType};

View File

@ -9,14 +9,22 @@
/// Position on the map /// Position on the map
#[derive(PartialEq, Copy, Clone, Debug, Eq, Hash)] #[derive(PartialEq, Copy, Clone, Debug, Eq, Hash)]
pub struct Position { pub struct Point {
x: usize, x: usize,
y: usize y: usize
} }
impl Position { impl Point {
pub fn new(x: usize, y: usize) -> Position { /// Create new point
Position {x, y} pub fn new(x: usize, y: usize) -> Point {
Point {x, y}
}
/// Euclidean distance to a given point
pub fn distance_to(self, point: &Point) -> f32 {
let a = (self.x as f32 - point.x as f32).powf(2.0);
let b = (self.y as f32 - point.y as f32).powf(2.0);
(a + b).sqrt()
} }
} }
@ -32,8 +40,8 @@ pub struct Map {
pub tiles : Vec<TileType>, pub tiles : Vec<TileType>,
pub width : usize, pub width : usize,
pub height : usize, pub height : usize,
pub starting_point: Option<Position>, pub starting_point: Option<Point>,
pub exit_point: Option<Position> pub exit_point: Option<Point>
} }
impl Map { impl Map {
@ -70,6 +78,14 @@ impl Map {
mod tests { mod tests {
use super::*; use super::*;
#[test]
fn test_distance() {
let p1 = Point::new(10, 10);
let p2 = Point::new(14, 7);
let distance = p1.distance_to(&p2);
assert_eq!(distance, 5.0);
}
#[test] #[test]
fn test_new_map() { fn test_new_map() {
let map = Map::new(10, 10); let map = Map::new(10, 10);

View File

@ -6,10 +6,30 @@
//! * MapGenerators are use to create initial map. //! * MapGenerators are use to create initial map.
//! * MapModifiers modify existing map. //! * MapModifiers modify existing map.
//! //!
//! Example
//! ```
//! 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();
//!
//! assert_eq!(map.width, 80);
//! assert_eq!(map.height, 50);
//! assert_eq!(map.starting_point.is_some(), true);
//! ```
//!
pub mod map; pub mod map;
pub mod cellular_automata; pub mod cellular_automata;
pub mod starting_point;
use std::time::{SystemTime, UNIX_EPOCH};
use rand::prelude::*; use rand::prelude::*;
use map::Map; use map::Map;
@ -34,16 +54,19 @@ pub struct MapBuilder {
} }
impl MapBuilder { impl MapBuilder {
/// Create Map Builder with initial map generator
pub fn new(generator : Box<dyn MapGenerator>) -> MapBuilder { pub fn new(generator : Box<dyn MapGenerator>) -> MapBuilder {
let system_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Can't access system time");
MapBuilder { MapBuilder {
generator, generator,
modifiers: Vec::new(), modifiers: Vec::new(),
rng: StdRng::seed_from_u64(0) rng: StdRng::seed_from_u64(system_time.as_secs())
} }
} }
pub fn with(&mut self, modifier : Box<dyn MapModifier>) { pub fn with(&mut self, modifier : Box<dyn MapModifier>) -> &mut MapBuilder {
self.modifiers.push(modifier); self.modifiers.push(modifier);
self
} }
pub fn build_map(&mut self) -> Map { pub fn build_map(&mut self) -> Map {
@ -51,10 +74,31 @@ impl MapBuilder {
// Build additional layers in turn // Build additional layers in turn
for modifier in self.modifiers.iter() { for modifier in self.modifiers.iter() {
modifier.modify_map(&mut self.rng, &mut map); map = modifier.modify_map(&mut self.rng, &map);
} }
map map
} }
} }
/// ------------------------------------------------------------------------------------------------
/// Module unit tests
/// ------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use super::*;
use cellular_automata::CellularAutomataGen;
use starting_point::{AreaStartingPosition, XStart, YStart};
#[test]
fn test_ca_map() {
let map = MapBuilder::new(Box::new(CellularAutomataGen::new(80, 50)))
.with(AreaStartingPosition::new(XStart::CENTER, YStart::CENTER))
.build_map();
assert_eq!(map.width, 80);
assert_eq!(map.height, 50);
assert_eq!(map.starting_point.is_some(), true);
}
}

View File

@ -0,0 +1,97 @@
//! Add starting point to the map
//!
//! This modifier will try to add starting point by finding the floor title closes
//! to the given point.
//!
//! 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 rand::prelude::StdRng;
use super::{MapModifier};
use super::map::{Map, Point, TileType};
/// Initial x region position
pub enum XStart { LEFT, CENTER, RIGHT }
/// Initial y region position
pub enum YStart { TOP, CENTER, BOTTOM }
/// Add starting position to the map
pub struct AreaStartingPosition {
x : XStart,
y : YStart
}
impl MapModifier for AreaStartingPosition {
fn modify_map(&self, _: &mut StdRng, map: &Map) -> Map {
self.build(map)
}
}
impl AreaStartingPosition {
/// Create new modifier with given region
pub fn new(x : XStart, y : YStart) -> Box<AreaStartingPosition> {
Box::new(AreaStartingPosition{
x, y
})
}
fn build(&self, map : &Map) -> Map {
let seed_x;
let seed_y;
match self.x {
XStart::LEFT => seed_x = 1,
XStart::CENTER => seed_x = map.width / 2,
XStart::RIGHT => seed_x = map.width - 2
}
match self.y {
YStart::TOP => seed_y = 1,
YStart::CENTER => seed_y = map.height / 2,
YStart::BOTTOM => seed_y = map.height - 2
}
let mut available_floors : Vec<(usize, f32)> = Vec::new();
for (idx, tiletype) in map.tiles.iter().enumerate() {
if *tiletype == TileType::Floor {
available_floors.push(
(
idx,
Point::new(idx % map.width, idx / map.width)
.distance_to(&Point::new(seed_x, seed_y))
)
);
}
}
if available_floors.is_empty() {
panic!("No valid floors to start on");
}
available_floors.sort_by(|a,b| a.1.partial_cmp(&b.1).unwrap());
let start_x = available_floors[0].0 % map.width;
let start_y = available_floors[0].0 / map.width;
let mut new_map = map.clone();
new_map.starting_point = Some(Point::new(start_x, start_y));
new_map
}
}