1use derive_more::{Add, AddAssign, Display, Sub, SubAssign};
2
3use crate::dir::Dir;
4
5#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy, Add, AddAssign, Sub, SubAssign, Display)]
7#[display("{x},{y}")]
8pub struct Pos {
9 pub x: isize,
11
12 pub y: isize,
14}
15
16impl Pos {
17 #[inline]
19 #[must_use]
20 pub const fn new(x: isize, y: isize) -> Self {
21 Self { x, y }
22 }
23
24 #[inline]
26 #[must_use]
27 pub const fn move_dir(&self, dir: Dir) -> Self {
28 match dir {
29 Dir::S => Self::new(self.x + 1, self.y),
30 Dir::E => Self::new(self.x, self.y + 1),
31 Dir::N => Self::new(self.x - 1, self.y),
32 Dir::W => Self::new(self.x, self.y - 1),
33 }
34 }
35
36 #[inline]
38 pub const fn move_dir_mut(&mut self, dir: Dir) {
39 match dir {
40 Dir::S => self.x += 1,
41 Dir::E => self.y += 1,
42 Dir::N => self.x -= 1,
43 Dir::W => self.y -= 1,
44 }
45 }
46
47 #[inline]
50 #[must_use]
51 pub const fn in_bounds(&self, bounds: (Self, Self)) -> bool {
52 bounds.0.x <= self.x && self.x <= bounds.1.x && bounds.0.y <= self.y && self.y <= bounds.1.y
53 }
54
55 #[inline]
57 #[must_use]
58 pub const fn manhattan_distance(&self, other: Self) -> usize {
59 self.x.abs_diff(other.x) + self.y.abs_diff(other.y)
60 }
61
62 #[inline]
64 pub fn adjacent(&self) -> impl Iterator<Item = Self> + use<> {
65 [
66 Self::new(self.x + 1, self.y),
67 Self::new(self.x, self.y + 1),
68 Self::new(self.x - 1, self.y),
69 Self::new(self.x, self.y - 1),
70 ]
71 .into_iter()
72 }
73
74 #[inline]
76 pub fn corners(&self) -> impl Iterator<Item = Self> + use<> {
77 [
78 Self::new(self.x + 1, self.y + 1),
79 Self::new(self.x - 1, self.y + 1),
80 Self::new(self.x - 1, self.y - 1),
81 Self::new(self.x + 1, self.y - 1),
82 ]
83 .into_iter()
84 }
85
86 #[inline]
88 pub fn neighbors(&self) -> impl Iterator<Item = Self> + use<> {
89 self.adjacent().chain(self.corners())
90 }
91}
92
93impl From<(isize, isize)> for Pos {
94 #[inline]
95 fn from(value: (isize, isize)) -> Self {
96 Self::new(value.0, value.1)
97 }
98}
99
100impl From<(usize, usize)> for Pos {
101 #[inline]
102 fn from(value: (usize, usize)) -> Self {
103 #[allow(clippy::cast_possible_wrap)]
104 Self::new(value.0 as isize, value.1 as isize)
105 }
106}
107
108impl From<Pos> for (usize, usize) {
109 #[inline]
110 fn from(val: Pos) -> Self {
111 #[allow(clippy::cast_sign_loss)]
112 (val.x as usize, val.y as usize)
113 }
114}
115
116pub trait PosGet<V> {
118 fn pos_get(&self, pos: Pos) -> Option<&V>;
121
122 fn pos_get_mut(&mut self, pos: Pos) -> Option<&mut V>;
125}
126
127#[cfg(test)]
128mod tests {
129 use rustc_hash::FxHashSet;
130
131 use super::Pos;
132 use crate::dir::Dir::*;
133
134 #[test]
138 fn new() {
139 let input: (isize, isize) = (1, 2);
140 let expected = (1, 2);
141 let pos = Pos::new(input.0, input.1);
142 let output = (pos.x, pos.y);
143 assert_eq!(expected, output, "\n input: {input:?}");
144 }
145
146 #[test]
147 fn move_dir() {
148 let input = [S, E, N, W];
149 let expected = [
150 Pos::new(2, 2),
151 Pos::new(1, 3),
152 Pos::new(0, 2),
153 Pos::new(1, 1),
154 ];
155 let pos = Pos::new(1, 2);
156 let output = input.map(|d| pos.move_dir(d));
157 assert_eq!(expected, output, "\n input: {input:?}");
158 }
159
160 #[test]
161 fn move_dir_mut() {
162 let input = [
163 (Pos::new(0, 1), S),
164 (Pos::new(1, 1), E),
165 (Pos::new(1, 2), N),
166 (Pos::new(2, 2), W),
167 ];
168 let expected = [
169 (Pos::new(1, 1), S),
170 (Pos::new(1, 2), E),
171 (Pos::new(0, 2), N),
172 (Pos::new(2, 1), W),
173 ];
174 let mut output = input;
175 for (pos, dir) in &mut output {
176 pos.move_dir_mut(*dir);
177 }
178 assert_eq!(expected, output, "\n input: {input:?}");
179 }
180
181 #[test]
182 fn in_bounds() {
183 let input = (
184 (Pos::new(1, 2), Pos::new(4, 7)),
185 [
186 Pos::new(2, 5),
187 Pos::new(1, 5),
188 Pos::new(4, 5),
189 Pos::new(2, 2),
190 Pos::new(2, 7),
191 Pos::new(0, 5),
192 Pos::new(5, 5),
193 Pos::new(2, 1),
194 Pos::new(2, 8),
195 ],
196 );
197 let expected = [true, true, true, true, true, false, false, false, false];
198 let output = input.1.map(|p| p.in_bounds(input.0));
199 assert_eq!(expected, output, "\n input: {input:?}");
200 }
201
202 #[test]
203 fn manhattan_distance() {
204 let input = [
205 (Pos::new(1, 2), Pos::new(3, 4)),
206 (Pos::new(-4, 3), Pos::new(2, -1)),
207 (Pos::new(0, 0), Pos::new(-1, 1)),
208 (Pos::new(4, -3), Pos::new(-2, 2)),
209 ];
210 let expected = [4, 10, 2, 11];
211 let output = input.map(|(p, q)| p.manhattan_distance(q));
212 assert_eq!(expected, output, "\n input: {input:?}");
213 }
214
215 #[test]
216 fn adjacent() {
217 let input = [
218 Pos::new(0, 0),
219 Pos::new(4, 5),
220 Pos::new(-1, 3),
221 Pos::new(0, -2),
222 ];
223 let expected = [
224 FxHashSet::from_iter([
225 Pos::new(1, 0),
226 Pos::new(0, 1),
227 Pos::new(-1, 0),
228 Pos::new(0, -1),
229 ]),
230 FxHashSet::from_iter([
231 Pos::new(5, 5),
232 Pos::new(4, 6),
233 Pos::new(3, 5),
234 Pos::new(4, 4),
235 ]),
236 FxHashSet::from_iter([
237 Pos::new(0, 3),
238 Pos::new(-1, 4),
239 Pos::new(-2, 3),
240 Pos::new(-1, 2),
241 ]),
242 FxHashSet::from_iter([
243 Pos::new(1, -2),
244 Pos::new(0, -1),
245 Pos::new(-1, -2),
246 Pos::new(0, -3),
247 ]),
248 ];
249 let output = input.map(|p| p.adjacent().collect());
250 assert_eq!(expected, output, "\n input: {input:?}");
251 }
252
253 #[test]
254 fn corners() {
255 let input = [
256 Pos::new(0, 0),
257 Pos::new(4, 5),
258 Pos::new(-1, 3),
259 Pos::new(0, -2),
260 ];
261 let expected = [
262 FxHashSet::from_iter([
263 Pos::new(1, 1),
264 Pos::new(-1, 1),
265 Pos::new(-1, -1),
266 Pos::new(1, -1),
267 ]),
268 FxHashSet::from_iter([
269 Pos::new(5, 6),
270 Pos::new(3, 6),
271 Pos::new(3, 4),
272 Pos::new(5, 4),
273 ]),
274 FxHashSet::from_iter([
275 Pos::new(0, 4),
276 Pos::new(-2, 4),
277 Pos::new(-2, 2),
278 Pos::new(0, 2),
279 ]),
280 FxHashSet::from_iter([
281 Pos::new(1, -1),
282 Pos::new(-1, -1),
283 Pos::new(-1, -3),
284 Pos::new(1, -3),
285 ]),
286 ];
287 let output = input.map(|p| p.corners().collect());
288 assert_eq!(expected, output, "\n input: {input:?}");
289 }
290
291 #[test]
292 fn neighbors() {
293 let input = [Pos::new(4, 5), Pos::new(-1, 3)];
294 let expected = [
295 FxHashSet::from_iter([
296 Pos::new(5, 5),
297 Pos::new(4, 6),
298 Pos::new(3, 5),
299 Pos::new(4, 4),
300 Pos::new(5, 6),
301 Pos::new(3, 6),
302 Pos::new(3, 4),
303 Pos::new(5, 4),
304 ]),
305 FxHashSet::from_iter([
306 Pos::new(0, 3),
307 Pos::new(-1, 4),
308 Pos::new(-2, 3),
309 Pos::new(-1, 2),
310 Pos::new(0, 4),
311 Pos::new(-2, 4),
312 Pos::new(-2, 2),
313 Pos::new(0, 2),
314 ]),
315 ];
316 let output = input.map(|p| p.neighbors().collect());
317 assert_eq!(expected, output, "\n input: {input:?}");
318 }
319
320 #[test]
324 fn pos_from_isize_tuple() {
325 let input: (isize, isize) = (1, 2);
326 let expected = Pos::new(1, 2);
327 let output = Pos::from(input);
328 assert_eq!(expected, output, "\n input: {input:?}");
329 }
330
331 #[test]
332 fn pos_from_usize_tuple() {
333 let input: (usize, usize) = (1, 2);
334 let expected = Pos::new(1, 2);
335 let output = Pos::from(input);
336 assert_eq!(expected, output, "\n input: {input:?}");
337 }
338
339 #[test]
340 fn usize_tuple_from_pos() {
341 let input = Pos::new(1, 2);
342 let expected = (1, 2);
343 let output = From::from(input);
344 assert_eq!(expected, output, "\n input: {input:?}");
345 }
346}