1use std::fmt::Display;
2
3use thiserror::Error;
4
5pub const MAIN_DIRECTIONS: [Direction; 4] = [
6 Direction::Up,
7 Direction::Right,
8 Direction::Down,
9 Direction::Left,
10];
11
12pub const MINOR_DIRECTIONS: [Direction; 4] = [
13 Direction::UpRight,
14 Direction::DownRight,
15 Direction::DownLeft,
16 Direction::UpLeft,
17];
18
19pub trait PixelPositionInterface {
21 fn row(&self) -> usize;
23
24 fn column(&self) -> usize;
26
27 fn expand(&self) -> (usize, usize) {
29 (self.row(), self.column())
30 }
31
32 fn bound<const H: usize, const W: usize>(&self) -> StrictPositionValidationResult<H, W> {
36 PixelStrictPosition::new(self.row(), self.column())
37 }
38
39 fn up(&self, amount: usize) -> PixelPosition {
41 PixelPosition::new(
42 self.row().checked_sub(amount).unwrap_or_default(),
43 self.column(),
44 )
45 }
46
47 fn left(&self, amount: usize) -> PixelPosition {
49 PixelPosition::new(
50 self.row(),
51 self.column().checked_sub(amount).unwrap_or_default(),
52 )
53 }
54
55 fn down(&self, amount: usize) -> PixelPosition {
57 PixelPosition::new(self.row().wrapping_add(amount), self.column())
58 }
59
60 fn right(&self, amount: usize) -> PixelPosition {
62 PixelPosition::new(self.row(), self.column().wrapping_add(amount))
63 }
64
65 fn direction(&self, dir: Direction, amount: usize) -> PixelPosition {
67 match dir {
68 Direction::Up => self.up(amount),
69 Direction::Right => self.right(amount),
70 Direction::Down => self.down(amount),
71 Direction::Left => self.left(amount),
72 Direction::UpRight => self.up(amount).right(amount),
73 Direction::DownRight => self.down(amount).right(amount),
74 Direction::DownLeft => self.down(amount).left(amount),
75 Direction::UpLeft => self.up(amount).left(amount),
76 }
77 }
78}
79
80pub trait PixelStrictPositionInterface<const H: usize, const W: usize> {
84 fn row(&self) -> usize;
86
87 fn column(&self) -> usize;
89
90 fn expand(&self) -> (usize, usize) {
92 (self.row(), self.column())
93 }
94
95 fn unbound(&self) -> PixelPosition {
97 PixelPosition::new(self.row(), self.column())
98 }
99
100 fn checked_up(&self, amount: usize) -> StrictPositionValidationResult<H, W> {
102 self.unbound().up(amount).bound()
103 }
104
105 fn checked_left(&self, amount: usize) -> StrictPositionValidationResult<H, W> {
107 self.unbound().left(amount).bound()
108 }
109
110 fn checked_down(&self, amount: usize) -> StrictPositionValidationResult<H, W> {
112 self.unbound().down(amount).bound()
113 }
114
115 fn checked_right(&self, amount: usize) -> StrictPositionValidationResult<H, W> {
117 self.unbound().right(amount).bound()
118 }
119
120 fn checked_direction(
122 &self,
123 dir: Direction,
124 amount: usize,
125 ) -> StrictPositionValidationResult<H, W> {
126 self.unbound().direction(dir, amount).bound()
127 }
128
129 fn bounding_up(&self, amount: usize) -> PixelStrictPosition<H, W> {
131 self.checked_up(amount).unwrap_or_else(|e| e.adjust())
132 }
133
134 fn bounding_left(&self, amount: usize) -> PixelStrictPosition<H, W> {
136 self.checked_left(amount).unwrap_or_else(|e| e.adjust())
137 }
138
139 fn bounding_down(&self, amount: usize) -> PixelStrictPosition<H, W> {
141 self.checked_down(amount).unwrap_or_else(|e| e.adjust())
142 }
143
144 fn bounding_right(&self, amount: usize) -> PixelStrictPosition<H, W> {
146 self.checked_right(amount).unwrap_or_else(|e| e.adjust())
147 }
148
149 fn bounding_direction(&self, dir: Direction, amount: usize) -> PixelStrictPosition<H, W> {
151 self.checked_direction(dir, amount)
152 .unwrap_or_else(|e| e.adjust())
153 }
154}
155
156#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
157pub struct PixelPosition {
158 row: usize,
159 column: usize,
160}
161
162impl Display for PixelPosition {
163 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
164 write!(f, "({}, {})", self.row, self.column)
165 }
166}
167
168impl PixelPosition {
169 pub const fn new(row: usize, column: usize) -> Self {
170 Self { row, column }
171 }
172}
173
174impl PixelPositionInterface for PixelPosition {
175 fn row(&self) -> usize {
176 self.row
177 }
178
179 fn column(&self) -> usize {
180 self.column
181 }
182}
183
184pub type StrictPositionValidationResult<const H: usize, const W: usize> =
185 Result<PixelStrictPosition<H, W>, PixelPositionOutOfBoundError<H, W>>;
186
187#[derive(Debug, Error)]
188pub enum PixelPositionOutOfBoundError<const H: usize, const W: usize> {
189 #[error("The provided row value {0:?} is equal or more that row bound ({H}).")]
190 InvalidRow(PixelPosition),
191 #[error("The provided column value {0:?} is equal or more that column bound ({W}).")]
192 InvalidColumn(PixelPosition),
193 #[error("Both provided row and column values are out of bound ({H}, {W}).")]
194 InvalidBoth(PixelPosition),
195}
196
197impl<const H: usize, const W: usize> PixelPositionOutOfBoundError<H, W> {
198 pub fn validate_position(row: usize, column: usize) -> StrictPositionValidationResult<H, W> {
199 use std::cmp::Ordering::*;
200 use PixelPositionOutOfBoundError::*;
201
202 let raw_position = PixelPosition::new(row, column);
203 match (row.cmp(&H), column.cmp(&W)) {
204 (Equal | Greater, Less) => Err(InvalidRow(raw_position)),
205 (Less, Equal | Greater) => Err(InvalidColumn(raw_position)),
206 (Equal | Greater, Equal | Greater) => Err(InvalidBoth(raw_position)),
207 _ => Ok(PixelStrictPosition { raw: raw_position }),
208 }
209 }
210
211 pub fn adjust(&self) -> PixelStrictPosition<H, W> {
213 use PixelPositionOutOfBoundError::*;
214 match self {
215 InvalidRow(position) => PixelStrictPosition::new(H - 1, position.column).unwrap(),
216 InvalidColumn(position) => PixelStrictPosition::new(position.row, W - 1).unwrap(),
217 InvalidBoth(_) => PixelStrictPosition::new(H - 1, W - 1).unwrap(),
218 }
219 }
220}
221
222#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
223pub struct PixelStrictPosition<const H: usize, const W: usize> {
224 raw: PixelPosition,
225}
226
227impl<const H: usize, const W: usize> Iterator for PixelStrictPosition<H, W> {
228 type Item = PixelStrictPosition<H, W>;
229
230 fn next(&mut self) -> Option<Self::Item> {
231 let next = self.bounding_right(1);
233 if next.column() == self.column() {
234 let next = self.bounding_down(1);
236 if next.row() == self.row() {
237 None
239 } else {
240 *self = PixelStrictPosition {
242 raw: PixelPosition::new(next.row(), 0),
243 };
244 Some(self.clone())
245 }
246 } else {
247 *self = next.clone();
249 Some(next)
250 }
251 }
252}
253
254impl<const H: usize, const W: usize> PixelStrictPosition<H, W> {
255 pub fn new(row: usize, column: usize) -> Result<Self, PixelPositionOutOfBoundError<H, W>> {
259 PixelPositionOutOfBoundError::validate_position(row, column)
260 }
261}
262
263impl<const H: usize, const W: usize> PixelStrictPositionInterface<H, W>
264 for PixelStrictPosition<H, W>
265{
266 fn row(&self) -> usize {
267 self.raw.row
268 }
269
270 fn column(&self) -> usize {
271 self.raw.column
272 }
273}
274
275impl<const H: usize, const W: usize> PixelStrictPositionInterface<H, W>
276 for &PixelStrictPosition<H, W>
277{
278 fn row(&self) -> usize {
279 self.raw.row
280 }
281
282 fn column(&self) -> usize {
283 self.raw.column
284 }
285}
286
287impl<const H: usize, const W: usize> PixelStrictPositionInterface<H, W>
288 for &mut PixelStrictPosition<H, W>
289{
290 fn row(&self) -> usize {
291 self.raw.row
292 }
293
294 fn column(&self) -> usize {
295 self.raw.column
296 }
297}
298
299pub trait IntoPixelStrictPosition<const H: usize, const W: usize> {
300 fn into_pixel_strict_position(self) -> PixelStrictPosition<H, W>;
301}
302
303impl<const H: usize, const W: usize, T> IntoPixelStrictPosition<H, W> for T
304where
305 T: PixelStrictPositionInterface<H, W>,
306{
307 fn into_pixel_strict_position(self) -> PixelStrictPosition<H, W> {
308 PixelStrictPosition::new(self.row(), self.column()).unwrap()
309 }
310}
311
312impl<const H: usize, const W: usize> IntoPixelStrictPosition<H, W> for (usize, usize) {
313 fn into_pixel_strict_position(self) -> PixelStrictPosition<H, W> {
314 PixelStrictPosition::new(self.0, self.1).unwrap_or_else(|e| e.adjust())
315 }
316}
317
318impl<const H: usize, const W: usize> IntoPixelStrictPosition<H, W> for [usize; 2] {
319 fn into_pixel_strict_position(self) -> PixelStrictPosition<H, W> {
320 PixelStrictPosition::new(self[0], self[1]).unwrap_or_else(|e| e.adjust())
321 }
322}
323
324#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
327pub enum StrictPositions {
328 TopLeft,
330
331 TopRight,
333
334 BottomRight,
336
337 BottomLeft,
339
340 Center,
342
343 TopCenter,
345
346 RightCenter,
348
349 BottomCenter,
351
352 LeftCenter,
354}
355
356impl<const H: usize, const W: usize> PixelStrictPositionInterface<H, W> for StrictPositions {
357 fn row(&self) -> usize {
358 use StrictPositions::*;
359 match self {
360 TopLeft => 0,
361 TopRight => 0,
362 BottomRight => H - 1,
363 BottomLeft => H - 1,
364 Center => H / 2,
365 TopCenter => 0,
366 RightCenter => H / 2,
367 BottomCenter => H - 1,
368 LeftCenter => H / 2,
369 }
370 }
371
372 fn column(&self) -> usize {
373 use StrictPositions::*;
374 match self {
375 TopLeft => 0,
376 TopRight => W - 1,
377 BottomRight => W - 1,
378 BottomLeft => 0,
379 Center => W / 2,
380 TopCenter => W / 2,
381 RightCenter => W - 1,
382 BottomCenter => W / 2,
383 LeftCenter => 0,
384 }
385 }
386}
387
388#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
390pub enum Direction {
391 Up,
393
394 UpRight,
396
397 Right,
399
400 DownRight,
402
403 Down,
405
406 DownLeft,
407
408 Left,
410
411 UpLeft,
412}
413
414impl Iterator for Direction {
415 type Item = Direction;
416
417 fn next(&mut self) -> Option<Self::Item> {
418 use Direction::*;
419 match self {
420 Up => UpRight,
421 UpRight => Right,
422 Right => DownRight,
423 DownRight => Down,
424 Down => DownLeft,
425 DownLeft => Left,
426 Left => UpLeft,
427 UpLeft => Up,
428 }
429 .into()
430 }
431}
432
433pub struct SingleCycle {
434 initial_dir: Direction,
435 current: Option<Direction>,
436}
437
438impl SingleCycle {
439 pub fn new(initial_dir: Direction) -> Self {
440 Self {
441 initial_dir,
442 current: None,
443 }
444 }
445}
446
447impl Iterator for SingleCycle {
448 type Item = Direction;
449
450 fn next(&mut self) -> Option<Self::Item> {
451 if let Some(mut current) = self.current {
452 let next = current.next().unwrap();
453
454 if next == self.initial_dir {
455 return None;
456 }
457
458 self.current = Some(next);
459 self.current
460 } else {
461 self.current = Some(self.initial_dir);
462 Some(self.initial_dir)
463 }
464 }
465}
466
467#[cfg(test)]
468mod tests {
469 use super::*;
470
471 #[test]
472 fn test_name() {
473 let pos = PixelStrictPosition::<5, 5>::new(0, 0).unwrap();
474
475 assert_eq!(
476 PixelStrictPosition {
477 raw: PixelPosition::new(1, 0)
478 },
479 pos.bounding_down(1)
480 );
481 assert_eq!(
482 PixelStrictPosition {
483 raw: PixelPosition::new(2, 0)
484 },
485 pos.bounding_down(2)
486 );
487 assert_eq!(
488 PixelStrictPosition {
489 raw: PixelPosition::new(3, 0)
490 },
491 pos.bounding_down(3)
492 );
493 assert_eq!(
494 PixelStrictPosition {
495 raw: PixelPosition::new(4, 0)
496 },
497 pos.bounding_down(4)
498 );
499 assert_eq!(
500 PixelStrictPosition {
501 raw: PixelPosition::new(4, 0)
502 },
503 pos.bounding_down(5)
504 );
505 assert_eq!(
506 PixelStrictPosition {
507 raw: PixelPosition::new(0, 4)
508 },
509 pos.bounding_right(5)
510 );
511 }
512
513 #[test]
514 fn test_iter() {
515 let mut pos = PixelStrictPosition::<2, 2>::new(0, 0).unwrap();
516
517 assert_eq!(
518 Some(PixelStrictPosition {
519 raw: PixelPosition::new(0, 1)
520 }),
521 pos.next()
522 );
523 assert_eq!(
524 Some(PixelStrictPosition {
525 raw: PixelPosition::new(1, 0)
526 }),
527 pos.next()
528 );
529 assert_eq!(
530 Some(PixelStrictPosition {
531 raw: PixelPosition::new(1, 1)
532 }),
533 pos.next()
534 );
535 assert_eq!(None, pos.next());
536 }
537
538 #[test]
539 fn test_direction_single_cycle() {
540 use Direction::*;
541 let mut single = SingleCycle::new(Down);
542
543 assert_eq!(Some(Down), single.next());
544 assert_eq!(Some(DownLeft), single.next());
545 assert_eq!(Some(Left), single.next());
546 assert_eq!(Some(UpLeft), single.next());
547 assert_eq!(Some(Up), single.next());
548 assert_eq!(Some(UpRight), single.next());
549 assert_eq!(Some(Right), single.next());
550 assert_eq!(Some(DownRight), single.next());
551 assert_eq!(None, single.next());
552 }
553}