1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
9pub struct Cell {
10 pub x: i32,
11 pub y: i32,
12}
13
14impl Cell {
15 pub fn new(x: i32, y: i32) -> Self {
16 Self { x, y }
17 }
18}
19
20pub struct CoverageGrid {
22 pub width: i32,
23 pub height: i32,
24 blocked: Vec<bool>,
25}
26
27impl CoverageGrid {
28 pub fn new(width: i32, height: i32) -> Self {
29 Self {
30 width,
31 height,
32 blocked: vec![false; (width * height) as usize],
33 }
34 }
35
36 pub fn set_blocked(&mut self, x: i32, y: i32, val: bool) {
37 if self.in_bounds(x, y) {
38 self.blocked[(y * self.width + x) as usize] = val;
39 }
40 }
41
42 pub fn is_blocked(&self, x: i32, y: i32) -> bool {
43 if !self.in_bounds(x, y) {
44 return true;
45 }
46 self.blocked[(y * self.width + x) as usize]
47 }
48
49 pub fn in_bounds(&self, x: i32, y: i32) -> bool {
50 x >= 0 && x < self.width && y >= 0 && y < self.height
51 }
52
53 pub fn is_free(&self, x: i32, y: i32) -> bool {
54 self.in_bounds(x, y) && !self.is_blocked(x, y)
55 }
56
57 pub fn free_cell_count(&self) -> usize {
58 self.blocked.iter().filter(|&&b| !b).count()
59 }
60}
61
62pub fn boustrophedon_coverage(grid: &CoverageGrid) -> Vec<Cell> {
67 let mut path = Vec::new();
68 let mut going_up = true;
69
70 for x in 0..grid.width {
71 let ys: Box<dyn Iterator<Item = i32>> = if going_up {
72 Box::new(0..grid.height)
73 } else {
74 Box::new((0..grid.height).rev())
75 };
76
77 let mut column_has_free = false;
78 for y in ys {
79 if grid.is_free(x, y) {
80 path.push(Cell::new(x, y));
81 column_has_free = true;
82 }
83 }
84
85 if column_has_free {
86 going_up = !going_up;
87 }
88 }
89
90 path
91}
92
93pub fn spiral_coverage(grid: &CoverageGrid, start_x: i32, start_y: i32) -> Vec<Cell> {
98 let mut path = Vec::new();
99 let mut visited = vec![false; (grid.width * grid.height) as usize];
100
101 if !grid.is_free(start_x, start_y) {
102 return path;
103 }
104
105 let dx = [1, 0, -1, 0];
107 let dy = [0, 1, 0, -1];
108
109 let mut x = start_x;
110 let mut y = start_y;
111 let mut dir = 0usize; let visit = |x: i32, y: i32, visited: &mut Vec<bool>| {
114 visited[(y * grid.width + x) as usize] = true;
115 };
116
117 let is_visited = |x: i32, y: i32, visited: &Vec<bool>| -> bool {
118 if !grid.in_bounds(x, y) {
119 return true;
120 }
121 visited[(y * grid.width + x) as usize]
122 };
123
124 path.push(Cell::new(x, y));
125 visit(x, y, &mut visited);
126
127 let total_free = grid.free_cell_count();
128 let mut stuck_count = 0;
129
130 while path.len() < total_free && stuck_count < 4 {
131 let nx = x + dx[dir];
132 let ny = y + dy[dir];
133
134 if grid.is_free(nx, ny) && !is_visited(nx, ny, &visited) {
135 x = nx;
136 y = ny;
137 path.push(Cell::new(x, y));
138 visit(x, y, &mut visited);
139 stuck_count = 0;
140 } else {
141 dir = (dir + 1) % 4;
143 stuck_count += 1;
144 }
145 }
146
147 if path.len() < total_free {
149 for cy in 0..grid.height {
150 for cx in 0..grid.width {
151 if grid.is_free(cx, cy) && !is_visited(cx, cy, &visited) {
152 path.push(Cell::new(cx, cy));
153 visit(cx, cy, &mut visited);
154 }
155 }
156 }
157 }
158
159 path
160}
161
162#[cfg(test)]
163mod tests {
164 use super::*;
165
166 #[test]
167 fn test_boustrophedon_covers_all_free_cells() {
168 let grid = CoverageGrid::new(5, 5);
169 let path = boustrophedon_coverage(&grid);
170
171 assert_eq!(path.len(), 25);
172 let mut seen = std::collections::HashSet::new();
174 for cell in &path {
175 assert!(seen.insert(*cell), "duplicate cell: {:?}", cell);
176 }
177 }
178
179 #[test]
180 fn test_boustrophedon_skips_obstacles() {
181 let mut grid = CoverageGrid::new(4, 4);
182 grid.set_blocked(1, 1, true);
183 grid.set_blocked(2, 2, true);
184
185 let path = boustrophedon_coverage(&grid);
186 assert_eq!(path.len(), 14); for cell in &path {
189 assert!(!grid.is_blocked(cell.x, cell.y));
190 }
191 }
192
193 #[test]
194 fn test_boustrophedon_path_is_connected_within_columns() {
195 let grid = CoverageGrid::new(3, 4);
196 let path = boustrophedon_coverage(&grid);
197
198 assert_eq!(path.len(), 12);
200 }
201
202 #[test]
203 fn test_spiral_covers_all_free_cells() {
204 let grid = CoverageGrid::new(5, 5);
205 let path = spiral_coverage(&grid, 2, 2);
206
207 assert_eq!(path.len(), 25);
208 let mut seen = std::collections::HashSet::new();
209 for cell in &path {
210 assert!(seen.insert(*cell), "duplicate cell: {:?}", cell);
211 }
212 }
213
214 #[test]
215 fn test_spiral_with_obstacles() {
216 let mut grid = CoverageGrid::new(5, 5);
217 grid.set_blocked(1, 1, true);
218 grid.set_blocked(3, 3, true);
219
220 let path = spiral_coverage(&grid, 0, 0);
221 assert_eq!(path.len(), 23); for cell in &path {
224 assert!(!grid.is_blocked(cell.x, cell.y));
225 }
226 }
227
228 #[test]
229 fn test_spiral_from_corner() {
230 let grid = CoverageGrid::new(3, 3);
231 let path = spiral_coverage(&grid, 0, 0);
232
233 assert_eq!(path.len(), 9);
234 assert_eq!(path[0], Cell::new(0, 0));
235 }
236
237 #[test]
238 fn test_spiral_blocked_start() {
239 let mut grid = CoverageGrid::new(3, 3);
240 grid.set_blocked(1, 1, true);
241 let path = spiral_coverage(&grid, 1, 1);
242 assert!(path.is_empty());
243 }
244
245 #[test]
246 fn test_empty_grid() {
247 let grid = CoverageGrid::new(0, 0);
248 let path = boustrophedon_coverage(&grid);
249 assert!(path.is_empty());
250 }
251}