2020-09-01 12:46:31 +00:00
|
|
|
//! Map structure contains information about tiles and other elements on the map.
|
|
|
|
//!
|
|
|
|
//! Map is created with generators and then can by modified with MapModifiers.
|
|
|
|
//!
|
|
|
|
//! This structure is not intented to be your map in the game (But can be used as one).
|
|
|
|
//! Rather the information from this map will be copied to the structures required by
|
|
|
|
//! specific game.
|
|
|
|
//!
|
|
|
|
|
2020-09-03 19:54:24 +00:00
|
|
|
use std::fmt;
|
|
|
|
|
|
|
|
|
2020-09-01 12:46:31 +00:00
|
|
|
/// Position on the map
|
|
|
|
#[derive(PartialEq, Copy, Clone, Debug, Eq, Hash)]
|
2020-09-02 10:01:16 +00:00
|
|
|
pub struct Point {
|
2020-09-03 19:54:24 +00:00
|
|
|
pub x: usize,
|
|
|
|
pub y: usize
|
2020-09-01 12:46:31 +00:00
|
|
|
}
|
|
|
|
|
2020-09-02 10:01:16 +00:00
|
|
|
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()
|
2020-09-01 12:46:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Possible tile type on the map
|
|
|
|
#[derive(PartialEq, Copy, Clone, Debug, Eq, Hash)]
|
2020-08-31 09:45:59 +00:00
|
|
|
pub enum TileType {
|
|
|
|
Wall, Floor
|
|
|
|
}
|
|
|
|
|
2020-09-01 12:46:31 +00:00
|
|
|
/// Map data
|
2020-08-31 09:45:59 +00:00
|
|
|
#[derive(Default, Clone)]
|
|
|
|
pub struct Map {
|
|
|
|
pub tiles : Vec<TileType>,
|
2020-08-31 12:13:52 +00:00
|
|
|
pub width : usize,
|
|
|
|
pub height : usize,
|
2020-09-02 10:01:16 +00:00
|
|
|
pub starting_point: Option<Point>,
|
|
|
|
pub exit_point: Option<Point>
|
2020-08-31 09:45:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Map {
|
|
|
|
|
|
|
|
/// Generates an empty map, consisting entirely of solid walls
|
2020-08-31 12:13:52 +00:00
|
|
|
pub fn new(width: usize, height: usize) -> Map {
|
|
|
|
let map_tile_count = width*height;
|
2020-08-31 09:45:59 +00:00
|
|
|
Map{
|
|
|
|
tiles : vec![TileType::Wall; map_tile_count],
|
|
|
|
width,
|
2020-09-01 12:46:31 +00:00
|
|
|
height,
|
|
|
|
starting_point: None,
|
|
|
|
exit_point: None,
|
2020-08-31 09:45:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-03 19:54:24 +00:00
|
|
|
/// 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
|
|
|
|
}
|
|
|
|
|
2020-08-31 20:03:48 +00:00
|
|
|
/// Get TileType at the given location
|
2020-08-31 12:13:52 +00:00
|
|
|
pub fn at(&self, x: usize, y: usize) -> TileType {
|
|
|
|
let idx = y * self.width + x;
|
2020-08-31 09:45:59 +00:00
|
|
|
self.tiles[idx]
|
|
|
|
}
|
|
|
|
|
2020-09-03 19:54:24 +00:00
|
|
|
/// 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
|
|
|
|
}
|
|
|
|
|
2020-08-31 20:03:48 +00:00
|
|
|
/// Modify tile at the given location
|
|
|
|
pub fn set_tile(&mut self, x: usize, y: usize, tile: TileType) {
|
|
|
|
let idx = y * self.width + x;
|
|
|
|
self.tiles[idx] = tile;
|
|
|
|
}
|
2020-08-31 09:45:59 +00:00
|
|
|
}
|
|
|
|
|
2020-09-03 19:54:24 +00:00
|
|
|
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(())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-31 09:45:59 +00:00
|
|
|
/// ------------------------------------------------------------------------------------------------
|
|
|
|
/// Module unit tests
|
|
|
|
/// ------------------------------------------------------------------------------------------------
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use super::*;
|
|
|
|
|
2020-09-02 10:01:16 +00:00
|
|
|
#[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);
|
|
|
|
}
|
|
|
|
|
2020-08-31 09:45:59 +00:00
|
|
|
#[test]
|
|
|
|
fn test_new_map() {
|
|
|
|
let map = Map::new(10, 10);
|
|
|
|
for i in 0..10 {
|
|
|
|
for j in 0..10 {
|
|
|
|
assert_eq!(map.at(i, j), TileType::Wall);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2020-09-03 19:54:24 +00:00
|
|
|
|
|
|
|
#[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);
|
|
|
|
}
|
2020-08-31 09:45:59 +00:00
|
|
|
}
|