1use super::*;
11
12#[derive(Clone, Debug)]
16pub struct Grid {
17 pub(super) width: u8,
18 pub(super) height: u8,
19 pub(super) h_walls: Vec<bool>,
22 pub(super) v_walls: Vec<bool>,
25 pub(super) tiles: Vec<TileKind>,
26}
27
28#[derive(Clone, Copy, PartialEq, Eq, Debug)]
33pub(crate) enum Walker {
34 Crab,
35 Gull,
36}
37
38impl Grid {
39 pub fn width(&self) -> u8 {
40 self.width
41 }
42
43 pub fn height(&self) -> u8 {
44 self.height
45 }
46
47 fn wall_edge(&self, x: usize, y: usize, dir: Direction) -> (bool, usize) {
52 let w = self.width as usize;
53 match dir {
54 Direction::Up => (true, y * w + x),
55 Direction::Down => (true, (y + 1) * w + x),
56 Direction::Left => (false, y * (w + 1) + x),
57 Direction::Right => (false, y * (w + 1) + x + 1),
58 }
59 }
60
61 pub(super) fn edge_blocked(&self, x: usize, y: usize, dir: Direction) -> bool {
63 let (horizontal, i) = self.wall_edge(x, y, dir);
64 if horizontal {
65 self.h_walls[i]
66 } else {
67 self.v_walls[i]
68 }
69 }
70
71 pub(super) fn set_edge(&mut self, x: usize, y: usize, dir: Direction, present: bool) {
73 let (horizontal, i) = self.wall_edge(x, y, dir);
74 if horizontal {
75 self.h_walls[i] = present;
76 } else {
77 self.v_walls[i] = present;
78 }
79 }
80
81 pub(super) fn wrap_coords(&self, x: i32, y: i32) -> (i32, i32) {
83 let w = i32::from(self.width);
84 let h = i32::from(self.height);
85 ((x % w + w) % w, (y % h + h) % h)
86 }
87
88 pub fn coords(&self, tile: u16) -> (i32, i32) {
92 (
93 i32::from(tile % u16::from(self.width)),
94 i32::from(tile / u16::from(self.width)),
95 )
96 }
97
98 pub fn index_of(&self, x: u8, y: u8) -> u16 {
100 self.index(i32::from(x), i32::from(y))
101 }
102
103 pub(super) fn index(&self, x: i32, y: i32) -> u16 {
104 debug_assert!(self.in_bounds(x, y));
105 (y * i32::from(self.width) + x) as u16
106 }
107
108 pub(super) fn in_bounds(&self, x: i32, y: i32) -> bool {
109 x >= 0 && y >= 0 && x < i32::from(self.width) && y < i32::from(self.height)
110 }
111}
112
113impl Board {
114 pub(super) fn resolve_walls_for(
117 &self,
118 tile: u16,
119 dir: &mut Direction,
120 handed: Handedness,
121 who: Walker,
122 ) {
123 let d = *dir;
124 if self.passable_for(tile, d, who) {
125 return;
126 }
127 let (first, second) = match handed {
128 Handedness::Left => (d.left(), d.right()),
129 Handedness::Right => (d.right(), d.left()),
130 };
131 if self.passable_for(tile, first, who) {
132 *dir = first;
133 } else if self.passable_for(tile, second, who) {
134 *dir = second;
135 } else {
136 *dir = d.reverse();
137 }
138 }
139
140 pub(super) fn resolve_walls(&self, crab: &mut Crab) {
143 self.resolve_walls_for(crab.tile, &mut crab.dir, crab.handed, Walker::Crab);
144 }
145
146 pub(super) fn turnstile_deflect(
152 &mut self,
153 tile: u16,
154 dir: &mut Direction,
155 handed: Handedness,
156 who: Walker,
157 ) -> bool {
158 let t = tile as usize;
159 let TileKind::Turnstile { next_right } = self.grid.tiles[t] else {
160 return false;
161 };
162 *dir = if next_right { dir.right() } else { dir.left() };
163 self.grid.tiles[t] = TileKind::Turnstile {
164 next_right: !next_right,
165 };
166 self.resolve_walls_for(tile, dir, handed, who);
167 true
168 }
169
170 pub(super) fn passable(&self, tile: u16, dir: Direction) -> bool {
173 self.passable_for(tile, dir, Walker::Crab)
174 }
175
176 pub(super) fn edge_blocked(&self, x: usize, y: usize, dir: Direction) -> bool {
177 self.grid.edge_blocked(x, y, dir)
178 }
179
180 pub(super) fn set_edge(&mut self, x: usize, y: usize, dir: Direction, present: bool) {
181 self.grid.set_edge(x, y, dir, present);
182 }
183
184 pub(super) fn passable_for(&self, tile: u16, dir: Direction, who: Walker) -> bool {
187 let (x, y) = self.coords(tile);
188 if self.edge_blocked(x as usize, y as usize, dir) {
189 return false;
190 }
191 let (dx, dy) = dir.offset();
192 let (nx, ny) = (x + dx, y + dy);
193 let dest = if !self.in_bounds(nx, ny) {
194 if !self.wrap {
195 return false;
196 }
197 let (wx, wy) = self.wrap_coords(nx, ny);
198 self.grid.tiles[self.index(wx, wy) as usize]
199 } else {
200 self.grid.tiles[self.index(nx, ny) as usize]
201 };
202 match dest {
203 TileKind::Rock => false,
204 TileKind::Kelp => who == Walker::Crab,
205 TileKind::Empty
206 | TileKind::Castle(_)
207 | TileKind::Spawner(_)
208 | TileKind::Turnstile { .. }
209 | TileKind::Pool => true,
210 }
211 }
212
213 pub(super) fn ring_openings(
217 &self,
218 cx: i32,
219 cy: i32,
220 ring: &[(i32, i32)],
221 ) -> Vec<(i32, i32, i32, i32)> {
222 ring.iter()
223 .map(|&(ox, oy)| (cx + ox, cy + oy, ox, oy))
224 .filter(|&(nx, ny, _, _)| {
225 self.in_bounds(nx, ny)
226 && self.grid.tiles[self.index(nx, ny) as usize] == TileKind::Empty
227 })
228 .collect()
229 }
230
231 pub(super) fn wrap_coords(&self, x: i32, y: i32) -> (i32, i32) {
232 self.grid.wrap_coords(x, y)
233 }
234
235 pub(super) fn neighbor(&self, tile: u16, dir: Direction) -> u16 {
236 let (x, y) = self.coords(tile);
237 let (dx, dy) = dir.offset();
238 if self.wrap {
239 let (wx, wy) = self.wrap_coords(x + dx, y + dy);
240 return self.index(wx, wy);
241 }
242 self.index(x + dx, y + dy)
243 }
244
245 pub fn step(&self, tile: u16, dir: Direction) -> Option<u16> {
250 let (x, y) = self.coords(tile);
251 let (dx, dy) = dir.offset();
252 if self.wrap {
253 let (wx, wy) = self.wrap_coords(x + dx, y + dy);
254 return Some(self.index(wx, wy));
255 }
256 self.in_bounds(x + dx, y + dy)
257 .then(|| self.index(x + dx, y + dy))
258 }
259
260 pub fn coords(&self, tile: u16) -> (i32, i32) {
264 self.grid.coords(tile)
265 }
266
267 pub fn index_of(&self, x: u8, y: u8) -> u16 {
269 self.grid.index_of(x, y)
270 }
271
272 pub(super) fn index(&self, x: i32, y: i32) -> u16 {
273 self.grid.index(x, y)
274 }
275
276 pub(super) fn in_bounds(&self, x: i32, y: i32) -> bool {
277 self.grid.in_bounds(x, y)
278 }
279}
280
281#[cfg(test)]
282mod tests {
283 use super::*;
284
285 #[test]
291 fn an_index_and_a_pair_of_coordinates_are_the_same_place() {
292 for (w, h) in [(1u8, 1u8), (2, 3), (9, 7), (21, 13)] {
293 let board = Board::new(w, h, 1);
294 for y in 0..h {
295 for x in 0..w {
296 let tile = board.index_of(x, y);
297 assert_eq!(
298 board.coords(tile),
299 (i32::from(x), i32::from(y)),
300 "{w}x{h}: ({x},{y}) did not survive the trip through {tile}"
301 );
302 assert!(
303 (tile as usize) < usize::from(w) * usize::from(h),
304 "{w}x{h}: ({x},{y}) indexed off the end at {tile}"
305 );
306 }
307 }
308 }
309 }
310}