here_be_dragons/src/map.rs

338 lines
10 KiB
Rust
Raw Normal View History

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-16 09:42:59 +00:00
use super::geometry::{Point, Rect, usize_abs};
2020-09-03 19:54:24 +00:00
2020-09-01 12:46:31 +00:00
#[derive(PartialEq, Copy, Clone, Debug, Eq, Hash)]
2021-06-28 15:06:47 +00:00
pub struct Tile {
is_blocked: bool,
2021-06-28 15:11:51 +00:00
index: usize,
2020-08-31 09:45:59 +00:00
}
2020-09-16 09:42:59 +00:00
#[derive(PartialEq, Copy, Clone)]
pub enum Symmetry { None, Horizontal, Vertical, Both }
2020-09-01 12:46:31 +00:00
/// Map data
2020-08-31 09:45:59 +00:00
#[derive(Default, Clone)]
pub struct Map {
2021-06-28 15:06:47 +00:00
pub tiles : Vec<Tile>,
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>,
2020-09-14 20:54:39 +00:00
pub exit_point: Option<Point>,
pub rooms: Vec<Rect>,
pub corridors: Vec<Vec<Point>>,
2020-08-31 09:45:59 +00:00
}
2021-06-28 15:06:47 +00:00
impl Tile {
2021-06-28 15:11:51 +00:00
pub fn new(is_blocked: bool, index: usize) -> Tile {
Tile { is_blocked, index}
}
2021-06-28 15:06:47 +00:00
pub fn wall() -> Tile {
2021-06-28 15:11:51 +00:00
Tile::new(true, 0)
2021-06-28 15:06:47 +00:00
}
pub fn floor() -> Tile {
2021-06-28 15:11:51 +00:00
Tile::new(false, 0)
2021-06-28 15:06:47 +00:00
}
pub fn is_walkable(&self) -> bool {
!self.is_blocked
}
pub fn is_blocked(&self) -> bool {
self.is_blocked
}
2021-07-13 17:17:51 +00:00
pub fn index(&self) -> usize {
self.index
}
2021-06-28 15:06:47 +00:00
}
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{
2021-06-28 15:06:47 +00:00
tiles : vec![Tile::wall(); map_tile_count],
2020-08-31 09:45:59 +00:00
width,
2020-09-01 12:46:31 +00:00
height,
starting_point: None,
exit_point: None,
2020-09-14 20:54:39 +00:00
rooms: Vec::new(),
corridors: Vec::new()
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 == ' ' {
2021-06-28 15:06:47 +00:00
map.set_tile(j, i, Tile::floor());
2020-09-03 19:54:24 +00:00
}
}
}
map
}
2020-08-31 20:03:48 +00:00
/// Get TileType at the given location
2021-06-28 15:06:47 +00:00
pub fn at(&self, x: usize, y: usize) -> Tile {
2020-09-08 20:13:51 +00:00
if x >= self.width || y >= self.height {
2021-06-28 15:06:47 +00:00
Tile::wall()
2020-09-08 20:13:51 +00:00
} else {
let idx = (y as usize) * self.width + (x as usize);
self.tiles[idx]
}
2020-08-31 09:45:59 +00:00
}
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 {
2021-06-28 15:06:47 +00:00
self.at(x, y).is_blocked == false
2020-09-03 19:54:24 +00:00
}
2020-08-31 20:03:48 +00:00
/// Modify tile at the given location
2021-06-28 15:06:47 +00:00
pub fn set_tile(&mut self, x: usize, y: usize, tile: Tile) {
2020-09-08 20:13:51 +00:00
if x < self.width && y < self.height {
2020-11-19 19:53:34 +00:00
let idx = self.xy_idx(x as usize, y as usize);
2020-09-08 20:13:51 +00:00
self.tiles[idx] = tile;
}
2020-08-31 20:03:48 +00:00
}
2020-11-19 19:53:34 +00:00
pub fn xy_idx(&self, x: usize, y: usize) -> usize {
y * self.width + x
}
2020-09-14 18:29:36 +00:00
/// Create room on the map at given location
/// Room is created by setting all tiles in the room to the Floor
2020-09-14 20:54:39 +00:00
pub fn add_room(&mut self, rect: Rect) {
2020-09-14 18:29:36 +00:00
for x in rect.x1..rect.x2 {
for y in rect.y1..rect.y2 {
2021-06-28 15:06:47 +00:00
self.set_tile(x as usize, y as usize, Tile::floor());
2020-09-14 18:29:36 +00:00
}
}
2020-09-14 20:54:39 +00:00
self.rooms.push(rect);
}
pub fn add_corridor(&mut self, from: Point, to:Point) {
let mut corridor = Vec::new();
let mut x = from.x;
let mut y = from.y;
while x != to.x || y != to.y {
if x < to.x {
x += 1;
} else if x > to.x {
x -= 1;
} else if y < to.y {
y += 1;
} else if y > to.y {
y -= 1;
}
2021-06-28 15:06:47 +00:00
if self.at(x, y).is_blocked {
2020-09-14 20:54:39 +00:00
corridor.push(Point::new(x, y));
2021-06-28 15:06:47 +00:00
self.set_tile(x, y, Tile::floor());
2020-09-14 20:54:39 +00:00
}
}
2020-09-14 18:29:36 +00:00
}
2020-09-16 09:42:59 +00:00
pub fn paint(&mut self, mode: Symmetry, brush_size: usize, x: usize, y: usize) {
match mode {
Symmetry::None => self.apply_paint(brush_size, x, y),
Symmetry::Horizontal => {
let center_x = self.width / 2;
if x == center_x {
self.apply_paint(brush_size, x, y);
} else {
let dist_x = usize_abs(center_x, x);
self.apply_paint(brush_size, center_x + dist_x, y);
self.apply_paint(brush_size, center_x - dist_x, y);
}
}
Symmetry::Vertical => {
let center_y = self.height / 2;
if y == center_y {
self.apply_paint(brush_size, x, y);
} else {
let dist_y = usize_abs(center_y, y);
self.apply_paint(brush_size, x, center_y + dist_y);
self.apply_paint(brush_size, x, center_y - dist_y);
}
}
Symmetry::Both => {
let center_x = self.width / 2;
let center_y = self.height / 2;
if x == center_x && y == center_y {
self.apply_paint(brush_size, x, y);
} else {
let dist_x = usize_abs(center_x, x);
self.apply_paint(brush_size, center_x + dist_x, y);
self.apply_paint(brush_size, center_x - dist_x, y);
let dist_y = usize_abs(center_y, y);
self.apply_paint(brush_size, x, center_y + dist_y);
self.apply_paint(brush_size, x, center_y - dist_y);
}
}
}
}
fn apply_paint(&mut self, brush_size: usize, x: usize, y: usize) {
match brush_size {
1 => {
2021-06-28 15:06:47 +00:00
self.set_tile(x, y, Tile::floor());
2020-09-16 09:42:59 +00:00
}
_ => {
let half_brush_size = brush_size / 2;
for brush_y in y-half_brush_size .. y+half_brush_size {
for brush_x in x-half_brush_size .. x+half_brush_size {
if brush_x > 1 && brush_x < self.width-1 && brush_y > 1 && brush_y < self.height-1 {
2021-06-28 15:06:47 +00:00
self.set_tile(brush_x, brush_y, Tile::floor());
2020-09-16 09:42:59 +00:00
}
}
}
}
}
}
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)
2021-06-28 15:06:47 +00:00
.map(|x| if self.at(x, y).is_blocked {'#'} else {' '} as u8)
2020-09-03 19:54:24 +00:00
.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::*;
#[test]
fn test_new_map() {
let map = Map::new(10, 10);
for i in 0..10 {
for j in 0..10 {
2021-06-28 15:06:47 +00:00
assert!(map.at(i, j).is_blocked);
2020-08-31 09:45:59 +00:00
}
}
}
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 {
2021-06-28 15:06:47 +00:00
assert!(map.at(i, 0).is_blocked);
assert!(map.at(i, 2).is_blocked);
2020-09-03 19:54:24 +00:00
if i == 0 || i == 9 {
2021-06-28 15:06:47 +00:00
assert!(map.at(i, 1).is_blocked);
2020-09-03 19:54:24 +00:00
} else {
2021-06-28 15:06:47 +00:00
assert!(map.at(i, 1).is_blocked == false);
2020-09-03 19:54:24 +00:00
}
}
}
#[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-09-14 18:29:36 +00:00
#[test]
fn test_create_room() {
let mut map = Map::new(5, 5);
2020-09-14 20:54:39 +00:00
map.add_room(Rect::new(1, 1, 3, 3));
2020-09-14 18:29:36 +00:00
for x in 0..map.width {
for y in 0..map.height {
if x == 0 || y == 0 || x == 4 || y == 4 {
2021-06-28 15:06:47 +00:00
assert!(map.at(x, y).is_blocked);
2020-09-14 18:29:36 +00:00
} else {
2021-06-28 15:06:47 +00:00
assert!(map.at(x, y).is_blocked == false);
2020-09-14 18:29:36 +00:00
}
}
}
}
2020-09-14 20:54:39 +00:00
#[test]
fn test_add_corridor() {
let map_str = "
##########
# # #
##########
";
let mut map = Map::from_string(map_str);
let expected_map_str = "
##########
# #
##########
";
let expected_map = Map::from_string(expected_map_str);
map.add_corridor(Point::new(1, 1), Point::new(8, 1));
assert_eq!(map.tiles, expected_map.tiles);
}
2020-08-31 09:45:59 +00:00
}