diff --git a/src/app/editor.rs b/src/app/editor.rs index ef69983..0a942ce 100644 --- a/src/app/editor.rs +++ b/src/app/editor.rs @@ -43,8 +43,8 @@ fn main() { let x = ((x - left_panel_size.x as i32) / SPRITE_SIZE_I32) as usize; let y = ((y - top_panel_size.y as i32) / SPRITE_SIZE_I32) as usize; - if let Some(tile) = map.tile(y, x) { - if tile == CollisionTile::empty() { + if let Some(tile) = map.collision_tile(y, x) { + if tile.is_empty() { map.set_tile(y, x, CollisionTile::full()); } else { map.set_tile(y, x, CollisionTile::empty()); diff --git a/src/engine/map/mod.rs b/src/engine/map/mod.rs index 379ca36..7ff5c48 100644 --- a/src/engine/map/mod.rs +++ b/src/engine/map/mod.rs @@ -82,6 +82,16 @@ impl CollisionTile { from_bottom: true, } } + + /// Tests whether a collision tile is full or not. + pub fn is_full(self) -> bool { + self.from_top && self.from_left && self.from_right && self.from_bottom + } + + /// Tests whether a collision tile is empty or not. + pub fn is_empty(self) -> bool { + !self.from_top && !self.from_left && !self.from_right && !self.from_bottom + } } /// This struct represents a renderable tile linking to its part in the tileset texture. @@ -94,79 +104,53 @@ impl GraphicTile { /// Creates the correct graphic tile depending on the neighbours. /// /// A none will be considered solid. - pub fn from_neighbour_options( - top_left: Option, - top: Option, - top_right: Option, - right: Option, - bottom_right: Option, - bottom: Option, - bottom_left: Option, - left: Option, - ) -> GraphicTile { - GraphicTile::from_neighbours( - top_left.unwrap_or_else(CollisionTile::full), - top.unwrap_or_else(CollisionTile::full), - top_right.unwrap_or_else(CollisionTile::full), - right.unwrap_or_else(CollisionTile::full), - bottom_right.unwrap_or_else(CollisionTile::full), - bottom.unwrap_or_else(CollisionTile::full), - bottom_left.unwrap_or_else(CollisionTile::full), - left.unwrap_or_else(CollisionTile::full), - ) + pub fn from_neighbour_options(tiles: &[Option; 8]) -> GraphicTile { + GraphicTile::from_neighbours(&[ + tiles[0].unwrap_or_else(CollisionTile::full), + tiles[1].unwrap_or_else(CollisionTile::full), + tiles[2].unwrap_or_else(CollisionTile::full), + tiles[3].unwrap_or_else(CollisionTile::full), + tiles[4].unwrap_or_else(CollisionTile::full), + tiles[5].unwrap_or_else(CollisionTile::full), + tiles[6].unwrap_or_else(CollisionTile::full), + tiles[7].unwrap_or_else(CollisionTile::full), + ]) } /// Creates the correct graphic tile depending on the neighbours. - pub fn from_neighbours( - top_left: CollisionTile, - top: CollisionTile, - top_right: CollisionTile, - right: CollisionTile, - bottom_right: CollisionTile, - bottom: CollisionTile, - bottom_left: CollisionTile, - left: CollisionTile, - ) -> GraphicTile { + pub fn from_neighbours(tiles: &[CollisionTile; 8]) -> GraphicTile { let mut byte = 0; - if top_left != CollisionTile::full() || - left != CollisionTile::full() || - top != CollisionTile::full() { + if !tiles[7].is_full() || !tiles[0].is_full() || !tiles[1].is_full() { byte += 1; } - if top != CollisionTile::full() { + if !tiles[1].is_full() { byte += 2; } - if top_right != CollisionTile::full() || - top != CollisionTile::full() || - right != CollisionTile::full() { + if !tiles[1] .is_full() || !tiles[2].is_full() || !tiles[3].is_full() { byte += 4; } - if right != CollisionTile::full() { + if !tiles[3] .is_full() { byte += 8; } - if bottom_right != CollisionTile::full() || - bottom != CollisionTile::full() || - right != CollisionTile::full() { + if !tiles[3].is_full() || !tiles[4].is_full() || !tiles[5].is_full() { byte += 16; } - if bottom != CollisionTile::full() { + if !tiles[5].is_full() { byte += 32; } - if bottom_left != CollisionTile::full() || - bottom != CollisionTile::full() || - left != CollisionTile::full() { + if !tiles[5].is_full() || !tiles[6].is_full() || !tiles[7].is_full() { byte += 64; } - if left != CollisionTile::full() { + if !tiles[7] .is_full() { byte += 128; } @@ -214,14 +198,34 @@ impl Drawable for PositionedTile { } } -/// The map represents the tiles contained in a level. +/// The content of a map that needs to be saved. #[derive(Serialize, Deserialize)] +struct SaveMap { + /// The entrance point of the character in the map. + entrance: (usize, usize), + + /// The collision tiles contained in the level. + collision_tiles: Matrix, +} + +impl SaveMap { + /// Creates a map from the save map. + fn to_map(self) -> Map { + Map::from_entrance_and_collision_tiles(self.entrance, self.collision_tiles) + } +} + +/// The map represents the tiles contained in a level. +#[derive(Clone)] pub struct Map { /// The entrace point of the character in the map. entrance: (usize, usize), - /// The tiles contained in the level. - tiles: Matrix<(CollisionTile, GraphicTile)>, + /// The collision tiles contained in the level. + collision_tiles: Matrix, + + /// The graphic tiles contained in the level. + graphic_tiles: Matrix, } impl Map { @@ -237,21 +241,29 @@ impl Map { Map::from_collision_tiles(tiles) } + /// Creates a SaveMap from a map + fn save_map(&self) -> SaveMap { + SaveMap { + entrance: self.entrance, + collision_tiles: self.collision_tiles.clone(), + } + } + /// Encodes the map into a binary format. pub fn encode(&self) -> Result> { - serialize(&self).map_err(Error::Encoding) + serialize(&self.save_map()).map_err(Error::Encoding) } /// Decodes a map from bytes. pub fn decode(content: &[u8]) -> Result { - deserialize(content).map_err(Error::Decoding) + deserialize(content).map(SaveMap::to_map).map_err(Error::Decoding) } /// Saves the map to a file. pub fn save>(&self, path: P) -> Result<()> { let file = File::create(path.as_ref()).map_err(Error::Save)?; let mut writer = BufWriter::new(file); - serialize_into(&mut writer, &self).map_err(Error::Encoding)?; + serialize_into(&mut writer, &self.save_map()).map_err(Error::Encoding)?; Ok(()) } @@ -259,85 +271,83 @@ impl Map { pub fn load>(path: P) -> Result { let file = File::open(path.as_ref()).map_err(Error::Load)?; let mut reader = BufReader::new(file); - Ok(deserialize_from(&mut reader).map_err(Error::Decoding)?) + Ok(deserialize_from(&mut reader).map(SaveMap::to_map).map_err(Error::Decoding)?) } - /// Creates a map from its tiles. - pub fn from_collision_tiles(tiles: Matrix) -> Map { - let rows = tiles.rows(); - let cols = tiles.cols(); + /// Creates a map from its entrance and collision tiles. + pub fn from_entrance_and_collision_tiles(e: (usize, usize), t: Matrix) -> Map { + let rows = t.rows(); + let cols = t.cols(); - let mut matrix = - Matrix::from_size(rows, cols, (CollisionTile::empty(), GraphicTile(None))); + let graphic_tiles = Matrix::from_size(rows, cols, GraphicTile(None)); - for i in 0..rows { - for j in 0..cols { - let graphic = if tiles[(i, j)] == CollisionTile::full() { - // TODO This is uggly - // If there is an overflow, we should give None instead - let (i, j) = (i as isize, j as isize); + let mut map = Map { + entrance: e, + collision_tiles: t, + graphic_tiles, + }; - GraphicTile::from_neighbour_options( - tiles.get(((i - 1) as usize, (j - 1) as usize)).cloned(), - tiles.get(((i ) as usize, (j - 1) as usize)).cloned(), - tiles.get(((i + 1) as usize, (j - 1) as usize)).cloned(), - tiles.get(((i + 1) as usize, (j ) as usize)).cloned(), - tiles.get(((i + 1) as usize, (j + 1) as usize)).cloned(), - tiles.get(((i ) as usize, (j + 1) as usize)).cloned(), - tiles.get(((i - 1) as usize, (j + 1) as usize)).cloned(), - tiles.get(((i - 1) as usize, (j ) as usize)).cloned(), - ) - } else { - GraphicTile(None) - }; - - matrix[(i, j)] = (tiles[(i, j)], graphic); + for i in 0 .. rows { + for j in 0 .. cols { + map.graphic_tiles[(i, j)] = map.graphic_tile(i, j); } } - Map { - entrance: Map::find_entrance(&matrix), - tiles: matrix, - } + map + } + + /// Creates a map from its tiles. + pub fn from_collision_tiles(collision_tiles: Matrix) -> Map { + let entrance = Map::find_entrance(&collision_tiles); + Map::from_entrance_and_collision_tiles(entrance, collision_tiles) + } + + /// Creates the neighbours of a tile. + pub fn neighbours(&self, i: isize, j: isize) -> [Option; 8] { + [ + self.collision_tiles.get_isize(i - 1, j - 1).cloned(), + self.collision_tiles.get_isize(i - 1, j ).cloned(), + self.collision_tiles.get_isize(i - 1, j + 1).cloned(), + self.collision_tiles.get_isize(i , j + 1).cloned(), + self.collision_tiles.get_isize(i + 1, j + 1).cloned(), + self.collision_tiles.get_isize(i + 1, j ).cloned(), + self.collision_tiles.get_isize(i + 1, j - 1).cloned(), + self.collision_tiles.get_isize(i , j - 1).cloned(), + ] } /// Returns the graphic tile corresponding to the collision tiles. pub fn graphic_tile(&self, i: usize, j: usize) -> GraphicTile { - if self.tiles[(i, j)].0 == CollisionTile::full() { - GraphicTile::from_neighbour_options( - self.tiles.get(((i - 1) as usize, (j - 1) as usize)).map(|x| x.0), - self.tiles.get(((i - 1) as usize, (j ) as usize)).map(|x| x.0), - self.tiles.get(((i - 1) as usize, (j + 1) as usize)).map(|x| x.0), - self.tiles.get(((i ) as usize, (j + 1) as usize)).map(|x| x.0), - self.tiles.get(((i + 1) as usize, (j + 1) as usize)).map(|x| x.0), - self.tiles.get(((i + 1) as usize, (j ) as usize)).map(|x| x.0), - self.tiles.get(((i + 1) as usize, (j - 1) as usize)).map(|x| x.0), - self.tiles.get(((i ) as usize, (j - 1) as usize)).map(|x| x.0), - ) + if self.collision_tiles[(i, j)].is_full() { + GraphicTile::from_neighbour_options(&self.neighbours(i as isize, j as isize)) } else { GraphicTile(None) } } /// Returns a tile of the map. - pub fn tile(&self, i: usize, j: usize) -> Option { - self.tiles.get((i, j)).map(|x| x.0) + pub fn collision_tile(&self, i: usize, j: usize) -> Option { + self.collision_tiles.get((i, j)).cloned() } /// Changes a tile of the map. pub fn set_tile(&mut self, i: usize, j: usize, tile: CollisionTile) { - self.tiles[(i, j)] = (tile, GraphicTile(None)); + // Change the collision tile + self.collision_tiles[(i, j)] = tile; + + // Refresh the current graphic tile and their neighbours for i in (i - 1) ..= (i + 1) { for j in (j - 1) ..= (j + 1) { - if self.tiles.get((i, j)).is_some() { - self.tiles[(i, j)].1 = self.graphic_tile(i, j); + let new_tile = self.graphic_tile(i, j); + if let Some(tile) = self.graphic_tiles.get_mut((i, j)) { + *tile = new_tile; } } } } /// Finds a possible entrance. - pub fn find_entrance(tiles: &Matrix<(CollisionTile, GraphicTile)>) -> (usize, usize) { + pub fn find_entrance(tiles: &Matrix) -> (usize, usize) { (tiles.rows() - 5, 1) } @@ -349,20 +359,20 @@ impl Map { /// Returns an iterator to the positioned tiles. pub fn at(&self, row: usize, col: usize) -> PositionedTile { PositionedTile { - collision: self.tiles[(row, col)].0, - graphic: self.tiles[(row, col)].1, + collision: self.collision_tiles[(row, col)], + graphic: self.graphic_tiles[(row, col)], position: (col as f32 * SPRITE_SIZE_F32, row as f32 * SPRITE_SIZE_F32), } } /// Returns the number of rows of the map. pub fn rows(&self) -> usize { - self.tiles.rows() + self.collision_tiles.rows() } /// Returns the number of columns of the map. pub fn cols(&self) -> usize { - self.tiles.cols() + self.collision_tiles.cols() } /// Checks whether the bounding box collides with elements of the map. @@ -376,8 +386,8 @@ impl Map { let mut damage = Damage::None; - let cols = self.tiles.cols() - 1; - let rows = self.tiles.rows() - 1; + let cols = self.collision_tiles.cols() - 1; + let rows = self.collision_tiles.rows() - 1; let min_col = clamp(new.left / SPRITE_SIZE_F32, 0.0, cols as f32) as usize; let min_row = clamp(new.top / SPRITE_SIZE_F32, 0.0, rows as f32) as usize; @@ -401,7 +411,7 @@ impl Map { } // Collisions between feet and ground - if self.tiles[(row, col)].0.from_top + if self.collision_tiles[(row, col)].from_top && old.top + old.height <= tile_top && new.top + new.height >= tile_top { @@ -414,7 +424,7 @@ impl Map { } // Collisions between right and right wall - if self.tiles[(row, col)].0.from_left + if self.collision_tiles[(row, col)].from_left && old.left + old.width <= tile_left && new.left + new.width >= tile_left { @@ -427,7 +437,7 @@ impl Map { } // Collisions between left and left wall - if self.tiles[(row, col)].0.from_right + if self.collision_tiles[(row, col)].from_right && old.left >= tile_left + SPRITE_SIZE_F32 && new.left <= tile_left + SPRITE_SIZE_F32 { @@ -440,7 +450,7 @@ impl Map { } // Collisions between head and roof - if self.tiles[(row, col)].0.from_bottom + if self.collision_tiles[(row, col)].from_bottom && old.top >= tile_top + SPRITE_SIZE_F32 && new.top <= tile_top + SPRITE_SIZE_F32 { @@ -463,8 +473,8 @@ impl Map { } // Collision between the player and the void - if new.top > self.tiles.rows() as f32 * SPRITE_SIZE_F32 { - new.top = self.tiles.rows() as f32 * SPRITE_SIZE_F32; + if new.top > self.collision_tiles.rows() as f32 * SPRITE_SIZE_F32 { + new.top = self.collision_tiles.rows() as f32 * SPRITE_SIZE_F32; collision_y = true; damage = Damage::Death; } diff --git a/src/engine/math/mod.rs b/src/engine/math/mod.rs index 475db8b..837b375 100644 --- a/src/engine/math/mod.rs +++ b/src/engine/math/mod.rs @@ -27,7 +27,7 @@ pub fn duration_as_f32(duration: &Duration) -> f32 { } /// A generic matrix type, useful for levels. -#[derive(Serialize, Deserialize)] +#[derive(Serialize, Deserialize, Clone)] pub struct Matrix { /// The number of rows of the matrix. rows: usize, @@ -77,6 +77,25 @@ impl Matrix { None } } + + + /// Returns the tile if any, none otherwise. + pub fn get_isize(&self, row: isize, col: isize) -> Option<&T> { + if row >= 0 && col >= 0 && (row as usize) < self.rows && (col as usize) < self.cols { + Some(&self[(row as usize, col as usize)]) + } else { + None + } + } + + /// Returns a mutable reference to the tile if any. + pub fn get_mut(&mut self, (row, col): (usize, usize)) -> Option<&mut T> { + if row < self.rows && col < self.cols { + Some(&mut self[(row, col)]) + } else { + None + } + } } impl Index<(usize, usize)> for Matrix {