use super::{CustomTile, Hold, Level, Rect, Surface, Terrain};
pub trait Grid {
fn tile_size(&self) -> i32;
fn tile(&self, tx: i32, ty: i32) -> CustomTile;
fn fell_out(&self, _r: Rect) -> bool {
false
}
fn span(&self, r: Rect) -> (i32, i32, i32, i32) {
let t = self.tile_size();
(
r.x.div_euclid(t),
r.y.div_euclid(t),
(r.right() - 1).div_euclid(t),
(r.bottom() - 1).div_euclid(t),
)
}
fn overlaps_solid(&self, r: Rect) -> bool {
let (tx0, ty0, tx1, ty1) = self.span(r);
(ty0..=ty1).any(|ty| (tx0..=tx1).any(|tx| self.tile(tx, ty).hold == Hold::Solid))
}
fn on_platform(&self, r: Rect) -> bool {
let t = self.tile_size();
if r.bottom().rem_euclid(t) != 0 {
return false;
}
let ty = r.bottom().div_euclid(t);
let (tx0, _, tx1, _) = self.span(r);
(tx0..=tx1).any(|tx| self.tile(tx, ty).hold == Hold::Platform)
}
fn surface_of(&self, r: Rect) -> Surface {
let t = self.tile_size();
let holding = |tx, ty| {
let c = self.tile(tx, ty);
(c.hold != Hold::Air).then_some(c.surface)
};
let (cx, cy) = (r.x + r.w / 2, r.y + r.h / 2);
if let Some(s) = holding(cx.div_euclid(t), cy.div_euclid(t)) {
return s;
}
let (tx0, ty0, tx1, ty1) = self.span(r);
(ty0..=ty1)
.flat_map(|ty| (tx0..=tx1).map(move |tx| (tx, ty)))
.find_map(|(tx, ty)| holding(tx, ty))
.unwrap_or_default()
}
}
impl<G: Grid + ?Sized> Grid for &G {
fn tile_size(&self) -> i32 {
(**self).tile_size()
}
fn tile(&self, tx: i32, ty: i32) -> CustomTile {
(**self).tile(tx, ty)
}
fn fell_out(&self, r: Rect) -> bool {
(**self).fell_out(r)
}
}
#[derive(Clone, Copy, Debug)]
pub struct GridTerrain<G>(pub G);
impl<G: Grid> Terrain for GridTerrain<G> {
fn solid(&self, r: Rect) -> bool {
self.0.overlaps_solid(r)
}
fn platform(&self, r: Rect) -> bool {
self.0.on_platform(r)
}
fn surface(&self, r: Rect) -> Surface {
self.0.surface_of(r)
}
fn fell_out(&self, r: Rect) -> bool {
self.0.fell_out(r)
}
crate::terrain_layer!(borrowed);
}
pub(crate) struct LevelGrid<'a>(pub &'a Level);
impl Grid for LevelGrid<'_> {
fn tile_size(&self) -> i32 {
self.0.tile_size()
}
fn tile(&self, tx: i32, ty: i32) -> CustomTile {
self.0.tile_kind(tx, ty)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::sim::Tile;
struct Mirror(Level);
impl Grid for Mirror {
fn tile_size(&self) -> i32 {
self.0.tile_size()
}
fn tile(&self, tx: i32, ty: i32) -> CustomTile {
match self.0.tile(tx, ty) {
Tile::Solid => CustomTile::solid(Surface::DEFAULT),
Tile::Platform => CustomTile::platform(Surface::DEFAULT),
Tile::Custom(id) => self.0.custom_tile(id),
Tile::Empty => CustomTile::default(),
}
}
}
#[test]
fn a_grid_answers_like_the_equivalent_level() {
let mut level =
Level::parse_with(
&["....==..", ".~~.....", "##ii##==", "........"],
|c| match c {
'~' => Some(Tile::Custom(1)),
'i' => Some(Tile::Custom(2)),
_ => None,
},
);
let ice = Surface {
friction: 0.1,
..Surface::DEFAULT
};
level.define(1, CustomTile::platform(Surface { bounce: 0.5, ..ice }));
level.define(2, CustomTile::solid(ice));
let grid = GridTerrain(Mirror(level.clone()));
for y in -14..34 {
for x in -8..66 {
for (w, h) in [(6, 12), (3, 3), (12, 2)] {
let r = Rect { x, y, w, h };
assert_eq!(grid.solid(r), level.solid(r), "{r:?}");
assert_eq!(grid.platform(r), level.platform(r), "{r:?}");
assert_eq!(grid.surface(r), level.surface(r), "{r:?}");
}
}
}
}
}