1use std::{collections::{HashMap, HashSet}, str::FromStr};
2use miette::Diagnostic;
3use thiserror::Error;
4
5pub type Position = glam::IVec2;
6pub type Velocity = glam::IVec2;
7
8pub fn manhattan_distance(a: &Position, b: &Position) -> i32 {
9 (a.x - b.x).abs() + (a.y - b.y).abs()
10}
11
12pub type UniquePositions = HashSet<Position>;
14pub const DIRECTIONS: [Position; 4] = [Position::NEG_Y, Position::X, Position::Y, Position::NEG_X];
16
17pub const DIAGONALS: [Position; 4] = [Position::NEG_ONE, Position::new(-1, 1), Position::new(1, -1), Position::ONE];
19
20pub const ALL_DIRECTIONS: [Position; 8] = [Position::NEG_Y, Position::ONE, Position::X, Position::new(1, -1), Position::Y, Position::NEG_ONE, Position::NEG_X, Position::new(-1, 1)];
22
23#[derive(Debug, derive_more::Deref, derive_more::DerefMut)]
24pub struct Grid<T>(pub Vec<Vec<T>>);
25
26impl FromStr for Grid<char> {
27 type Err = miette::Error;
28
29 fn from_str(input: &str) -> miette::Result<Self> {
30 Ok(Self(input.lines()
31 .map(|line| line.chars().collect()).collect()))
32 }
33}
34
35impl FromStr for Grid<bool> {
36 type Err = miette::Error;
37
38 fn from_str(input: &str) -> miette::Result<Self> {
39 Ok(Self(input.lines()
40 .map(|line| line.chars().map(|c| c == '#').collect()).collect()))
41 }
42}
43
44impl<T: std::fmt::Debug + Copy + PartialEq> Grid<T> {
45 pub fn initialize(width: usize, height: usize, value: T) -> Self {
46 let mut grid = Vec::with_capacity(height);
47 for _ in 0..height {
48 let row = vec![value; width];
49 grid.push(row);
50 }
51
52 Self(grid)
53 }
54
55 pub fn get_width(&self) -> usize {
56 self[0].len()
57 }
58
59 pub fn get_height(&self) -> usize {
60 self.len()
61 }
62
63 pub fn get_at_unbounded(&self, pos: Position) -> T {
64 self[pos.y as usize][pos.x as usize]
65 }
66
67 pub fn get_at(&self, pos: Position) -> Option<T> {
69 if !self.in_bounds(pos) {
71 return None;
72 }
73
74 Some(self[pos.y as usize][pos.x as usize])
75 }
77 pub fn get_neighbors(&self, pos: Position) -> Vec<(Position, T)> {
79 let mut neighbors = Vec::with_capacity(4);
80
81 for delta in DIRECTIONS.iter() {
82 let new_pos = pos + *delta;
83
84 if new_pos.x >= 0 && new_pos.x < self.get_width() as i32 &&
86 new_pos.y >= 0 && new_pos.y < self.get_height() as i32 {
87 neighbors.push((new_pos, self.get_at_unbounded(new_pos)));
88 }
89 }
90
91 neighbors
92 }
93
94 pub fn get_all_neighbors(&self, pos: Position) -> Vec<(Position, T)> {
95 let mut neighbors = Vec::with_capacity(8);
96
97 for delta in ALL_DIRECTIONS.iter() {
98 let new_pos = pos + *delta;
99
100 if new_pos.x >= 0 && new_pos.x < self.get_width() as i32 &&
102 new_pos.y >= 0 && new_pos.y < self.get_height() as i32 {
103 neighbors.push((new_pos, self.get_at_unbounded(new_pos)));
104 }
105 }
106
107 neighbors
108 }
109
110 pub fn in_bounds(&self, pos: Position) -> bool {
111 pos.x >= 0 && pos.y >= 0 && pos.x < self.get_width() as i32 && pos.y < self.get_height() as i32
112 }
113
114 pub fn set_at(&mut self, pos: Position, value: T) -> Option<()> {
115 if !self.in_bounds(pos) {
116 return None;
117 }
118 self[pos.y as usize][pos.x as usize] = value;
119 Some(())
120 }
121
122 pub fn set_at_unbounded(&mut self, pos: Position, value: T) {
124 self[pos.y as usize][pos.x as usize] = value;
125 }
126
127 pub fn walk<F: FnMut(Position) -> O, O>(&self, mut see: F) {
129 for row in 0..self.get_height() {
130 for col in 0..self.get_width() {
131 let pos = Position::new(col as i32, row as i32);
132
133 see(pos);
134 }
135 }
136 }
137
138 pub fn walk_region<F>(&mut self, start: Position, end: Position, mut see: F)
139 where
140 F: FnMut(&mut Self, Position)
141 {
142 for row in start.y..=end.y {
143 for col in start.x..=end.x {
144 let pos = Position::new(col, row);
145 see(self, pos);
146 }
147 }
148 }
149
150 pub fn position_from_str(s: &str) -> miette::Result<Position> {
161 let parts: Vec<i32> = s.split(',').map(|s| s.parse().unwrap()).collect();
162 Ok(Position::new(parts[0], parts[1]))
163 }
164}
165
166#[derive(Debug, Default, derive_more::Deref, derive_more::DerefMut)]
167pub struct Visited<T>(HashMap<Position, T>);
168
169impl<T> Visited<T> {
170 pub fn new(k: Position, v: T) -> Self {
171 let mut map = HashMap::new();
172 map.insert(k, v).expect("unique key");
173
174 Self(map)
175 }
176}
177
178#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Ord, PartialOrd)]
179pub enum Direction {
180 Up,
182 Down,
184 Left,
186 Right
188}
189
190#[derive(Error, Diagnostic, Debug)]
191pub enum DirectionError {
192 #[error("Invalid direction character: {0}")]
193 #[diagnostic(code(direction::invalid_char))]
194 InvalidChar(char),
195
196 #[error("Invalid direction string: {0}")]
197 #[diagnostic(code(direction::invalid_str))]
198 InvalidStr(String),
199
200 #[error("Invalid direction mapping: Expected 4 unique directions")]
201 #[diagnostic(code(direction::invalid_mapping))]
202 InvalidMapping,
203
204 }
208
209impl FromStr for Direction {
210 type Err = DirectionError;
211
212 fn from_str(s: &str) -> miette::Result<Self, Self::Err> {
213 match s.to_lowercase().as_str() {
214 "^" | "up" | "a" | "north" | "n" => Ok(Direction::Up),
215 "v" | "down" | "x" | "south" | "s" => Ok(Direction::Down),
216 "<" | "left" | "#" | "west" | "w" => Ok(Direction::Left),
218 ">" | "right" | "o" | "east" | "e" => Ok(Direction::Right),
219 _ => Err(DirectionError::InvalidStr(s.to_string()))
220 }
221 }
222}
223
224impl Direction {
225 pub fn with_mapping4<T>(mapping: [T; 4]) -> impl Fn(&T) -> Result<Direction, DirectionError>
228 where
229 T: std::fmt::Display + Eq + Clone + std::hash::Hash,
230 {
231 move |s| {
238 if *s == mapping[0] { Ok(Direction::Up) }
239 else if *s == mapping[1] { Ok(Direction::Down) }
240 else if *s == mapping[2] { Ok(Direction::Left) }
241 else if *s == mapping[3] { Ok(Direction::Right) }
242 else { Err(DirectionError::InvalidStr(s.to_string())) }
243 }
244 }
245
246 pub fn parse(c: char) -> miette::Result<Self, DirectionError> {
248 match c.to_ascii_lowercase() {
249 '^' | 'a' | 'n' => Ok(Direction::Up),
250 'v' | 'x' | 's' => Ok(Direction::Down),
251 '<' | '#' | 'e' => Ok(Direction::Left),
253 '>' | 'o' | 'w' => Ok(Direction::Right),
255 _ => Err(DirectionError::InvalidChar(c))
256 }
257 }
258
259 fn opposite(&self) -> Direction {
260 match self {
261 Direction::Up => Direction::Down,
262 Direction::Down => Direction::Up,
263 Direction::Left => Direction::Right,
264 Direction::Right => Direction::Left
265 }
266 }
267
268 pub fn to_offset(&self) -> Position {
269 match self {
270 Direction::Up => Position::NEG_Y,
271 Direction::Down => Position::Y,
272 Direction::Left => Position::NEG_X,
273 Direction::Right => Position::X
274 }
275 }
276
277 pub fn turn_right(&self) -> Direction {
278 match self {
279 Direction::Up => Direction::Right,
280 Direction::Right => Direction::Down,
281 Direction::Down => Direction::Left,
282 Direction::Left => Direction::Up,
283 }
284 }
285
286 pub fn turn_left(&self) -> Direction {
287 self.turn_right().opposite()
288 }
289}
290
291#[cfg(test)]
292mod tests {
293 use super::*;
294
295
296
297}