use log::debug; use std::convert::From; #[derive(Clone)] pub enum TileType { Empty, Wall, Floor, StairsUp, StairsDown, Character(&'static str), Player, } #[derive(Clone)] pub struct Tile { tile_type: TileType, visible: bool, } impl Tile { pub fn new(tile_type: TileType, visible: bool) -> Self { Tile { tile_type, visible } } pub fn get_type(&self) -> &TileType { &self.tile_type } pub fn is_visible(&self) -> bool { self.visible } pub fn visibility(&mut self, visible: bool) { self.visible = visible; } } impl From<TileType> for Tile { fn from(tile_type: TileType) -> Self { Tile { tile_type, visible: false, // <--- TODO: this set the default beaviour // - true: all tiles of world and entities will be drawn // - false: only draw tiles visible for the player } } } pub struct TileGrid { grid: Vec<Vec<Tile>>, xsize: usize, ysize: usize, } impl TileGrid { pub fn new(xsize: usize, ysize: usize) -> TileGrid { let mut grid = TileGrid { grid: Vec::with_capacity(ysize), xsize, ysize, }; for _ in 0..ysize { let mut subvec = Vec::with_capacity(xsize); for _ in 0..xsize { subvec.push(Tile::new(TileType::Empty, true)); } grid.grid.push(subvec); } grid } pub fn set_tile(&mut self, x: usize, y: usize, tile: Tile) { self.grid[y][x] = tile; } /// Sets a tile if nothing lies underneath it. pub fn set_empty_tile(&mut self, x: usize, y: usize, tile: Tile) { self.set_tile( x, y, match self.grid[y][x].tile_type { TileType::Empty => tile, _ => self.grid[y][x].clone(), }, ) } pub fn raw_data(&self) -> &Vec<Vec<Tile>> { &self.grid } pub fn block_at(&self, x: usize, y: usize) -> &Tile { &self.grid[y + 1][x] } pub fn xsize(&self) -> usize { self.xsize } pub fn ysize(&self) -> usize { self.ysize } fn reveal(&mut self, x: usize, y: usize) { self.grid[y][x].visibility(true); } /// Clears all blocks in a single line of sight ray; stop when encountering a wall /// This uses the bresenham algorithm, see: /// https://en.wikipedia.org/wiki/Bresenham%27s_line_algorithm fn clear_los(&mut self, start: &(usize, usize), end: &(usize, usize)) { let dx = (end.0 as isize - start.0 as isize).abs(); let sx: isize = if start.0 < end.0 { 1 } else { -1 }; let dy = -(end.1 as isize - start.1 as isize).abs(); let sy: isize = if start.1 < end.1 { 1 } else { -1 }; let mut err = dx + dy; let mut x = start.0; let mut y = start.1; // the tile we're standing on needs to be visible. self.reveal(start.0, start.1); loop { if x == end.0 && y == end.1 { self.reveal(x, y); break; } if let TileType::Empty = self.grid[y][x].get_type() { break; } let err2 = 2 * err; if err2 >= dy { err += dy; x = (x as isize + sx).max(0) as usize; } if err2 <= dx { err += dx; y = (y as isize + sy).max(0) as usize; } self.reveal(x, y); } } /// Walk around the perimeter of the line of sight and ray-trace to clear tiles /// up to the nearest obstacle. pub fn clear_fog_of_war(&mut self, center: &(usize, usize), radius: usize) { let a = ( center.0.saturating_sub(radius), center.1.saturating_sub(radius), ); let b = (center.0 + radius, center.1.saturating_sub(radius)); let c = (center.0 + radius, center.1 + radius); let d = (center.0.saturating_sub(radius), center.1 + radius); debug!("LOS: {:?} - {:?} {:?} {:?} {:?}", center, a, b, c, d); // From a to b for x in a.0..=b.0 { // debug!("Clear LOS from {:?} to {:?}", center, (x, a.1)); self.clear_los(center, &(x, a.1)); } // From b to c for y in b.1..=c.1 { // debug!("Clear LOS from {:?} to {:?}", center, (b.0, y)); self.clear_los(center, &(b.0, y)); } // From c to d for x in d.0..=c.0 { // debug!("Clear LOS from {:?} to {:?}", center, (x, c.1)); self.clear_los(center, &(x, c.1)); } // From d to a for y in a.1..=d.1 { // debug!("Clear LOS from {:?} to {:?}", center, (d.0, y)); self.clear_los(center, &(d.0, y)); } } } pub fn tile_to_str(tile: &Tile) -> &str { if tile.is_visible() { match tile.tile_type { TileType::Floor => ".", TileType::Wall => "#", TileType::Empty => " ", TileType::StairsDown => ">", TileType::StairsUp => "<", TileType::Player => "@", TileType::Character(t) => t, } } else { " " } } pub trait Tileable { fn tile(&self, grid: &mut TileGrid) -> Result<(), String>; }