use super::*;
#[derive(Clone, Debug)]
pub struct Grid {
pub(super) width: u8,
pub(super) height: u8,
pub(super) h_walls: Vec<bool>,
pub(super) v_walls: Vec<bool>,
pub(super) tiles: Vec<TileKind>,
}
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub(crate) enum Walker {
Crab,
Gull,
}
impl Grid {
pub fn width(&self) -> u8 {
self.width
}
pub fn height(&self) -> u8 {
self.height
}
fn wall_edge(&self, x: usize, y: usize, dir: Direction) -> (bool, usize) {
let w = self.width as usize;
match dir {
Direction::Up => (true, y * w + x),
Direction::Down => (true, (y + 1) * w + x),
Direction::Left => (false, y * (w + 1) + x),
Direction::Right => (false, y * (w + 1) + x + 1),
}
}
pub(super) fn edge_blocked(&self, x: usize, y: usize, dir: Direction) -> bool {
let (horizontal, i) = self.wall_edge(x, y, dir);
if horizontal {
self.h_walls[i]
} else {
self.v_walls[i]
}
}
pub(super) fn set_edge(&mut self, x: usize, y: usize, dir: Direction, present: bool) {
let (horizontal, i) = self.wall_edge(x, y, dir);
if horizontal {
self.h_walls[i] = present;
} else {
self.v_walls[i] = present;
}
}
pub(super) fn wrap_coords(&self, x: i32, y: i32) -> (i32, i32) {
let w = i32::from(self.width);
let h = i32::from(self.height);
((x % w + w) % w, (y % h + h) % h)
}
pub fn coords(&self, tile: u16) -> (i32, i32) {
(
i32::from(tile % u16::from(self.width)),
i32::from(tile / u16::from(self.width)),
)
}
pub fn index_of(&self, x: u8, y: u8) -> u16 {
self.index(i32::from(x), i32::from(y))
}
pub(super) fn index(&self, x: i32, y: i32) -> u16 {
debug_assert!(self.in_bounds(x, y));
(y * i32::from(self.width) + x) as u16
}
pub(super) fn in_bounds(&self, x: i32, y: i32) -> bool {
x >= 0 && y >= 0 && x < i32::from(self.width) && y < i32::from(self.height)
}
}
impl Board {
pub(super) fn resolve_walls_for(
&self,
tile: u16,
dir: &mut Direction,
handed: Handedness,
who: Walker,
) {
let d = *dir;
if self.passable_for(tile, d, who) {
return;
}
let (first, second) = match handed {
Handedness::Left => (d.left(), d.right()),
Handedness::Right => (d.right(), d.left()),
};
if self.passable_for(tile, first, who) {
*dir = first;
} else if self.passable_for(tile, second, who) {
*dir = second;
} else {
*dir = d.reverse();
}
}
pub(super) fn resolve_walls(&self, crab: &mut Crab) {
self.resolve_walls_for(crab.tile, &mut crab.dir, crab.handed, Walker::Crab);
}
pub(super) fn turnstile_deflect(
&mut self,
tile: u16,
dir: &mut Direction,
handed: Handedness,
who: Walker,
) -> bool {
let t = tile as usize;
let TileKind::Turnstile { next_right } = self.grid.tiles[t] else {
return false;
};
*dir = if next_right { dir.right() } else { dir.left() };
self.grid.tiles[t] = TileKind::Turnstile {
next_right: !next_right,
};
self.resolve_walls_for(tile, dir, handed, who);
true
}
pub(super) fn passable(&self, tile: u16, dir: Direction) -> bool {
self.passable_for(tile, dir, Walker::Crab)
}
pub(super) fn edge_blocked(&self, x: usize, y: usize, dir: Direction) -> bool {
self.grid.edge_blocked(x, y, dir)
}
pub(super) fn set_edge(&mut self, x: usize, y: usize, dir: Direction, present: bool) {
self.grid.set_edge(x, y, dir, present);
}
pub(super) fn passable_for(&self, tile: u16, dir: Direction, who: Walker) -> bool {
let (x, y) = self.coords(tile);
if self.edge_blocked(x as usize, y as usize, dir) {
return false;
}
let (dx, dy) = dir.offset();
let (nx, ny) = (x + dx, y + dy);
let dest = if !self.in_bounds(nx, ny) {
if !self.wrap {
return false;
}
let (wx, wy) = self.wrap_coords(nx, ny);
self.grid.tiles[self.index(wx, wy) as usize]
} else {
self.grid.tiles[self.index(nx, ny) as usize]
};
match dest {
TileKind::Rock => false,
TileKind::Kelp => who == Walker::Crab,
TileKind::Empty
| TileKind::Castle(_)
| TileKind::Spawner(_)
| TileKind::Turnstile { .. }
| TileKind::Pool => true,
}
}
pub(super) fn ring_openings(
&self,
cx: i32,
cy: i32,
ring: &[(i32, i32)],
) -> Vec<(i32, i32, i32, i32)> {
ring.iter()
.map(|&(ox, oy)| (cx + ox, cy + oy, ox, oy))
.filter(|&(nx, ny, _, _)| {
self.in_bounds(nx, ny)
&& self.grid.tiles[self.index(nx, ny) as usize] == TileKind::Empty
})
.collect()
}
pub(super) fn wrap_coords(&self, x: i32, y: i32) -> (i32, i32) {
self.grid.wrap_coords(x, y)
}
pub(super) fn neighbor(&self, tile: u16, dir: Direction) -> u16 {
let (x, y) = self.coords(tile);
let (dx, dy) = dir.offset();
if self.wrap {
let (wx, wy) = self.wrap_coords(x + dx, y + dy);
return self.index(wx, wy);
}
self.index(x + dx, y + dy)
}
pub fn step(&self, tile: u16, dir: Direction) -> Option<u16> {
let (x, y) = self.coords(tile);
let (dx, dy) = dir.offset();
if self.wrap {
let (wx, wy) = self.wrap_coords(x + dx, y + dy);
return Some(self.index(wx, wy));
}
self.in_bounds(x + dx, y + dy)
.then(|| self.index(x + dx, y + dy))
}
pub fn coords(&self, tile: u16) -> (i32, i32) {
self.grid.coords(tile)
}
pub fn index_of(&self, x: u8, y: u8) -> u16 {
self.grid.index_of(x, y)
}
pub(super) fn index(&self, x: i32, y: i32) -> u16 {
self.grid.index(x, y)
}
pub(super) fn in_bounds(&self, x: i32, y: i32) -> bool {
self.grid.in_bounds(x, y)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn an_index_and_a_pair_of_coordinates_are_the_same_place() {
for (w, h) in [(1u8, 1u8), (2, 3), (9, 7), (21, 13)] {
let board = Board::new(w, h, 1);
for y in 0..h {
for x in 0..w {
let tile = board.index_of(x, y);
assert_eq!(
board.coords(tile),
(i32::from(x), i32::from(y)),
"{w}x{h}: ({x},{y}) did not survive the trip through {tile}"
);
assert!(
(tile as usize) < usize::from(w) * usize::from(h),
"{w}x{h}: ({x},{y}) indexed off the end at {tile}"
);
}
}
}
}
}