WIP. Added simple rooms

This commit is contained in:
klangner 2020-09-14 20:29:36 +02:00
parent ce6a29d899
commit 0e40877eab
14 changed files with 262 additions and 65 deletions

View File

@ -4,6 +4,7 @@ use mapgen::dungeon::{
MapBuilder,
map::TileType,
cellular_automata::CellularAutomataGen,
random_rooms::RandomRoomsGen,
starting_point::{AreaStartingPosition, XStart, YStart},
cull_unreachable::CullUnreachable,
distant_exit::DistantExit,
@ -29,11 +30,24 @@ pub struct World {
impl World {
pub fn new_cellular_automata(width: u32, height: u32, seed: u32) -> World {
let mut rng = StdRng::seed_from_u64(seed as u64);
let map = MapBuilder::new(Box::new(CellularAutomataGen::new(width as usize, height as usize)))
let map = MapBuilder::new(Box::new(CellularAutomataGen::new()))
.with(AreaStartingPosition::new(XStart::CENTER, YStart::CENTER))
.with(CullUnreachable::new())
.with(DistantExit::new())
.build_map_with_rng(&mut rng);
.build_map_with_rng(width as usize, height as usize, &mut rng);
let tiles = (0..map.tiles.len())
.map(|i| if map.tiles[i] == TileType::Floor {Cell::Floor} else {Cell::Wall})
.collect();
World {
width,
height,
tiles }
}
pub fn new_random_rooms(width: u32, height: u32, seed: u32) -> World {
let mut rng = StdRng::seed_from_u64(seed as u64);
let map = MapBuilder::new(Box::new(RandomRoomsGen::new()))
.build_map_with_rng(width as usize, height as usize, &mut rng);
let tiles = (0..map.tiles.len())
.map(|i| if map.tiles[i] == TileType::Floor {Cell::Floor} else {Cell::Wall})
.collect();

View File

@ -28,7 +28,10 @@ function newCellularAutomata() {
}
function newSimpleRooms() {
console.log("Simple rooms")
var seed = Date.now();
world = World.new_random_rooms(width, height, seed);
requestAnimationFrame(renderLoop);
infoDiv.textContent = "Random Rooms with the seed: " + seed;
}
const renderLoop = () => {

70
src/common/geometry.rs Normal file
View File

@ -0,0 +1,70 @@
//! Support function for 2D geometry
//!
/// Position on the map
#[derive(Default, PartialEq, Copy, Clone, Debug, Eq, Hash)]
pub struct Point {
pub x: usize,
pub y: usize
}
impl Point {
/// Create new point
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()
}
}
/// Rectangle region on the map
#[derive(PartialEq, Copy, Clone)]
pub struct Rect {
pub x1 : i32,
pub x2 : i32,
pub y1 : i32,
pub y2 : i32
}
impl Rect {
pub fn new(x:i32, y: i32, width:i32, height:i32) -> Rect {
Rect{x1:x, y1:y, x2:x+width, y2:y+height}
}
/// Returns true if this overlaps with other
pub fn intersect(&self, other:&Rect) -> bool {
self.x1 <= other.x2 && self.x2 >= other.x1 && self.y1 <= other.y2 && self.y2 >= other.y1
}
pub fn center(&self) -> (i32, i32) {
((self.x1 + self.x2)/2, (self.y1 + self.y2)/2)
}
}
/// ------------------------------------------------------------------------------------------------
/// Module unit tests
/// ------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
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]
fn test_intersect() {
let rect1 = Rect::new(10, 10, 40, 40);
let rect2 = Rect::new(30, 30, 60, 60);
assert!(rect1.intersect(&rect2));
}
}

2
src/common/mod.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod geometry;
pub mod random;

29
src/common/random.rs Normal file
View File

@ -0,0 +1,29 @@
//! Helper function for random number generator
//!
use rand::prelude::*;
/// Generate random number between start and end inclusive on both ends
pub fn random_range(rng: &mut StdRng, start: usize, end: usize) -> usize {
let max = (end - start + 1) as u32;
((rng.next_u32() % max) + start as u32) as usize
}
/// ------------------------------------------------------------------------------------------------
/// Module unit tests
/// ------------------------------------------------------------------------------------------------
#[cfg(test)]
mod tests {
use std::time::{SystemTime, UNIX_EPOCH};
use rand::prelude::*;
use super::*;
#[test]
fn test_range() {
let system_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Can't access system time");
let mut rng = StdRng::seed_from_u64(system_time.as_millis() as u64);
let x = random_range(&mut rng, 5, 8);
assert!(x >= 5 && x <= 8);
}
}

View File

@ -15,8 +15,8 @@
//! };
//!
//! let mut rng = StdRng::seed_from_u64(100);
//! let gen = CellularAutomataGen::new(80, 50);
//! let map = gen.generate_map(&mut rng);
//! let gen = CellularAutomataGen::new();
//! let map = gen.generate_map(80, 50, &mut rng);
//!
//! assert_eq!(map.width, 80);
//! assert_eq!(map.height, 50);
@ -29,29 +29,26 @@ use super::map::{Map, TileType};
/// Map generator and modifier
pub struct CellularAutomataGen {
width: usize,
height: usize
}
pub struct CellularAutomataGen {}
impl MapGenerator for CellularAutomataGen {
fn generate_map(&self, rng : &mut StdRng) -> Map {
self.build(rng)
fn generate_map(&self, width: usize, height: usize, rng : &mut StdRng) -> Map {
self.build(width, height, rng)
}
}
impl CellularAutomataGen {
/// Create generator which will create map with the given dimension.
pub fn new(width: usize, height: usize) -> CellularAutomataGen {
CellularAutomataGen {width, height}
pub fn new() -> CellularAutomataGen {
CellularAutomataGen {}
}
/// Generate map
fn build(&self, rng : &mut StdRng) -> Map {
let mut map = Map::new(self.width, self.height);
fn build(&self, width: usize, height: usize, rng: &mut StdRng) -> Map {
let mut map = Map::new(width, height);
// First we completely randomize the map, setting 55% of it to be floor.
for y in 1..self.height-1 {
for x in 1..self.width-1 {
for y in 1..height-1 {
for x in 1..width-1 {
let roll = rng.next_u32() % 100;
if roll > 55 { map.set_tile(x, y, TileType::Floor) }
else { map.set_tile(x, y, TileType::Wall) }

View File

@ -50,7 +50,8 @@ mod tests {
use rand::prelude::*;
use super::*;
use super::MapModifier;
use crate::dungeon::map::{Point, Map};
use crate::common::geometry::Point;
use crate::dungeon::map::Map;
#[test]
fn test_culling() {

View File

@ -8,9 +8,10 @@
//! Example generator usage:
//! ---
//! use rand::prelude::*;
//! use crate::common::geometry::Point;
//! use mapgen::dungeon::{
//! MapModifier,
//! map::{Map, Point, TileType},
//! map::{Map, TileType},
//! starting_point::{AreaStartingPosition, XStart, YStart}
//! };
//!
@ -97,7 +98,8 @@ impl DijkstraMap {
#[cfg(test)]
mod tests {
use super::*;
use crate::dungeon::map::{Point, Map};
use crate::common::geometry::Point;
use crate::dungeon::map::Map;
#[test]
fn test_culling() {

View File

@ -6,8 +6,9 @@
use std::f32;
use rand::prelude::StdRng;
use super::{MapModifier};
use super::map::{Map, Point};
use crate::common::geometry::Point;
use super::MapModifier;
use super::map::Map;
use super::dijkstra::DijkstraMap;
@ -53,7 +54,8 @@ mod tests {
use rand::prelude::*;
use super::*;
use super::MapModifier;
use crate::dungeon::map::{Point, Map};
use crate::common::geometry::Point;
use crate::dungeon::map::Map;
#[test]
fn test_exit() {

View File

@ -8,30 +8,9 @@
//!
use std::fmt;
use crate::common::geometry::{Point, Rect};
/// Position on the map
#[derive(Default, PartialEq, Copy, Clone, Debug, Eq, Hash)]
pub struct Point {
pub x: usize,
pub y: usize
}
impl Point {
/// Create new point
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()
}
}
/// Possible tile type on the map
#[derive(PartialEq, Copy, Clone, Debug, Eq, Hash)]
pub enum TileType {
Wall, Floor
@ -123,6 +102,16 @@ impl Map {
self.tiles[idx] = tile;
}
}
/// Create room on the map at given location
/// Room is created by setting all tiles in the room to the Floor
pub fn create_room(&mut self, rect: &Rect) {
for x in rect.x1..rect.x2 {
for y in rect.y1..rect.y2 {
self.set_tile(x as usize, y as usize, TileType::Floor);
}
}
}
}
impl fmt::Display for Map {
@ -145,14 +134,6 @@ impl fmt::Display for Map {
mod tests {
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]
fn test_new_map() {
let map = Map::new(10, 10);
@ -198,4 +179,20 @@ mod tests {
let expected_exists = vec![(2, 1, 1.0), (1, 2, 1.0), (2, 2, 1.45)];
assert_eq!(exists, expected_exists);
}
#[test]
fn test_create_room() {
let mut map = Map::new(5, 5);
map.create_room(&Rect::new(1, 1, 3, 3));
for x in 0..map.width {
for y in 0..map.height {
if x == 0 || y == 0 || x == 4 || y == 4 {
assert_eq!(map.at(x, y), TileType::Wall);
} else {
assert_eq!(map.at(x, y), TileType::Floor);
}
}
}
}
}

View File

@ -8,16 +8,17 @@
//!
//! Example
//! ```
//! use mapgen::common::geometry::Point;
//! use mapgen::dungeon::{
//! MapBuilder,
//! map::{Map, Point, TileType},
//! map::{Map, TileType},
//! cellular_automata::CellularAutomataGen,
//! starting_point::{AreaStartingPosition, XStart, YStart},
//! };
//!
//! let map = MapBuilder::new(Box::new(CellularAutomataGen::new(80, 50)))
//! let map = MapBuilder::new(Box::new(CellularAutomataGen::new()))
//! .with(AreaStartingPosition::new(XStart::CENTER, YStart::CENTER))
//! .build_map();
//! .build_map(80, 50);
//!
//! assert_eq!(map.width, 80);
//! assert_eq!(map.height, 50);
@ -29,6 +30,7 @@ pub mod map;
pub mod cellular_automata;
pub mod cull_unreachable;
pub mod distant_exit;
pub mod random_rooms;
pub mod starting_point;
mod dijkstra;
@ -40,7 +42,7 @@ use map::Map;
/// Trait which should be implemented by any map generator which want to be used
/// by MapBuilder
pub trait MapGenerator {
fn generate_map(&self, rng: &mut StdRng) -> Map;
fn generate_map(&self, width: usize, height: usize, rng: &mut StdRng) -> Map;
}
/// Trait which should be implemented by map modifier.
@ -70,15 +72,15 @@ impl MapBuilder {
}
/// Build map using random number seeded with system time
pub fn build_map(&mut self) -> Map {
pub fn build_map(&mut self, width: usize, height: usize) -> Map {
let system_time = SystemTime::now().duration_since(UNIX_EPOCH).expect("Can't access system time");
let mut rng = StdRng::seed_from_u64(system_time.as_millis() as u64);
self.build_map_with_rng(&mut rng)
self.build_map_with_rng(width, height, &mut rng)
}
/// Build map using provided random number generator
pub fn build_map_with_rng(&mut self, rng: &mut StdRng) -> Map {
let mut map = self.generator.generate_map(rng);
pub fn build_map_with_rng(&mut self, width: usize, height: usize, rng: &mut StdRng) -> Map {
let mut map = self.generator.generate_map(width, height, rng);
// Build additional layers in turn
for modifier in self.modifiers.iter() {
@ -101,9 +103,9 @@ mod tests {
#[test]
fn test_ca_map() {
let map = MapBuilder::new(Box::new(CellularAutomataGen::new(80, 50)))
let map = MapBuilder::new(Box::new(CellularAutomataGen::new()))
.with(AreaStartingPosition::new(XStart::CENTER, YStart::CENTER))
.build_map();
.build_map(80, 50);
assert_eq!(map.width, 80);
assert_eq!(map.height, 50);

View File

@ -0,0 +1,75 @@
//! Random rooms map generator.
//!
//! Try to generate rooms of different size to fille the whole map area.
//!
//! Example generator usage:
//! ```
//! use rand::prelude::*;
//! use mapgen::dungeon::{
//! MapGenerator,
//! random_rooms::RandomRoomsGen
//! };
//!
//! let mut rng = StdRng::seed_from_u64(100);
//! let gen = RandomRoomsGen::new();
//! let map = gen.generate_map(80, 50, &mut rng);
//!
//! assert_eq!(map.width, 80);
//! assert_eq!(map.height, 50);
//! ```
//!
use rand::prelude::*;
use super::MapGenerator;
use crate::common::geometry::Rect;
use crate::common::random;
use super::map::{Map};
pub struct RandomRoomsGen {
max_rooms: usize,
min_room_size: usize,
max_room_size: usize,
}
impl MapGenerator for RandomRoomsGen {
fn generate_map(&self, width: usize, height: usize, rng : &mut StdRng) -> Map {
self.build_rooms(width, height, rng)
}
}
impl RandomRoomsGen {
pub fn new() -> RandomRoomsGen {
RandomRoomsGen{
max_rooms: 30,
min_room_size: 6,
max_room_size: 10
}
}
fn build_rooms(&self, width: usize, height: usize, rng : &mut StdRng) -> Map {
let mut map = Map::new(width, height);
let mut rooms : Vec<Rect> = Vec::new();
// Create room dimensions
for _ in 0..self.max_rooms {
let w = random::random_range(rng, self.min_room_size, self.max_room_size);
let h = random::random_range(rng, self.min_room_size, self.max_room_size);
let x = random::random_range(rng, 0, width - w);
let y = random::random_range(rng, 0, height - h);
let new_room = Rect::new(x as i32, y as i32, w as i32, h as i32);
let intersects = rooms.iter().any(|r| new_room.intersect(r));
if !intersects {
rooms.push(new_room);
}
}
// Apply rooms to the map
for room in rooms {
map.create_room(&room);
}
map
}
}

View File

@ -6,9 +6,10 @@
//! Example modifier usage:
//! ```
//! use rand::prelude::*;
//! use mapgen::common::geometry::Point;
//! use mapgen::dungeon::{
//! MapModifier,
//! map::{Map, Point, TileType},
//! map::{Map, TileType},
//! starting_point::{AreaStartingPosition, XStart, YStart}
//! };
//!
@ -24,7 +25,8 @@
use rand::prelude::StdRng;
use super::{MapModifier};
use super::map::{Map, Point, TileType};
use crate::common::geometry::Point;
use super::map::{Map, TileType};
/// Initial x region position

View File

@ -4,4 +4,5 @@
//! * Dungeon maps
//!
pub mod common;
pub mod dungeon;