This commit is contained in:
Thomas Forgione 2019-04-02 21:59:14 +02:00
parent 7bab06b2b6
commit a66e596db0
No known key found for this signature in database
GPG Key ID: BFD17A2D71B3B5E7
3 changed files with 145 additions and 116 deletions

View File

@ -43,8 +43,8 @@ fn main() {
let x = ((x - left_panel_size.x as i32) / SPRITE_SIZE_I32) as usize; 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; let y = ((y - top_panel_size.y as i32) / SPRITE_SIZE_I32) as usize;
if let Some(tile) = map.tile(y, x) { if let Some(tile) = map.collision_tile(y, x) {
if tile == CollisionTile::empty() { if tile.is_empty() {
map.set_tile(y, x, CollisionTile::full()); map.set_tile(y, x, CollisionTile::full());
} else { } else {
map.set_tile(y, x, CollisionTile::empty()); map.set_tile(y, x, CollisionTile::empty());

View File

@ -82,6 +82,16 @@ impl CollisionTile {
from_bottom: true, 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. /// 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. /// Creates the correct graphic tile depending on the neighbours.
/// ///
/// A none will be considered solid. /// A none will be considered solid.
pub fn from_neighbour_options( pub fn from_neighbour_options(tiles: &[Option<CollisionTile>; 8]) -> GraphicTile {
top_left: Option<CollisionTile>, GraphicTile::from_neighbours(&[
top: Option<CollisionTile>, tiles[0].unwrap_or_else(CollisionTile::full),
top_right: Option<CollisionTile>, tiles[1].unwrap_or_else(CollisionTile::full),
right: Option<CollisionTile>, tiles[2].unwrap_or_else(CollisionTile::full),
bottom_right: Option<CollisionTile>, tiles[3].unwrap_or_else(CollisionTile::full),
bottom: Option<CollisionTile>, tiles[4].unwrap_or_else(CollisionTile::full),
bottom_left: Option<CollisionTile>, tiles[5].unwrap_or_else(CollisionTile::full),
left: Option<CollisionTile>, tiles[6].unwrap_or_else(CollisionTile::full),
) -> GraphicTile { tiles[7].unwrap_or_else(CollisionTile::full),
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),
)
} }
/// Creates the correct graphic tile depending on the neighbours. /// Creates the correct graphic tile depending on the neighbours.
pub fn from_neighbours( pub fn from_neighbours(tiles: &[CollisionTile; 8]) -> GraphicTile {
top_left: CollisionTile,
top: CollisionTile,
top_right: CollisionTile,
right: CollisionTile,
bottom_right: CollisionTile,
bottom: CollisionTile,
bottom_left: CollisionTile,
left: CollisionTile,
) -> GraphicTile {
let mut byte = 0; let mut byte = 0;
if top_left != CollisionTile::full() || if !tiles[7].is_full() || !tiles[0].is_full() || !tiles[1].is_full() {
left != CollisionTile::full() ||
top != CollisionTile::full() {
byte += 1; byte += 1;
} }
if top != CollisionTile::full() { if !tiles[1].is_full() {
byte += 2; byte += 2;
} }
if top_right != CollisionTile::full() || if !tiles[1] .is_full() || !tiles[2].is_full() || !tiles[3].is_full() {
top != CollisionTile::full() ||
right != CollisionTile::full() {
byte += 4; byte += 4;
} }
if right != CollisionTile::full() { if !tiles[3] .is_full() {
byte += 8; byte += 8;
} }
if bottom_right != CollisionTile::full() || if !tiles[3].is_full() || !tiles[4].is_full() || !tiles[5].is_full() {
bottom != CollisionTile::full() ||
right != CollisionTile::full() {
byte += 16; byte += 16;
} }
if bottom != CollisionTile::full() { if !tiles[5].is_full() {
byte += 32; byte += 32;
} }
if bottom_left != CollisionTile::full() || if !tiles[5].is_full() || !tiles[6].is_full() || !tiles[7].is_full() {
bottom != CollisionTile::full() ||
left != CollisionTile::full() {
byte += 64; byte += 64;
} }
if left != CollisionTile::full() { if !tiles[7] .is_full() {
byte += 128; 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)] #[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<CollisionTile>,
}
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 { pub struct Map {
/// The entrace point of the character in the map. /// The entrace point of the character in the map.
entrance: (usize, usize), entrance: (usize, usize),
/// The tiles contained in the level. /// The collision tiles contained in the level.
tiles: Matrix<(CollisionTile, GraphicTile)>, collision_tiles: Matrix<CollisionTile>,
/// The graphic tiles contained in the level.
graphic_tiles: Matrix<GraphicTile>,
} }
impl Map { impl Map {
@ -237,21 +241,29 @@ impl Map {
Map::from_collision_tiles(tiles) 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. /// Encodes the map into a binary format.
pub fn encode(&self) -> Result<Vec<u8>> { pub fn encode(&self) -> Result<Vec<u8>> {
serialize(&self).map_err(Error::Encoding) serialize(&self.save_map()).map_err(Error::Encoding)
} }
/// Decodes a map from bytes. /// Decodes a map from bytes.
pub fn decode(content: &[u8]) -> Result<Map> { pub fn decode(content: &[u8]) -> Result<Map> {
deserialize(content).map_err(Error::Decoding) deserialize(content).map(SaveMap::to_map).map_err(Error::Decoding)
} }
/// Saves the map to a file. /// Saves the map to a file.
pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> { pub fn save<P: AsRef<Path>>(&self, path: P) -> Result<()> {
let file = File::create(path.as_ref()).map_err(Error::Save)?; let file = File::create(path.as_ref()).map_err(Error::Save)?;
let mut writer = BufWriter::new(file); 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(()) Ok(())
} }
@ -259,85 +271,83 @@ impl Map {
pub fn load<P: AsRef<Path>>(path: P) -> Result<Map> { pub fn load<P: AsRef<Path>>(path: P) -> Result<Map> {
let file = File::open(path.as_ref()).map_err(Error::Load)?; let file = File::open(path.as_ref()).map_err(Error::Load)?;
let mut reader = BufReader::new(file); 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. /// Creates a map from its entrance and collision tiles.
pub fn from_collision_tiles(tiles: Matrix<CollisionTile>) -> Map { pub fn from_entrance_and_collision_tiles(e: (usize, usize), t: Matrix<CollisionTile>) -> Map {
let rows = tiles.rows(); let rows = t.rows();
let cols = tiles.cols(); let cols = t.cols();
let mut matrix = let graphic_tiles = Matrix::from_size(rows, cols, GraphicTile(None));
Matrix::from_size(rows, cols, (CollisionTile::empty(), GraphicTile(None)));
for i in 0..rows { let mut map = Map {
for j in 0..cols { entrance: e,
let graphic = if tiles[(i, j)] == CollisionTile::full() { collision_tiles: t,
// TODO This is uggly graphic_tiles,
// If there is an overflow, we should give None instead };
let (i, j) = (i as isize, j as isize);
GraphicTile::from_neighbour_options( for i in 0 .. rows {
tiles.get(((i - 1) as usize, (j - 1) as usize)).cloned(), for j in 0 .. cols {
tiles.get(((i ) as usize, (j - 1) as usize)).cloned(), map.graphic_tiles[(i, j)] = map.graphic_tile(i, j);
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);
} }
} }
Map { map
entrance: Map::find_entrance(&matrix), }
tiles: matrix,
} /// Creates a map from its tiles.
pub fn from_collision_tiles(collision_tiles: Matrix<CollisionTile>) -> 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<CollisionTile>; 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. /// Returns the graphic tile corresponding to the collision tiles.
pub fn graphic_tile(&self, i: usize, j: usize) -> GraphicTile { pub fn graphic_tile(&self, i: usize, j: usize) -> GraphicTile {
if self.tiles[(i, j)].0 == CollisionTile::full() { if self.collision_tiles[(i, j)].is_full() {
GraphicTile::from_neighbour_options( GraphicTile::from_neighbour_options(&self.neighbours(i as isize, j as isize))
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),
)
} else { } else {
GraphicTile(None) GraphicTile(None)
} }
} }
/// Returns a tile of the map. /// Returns a tile of the map.
pub fn tile(&self, i: usize, j: usize) -> Option<CollisionTile> { pub fn collision_tile(&self, i: usize, j: usize) -> Option<CollisionTile> {
self.tiles.get((i, j)).map(|x| x.0) self.collision_tiles.get((i, j)).cloned()
} }
/// Changes a tile of the map. /// Changes a tile of the map.
pub fn set_tile(&mut self, i: usize, j: usize, tile: CollisionTile) { 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 i in (i - 1) ..= (i + 1) {
for j in (j - 1) ..= (j + 1) { for j in (j - 1) ..= (j + 1) {
if self.tiles.get((i, j)).is_some() { let new_tile = self.graphic_tile(i, j);
self.tiles[(i, j)].1 = self.graphic_tile(i, j); if let Some(tile) = self.graphic_tiles.get_mut((i, j)) {
*tile = new_tile;
} }
} }
} }
} }
/// Finds a possible entrance. /// Finds a possible entrance.
pub fn find_entrance(tiles: &Matrix<(CollisionTile, GraphicTile)>) -> (usize, usize) { pub fn find_entrance(tiles: &Matrix<CollisionTile>) -> (usize, usize) {
(tiles.rows() - 5, 1) (tiles.rows() - 5, 1)
} }
@ -349,20 +359,20 @@ impl Map {
/// Returns an iterator to the positioned tiles. /// Returns an iterator to the positioned tiles.
pub fn at(&self, row: usize, col: usize) -> PositionedTile { pub fn at(&self, row: usize, col: usize) -> PositionedTile {
PositionedTile { PositionedTile {
collision: self.tiles[(row, col)].0, collision: self.collision_tiles[(row, col)],
graphic: self.tiles[(row, col)].1, graphic: self.graphic_tiles[(row, col)],
position: (col as f32 * SPRITE_SIZE_F32, row as f32 * SPRITE_SIZE_F32), position: (col as f32 * SPRITE_SIZE_F32, row as f32 * SPRITE_SIZE_F32),
} }
} }
/// Returns the number of rows of the map. /// Returns the number of rows of the map.
pub fn rows(&self) -> usize { pub fn rows(&self) -> usize {
self.tiles.rows() self.collision_tiles.rows()
} }
/// Returns the number of columns of the map. /// Returns the number of columns of the map.
pub fn cols(&self) -> usize { pub fn cols(&self) -> usize {
self.tiles.cols() self.collision_tiles.cols()
} }
/// Checks whether the bounding box collides with elements of the map. /// Checks whether the bounding box collides with elements of the map.
@ -376,8 +386,8 @@ impl Map {
let mut damage = Damage::None; let mut damage = Damage::None;
let cols = self.tiles.cols() - 1; let cols = self.collision_tiles.cols() - 1;
let rows = self.tiles.rows() - 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_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; 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 // 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 && old.top + old.height <= tile_top
&& new.top + new.height >= tile_top && new.top + new.height >= tile_top
{ {
@ -414,7 +424,7 @@ impl Map {
} }
// Collisions between right and right wall // 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 && old.left + old.width <= tile_left
&& new.left + new.width >= tile_left && new.left + new.width >= tile_left
{ {
@ -427,7 +437,7 @@ impl Map {
} }
// Collisions between left and left wall // 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 && old.left >= tile_left + SPRITE_SIZE_F32
&& new.left <= tile_left + SPRITE_SIZE_F32 && new.left <= tile_left + SPRITE_SIZE_F32
{ {
@ -440,7 +450,7 @@ impl Map {
} }
// Collisions between head and roof // 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 && old.top >= tile_top + SPRITE_SIZE_F32
&& new.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 // Collision between the player and the void
if 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.tiles.rows() as f32 * SPRITE_SIZE_F32; new.top = self.collision_tiles.rows() as f32 * SPRITE_SIZE_F32;
collision_y = true; collision_y = true;
damage = Damage::Death; damage = Damage::Death;
} }

View File

@ -27,7 +27,7 @@ pub fn duration_as_f32(duration: &Duration) -> f32 {
} }
/// A generic matrix type, useful for levels. /// A generic matrix type, useful for levels.
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize, Clone)]
pub struct Matrix<T> { pub struct Matrix<T> {
/// The number of rows of the matrix. /// The number of rows of the matrix.
rows: usize, rows: usize,
@ -77,6 +77,25 @@ impl<T> Matrix<T> {
None 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<T> Index<(usize, usize)> for Matrix<T> { impl<T> Index<(usize, usize)> for Matrix<T> {