balanced_direction/
lib.rs

1//! This module provides an enum `Balance` that models a position within a 3x3 grid,
2//! along with several utility methods to manipulate these positions.
3//!
4//! The `Balance` enum is intended for scenarios where balanced ternary logic or
5//! grid-based movement is required. It represents nine possible positions in a
6//! 3x3 grid, with the central point being `(0, 0)` and other points positioned
7//! as offsets relative to this center.
8//!
9//! ## Main Features
10//!
11//! - `Balance` enum variants represent specific positions in the grid, such as
12//!   `TopLeft`, `Center`, or `BottomRight`.
13//! - Methods to convert between `Balance` variants and their 2D vector representations.
14//! - Utility methods to move a position in the grid (e.g., `up`, `down`, `left`, `right`).
15//!
16//! ## Usage Examples
17//!
18//! Basic usage of `Balance` to convert between variants and vectors:
19//!
20//! ```rust
21//! use balanced_direction::Balance;
22//!
23//! let position = Balance::TopLeft;
24//! assert_eq!(position.to_vector(), (-1, -1));
25//!
26//! let center = Balance::Center;
27//! assert_eq!(center.to_vector(), (0, 0));
28//!
29//! let balance = Balance::from_vector(-1, -1);
30//! assert_eq!(balance, Balance::TopLeft);
31//! ```
32//!
33//! Moving positions in the grid:
34//!
35//! ```rust
36//! use balanced_direction::Balance;
37//!
38//! let balance = Balance::Center;
39//! assert_eq!(balance.up(), Balance::Top);
40//! assert_eq!(balance.down(), Balance::Bottom);
41//! assert_eq!(balance.left(), Balance::Left);
42//! assert_eq!(balance.right(), Balance::Right);
43//! ```
44#![cfg_attr(not(test), no_std)]
45extern crate alloc;
46
47use alloc::vec::Vec;
48use core::ops::{Add, Mul, Neg, Not, Sub};
49
50/// Represents a position within a 3x3 grid, with each variant corresponding to a specific point.
51///
52/// The `Balance` enum is used to model a balanced ternary direction or position
53/// within a 2D grid. Each variant represents one of the nine possible positions
54/// in the grid, where the center (`Balance::Center`) is `(0, 0)` and the
55/// surrounding positions are offsets from this central point.
56///
57/// # Variants
58///
59/// - `TopLeft`: The position at `(-1, -1)`
60/// - `Top`: The position at `(0, -1)`
61/// - `TopRight`: The position at `(1, -1)`
62/// - `Left`: The position at `(-1, 0)`
63/// - `Center`: The central position `(0, 0)`
64/// - `Right`: The position at `(1, 0)`
65/// - `BottomLeft`: The position at `(-1, 1)`
66/// - `Bottom`: The position at `(0, 1)`
67/// - `BottomRight`: The position at `(1, 1)`
68///
69/// # Examples
70///
71/// ```
72/// use balanced_direction::Balance;
73///
74/// let position = Balance::TopLeft;
75/// assert_eq!(position.to_vector(), (-1, -1));
76///
77/// let center = Balance::Center;
78/// assert_eq!(center.to_vector(), (0, 0));
79/// ```
80#[derive(Debug, PartialEq, Clone, Copy, Eq, Hash)]
81pub enum Balance {
82    TopLeft,
83    Top,
84    TopRight,
85    Left,
86    Center,
87    Right,
88    BottomLeft,
89    Bottom,
90    BottomRight,
91}
92
93impl Balance {
94    /// Converts the current `Balance` variant into a 2D vector `(i8, i8)` representing its coordinates.
95    ///
96    /// # Returns
97    ///
98    /// A tuple `(i8, i8)` representing the position in the 3x3 grid.
99    ///
100    /// # Examples
101    ///
102    /// ```
103    /// use balanced_direction::Balance;
104    ///
105    /// let position = Balance::TopLeft;
106    /// assert_eq!(position.to_vector(), (-1, -1));
107    ///
108    /// let center = Balance::Center;
109    /// assert_eq!(center.to_vector(), (0, 0));
110    /// ```
111    pub fn to_vector(self) -> (i8, i8) {
112        match self {
113            Balance::TopLeft => (-1, -1),
114            Balance::Top => (0, -1),
115            Balance::TopRight => (1, -1),
116            Balance::Left => (-1, 0),
117            Balance::Center => (0, 0),
118            Balance::Right => (1, 0),
119            Balance::BottomLeft => (-1, 1),
120            Balance::Bottom => (0, 1),
121            Balance::BottomRight => (1, 1),
122        }
123    }
124
125    /// Calculates the scalar magnitude squared for the vector representation
126    /// of the current `Balance` position within the grid.
127    ///
128    /// The scalar magnitude squared is defined as `x^2 + y^2`, where `(x, y)`
129    /// are the coordinates of the position.
130    ///
131    /// # Returns
132    ///
133    /// An `i8` value representing the scalar magnitude squared of the position.
134    ///
135    /// # Examples
136    ///
137    /// ```
138    /// use balanced_direction::Balance;
139    ///
140    /// let position = Balance::TopLeft;
141    /// assert_eq!(position.to_scalar(), 2);
142    ///
143    /// let center = Balance::Center;
144    /// assert_eq!(center.to_scalar(), 0);
145    /// ```
146    pub fn to_scalar(self) -> i8 {
147        let (x, y) = self.to_vector();
148        x * x + y * y
149    }
150
151    /// Calculates the Euclidean (or absolute) magnitude of the vector representation
152    /// of the current `Balance` position.
153    ///
154    /// The magnitude is defined as the square root of the scalar magnitude squared (`√(x² + y²)`),
155    /// where `(x, y)` are the coordinates of the position.
156    ///
157    /// # Returns
158    ///
159    /// A `f32` value representing the Euclidean magnitude of the position.
160    ///
161    /// # Examples
162    ///
163    /// ```
164    /// use balanced_direction::Balance;
165    ///
166    /// let position = Balance::TopLeft;
167    /// assert!((position.to_magnitude() - 2.0f32.sqrt()).abs() < 1e-6);
168    ///
169    /// let center = Balance::Center;
170    /// assert_eq!(center.to_magnitude(), 0.0);
171    /// ```
172    pub fn to_magnitude(self) -> f32 {
173        #[allow(unused_imports)]
174        use micromath::F32Ext;
175        (self.to_scalar() as f32).sqrt()
176    }
177
178    /// Converts the current `Balance` position into its corresponding
179    /// angle in degrees in a Cartesian coordinate system.
180    ///
181    /// The angle is calculated in a counter-clockwise direction starting from
182    /// the positive x-axis, with `(-y, x)` treated as the vector
183    /// direction. The angle is returned in the range `[-180.0, 180.0]` degrees.
184    ///
185    /// # Returns
186    ///
187    /// A `f32` value representing the angle in degrees.
188    ///
189    /// # Examples
190    ///
191    /// ```
192    /// use balanced_direction::Balance;
193    ///
194    /// let position = Balance::Top;
195    /// assert_eq!(position.to_angle(), 90.0);
196    /// ```
197    pub fn to_angle(self) -> f32 {
198        #[allow(unused_imports)]
199        use micromath::F32Ext;
200        let (x, y) = self.to_vector();
201        let angle = (-y as f32).atan2(x as f32);
202        angle.to_degrees()
203    }
204
205    /// Constructs a `Balance` enum variant based on the given angle in degrees.
206    ///
207    /// The angle is treated in the Cartesian coordinate system, where:
208    /// - `0` degrees corresponds to `Balance::Right` (positive x-axis),
209    /// - Positive angles proceed counterclockwise, and negative angles proceed clockwise,
210    /// - The `angle` is normalized into the range `[-180.0, 180.0]` and converted into
211    ///   the nearest discrete position `(x, y)` on the 3x3 grid.
212    ///
213    /// # Parameters
214    ///
215    /// - `angle`: A `f32` value representing the angle in degrees.
216    ///
217    /// # Returns
218    ///
219    /// A `Balance` enum variant corresponding to the direction indicated by the angle.
220    ///
221    /// # Examples
222    ///
223    /// ```
224    /// use balanced_direction::Balance;
225    ///
226    /// let balance = Balance::from_angle(45.0);
227    /// assert_eq!(balance, Balance::TopRight);
228    ///
229    /// let balance = Balance::from_angle(-135.0);
230    /// assert_eq!(balance, Balance::BottomLeft);
231    /// ```
232    pub fn from_angle(angle: f32) -> Self {
233        #[allow(unused_imports)]
234        use micromath::F32Ext;
235        let angle = angle.to_radians();
236        let x = angle.cos();
237        let y = -angle.sin();
238        let (x, y) = (x.round() as i8, y.round() as i8);
239        Self::from_vector(x, y)
240    }
241
242    /// Returns the x-coordinate of the current `Balance` position in the 3x3 grid.
243    ///
244    /// # Returns
245    ///
246    /// An `i8` value representing the x-coordinate of the position.
247    ///
248    /// # Examples
249    ///
250    /// ```
251    /// use balanced_direction::Balance;
252    ///
253    /// let position = Balance::Right;
254    /// assert_eq!(position.x(), 1);
255    ///
256    /// let position = Balance::Center;
257    /// assert_eq!(position.x(), 0);
258    /// ```
259    pub fn x(self) -> i8 {
260        let (x, _) = self.to_vector();
261        x
262    }
263
264    /// Returns the y-coordinate of the current `Balance` position in the 3x3 grid.
265    ///
266    /// # Returns
267    ///
268    /// An `i8` value representing the y-coordinate of the position.
269    ///
270    /// # Examples
271    ///
272    /// ```
273    /// use balanced_direction::Balance;
274    ///
275    /// let position = Balance::Bottom;
276    /// assert_eq!(position.y(), 1);
277    ///
278    /// let position = Balance::Center;
279    /// assert_eq!(position.y(), 0);
280    /// ```
281    pub fn y(self) -> i8 {
282        let (_, y) = self.to_vector();
283        y
284    }
285
286    /// Converts a pair of integers `(a, b)` into the corresponding `Balance` variant.
287    ///
288    /// # Parameters
289    ///
290    /// - `a`: The x-coordinate in the 2D grid, expected to be in the range `-1..=1`.
291    /// - `b`: The y-coordinate in the 2D grid, expected to be in the range `-1..=1`.
292    ///
293    /// # Returns
294    ///
295    /// The `Balance` variant that corresponds to the provided `(a, b)` coordinates.
296    ///
297    /// # Panics
298    ///
299    /// Panics if the provided `(a, b)` pair does not correspond to a valid `Balance` variant.
300    ///
301    /// # Examples
302    ///
303    /// ```
304    /// use balanced_direction::Balance;
305    ///
306    /// let balance = Balance::from_vector(-1, -1);
307    /// assert_eq!(balance, Balance::TopLeft);
308    ///
309    /// let balance = Balance::from_vector(0, 1);
310    /// assert_eq!(balance, Balance::Bottom);
311    /// ```
312    pub fn from_vector(a: i8, b: i8) -> Self {
313        match (a, b) {
314            (-1, -1) => Balance::TopLeft,
315            (0, -1) => Balance::Top,
316            (1, -1) => Balance::TopRight,
317            (-1, 0) => Balance::Left,
318            (0, 0) => Balance::Center,
319            (1, 0) => Balance::Right,
320            (-1, 1) => Balance::BottomLeft,
321            (0, 1) => Balance::Bottom,
322            (1, 1) => Balance::BottomRight,
323            _ => panic!("Invalid vector"),
324        }
325    }
326
327    /// Moves the current position upwards in the 3x3 grid while staying within bounds.
328    ///
329    /// # Returns
330    ///
331    /// The `Balance` variant representing the position directly above the current one.
332    /// If the current position is at the top edge, the result will stay at the edge.
333    ///
334    /// # Examples
335    ///
336    /// ```
337    /// use balanced_direction::Balance;
338    ///
339    /// let balance = Balance::Center;
340    /// assert_eq!(balance.up(), Balance::Top);
341    ///
342    /// let balance = Balance::Top;
343    /// assert_eq!(balance.up(), Balance::Top);
344    /// ```
345    pub fn up(self) -> Self {
346        let (x, y) = self.to_vector();
347        Self::from_vector(x, (y - 1).clamp(-1, 1))
348    }
349
350    /// Moves the current position downwards in the 3x3 grid while staying within bounds.
351    ///
352    /// # Returns
353    ///
354    /// The `Balance` variant representing the position directly below the current one.
355    /// If the current position is at the bottom edge, the result will stay at the edge.
356    ///
357    /// # Examples
358    ///
359    /// ```
360    /// use balanced_direction::Balance;
361    ///
362    /// let balance = Balance::Center;
363    /// assert_eq!(balance.down(), Balance::Bottom);
364    ///
365    /// let balance = Balance::Bottom;
366    /// assert_eq!(balance.down(), Balance::Bottom);
367    /// ```
368    pub fn down(self) -> Self {
369        let (x, y) = self.to_vector();
370        Self::from_vector(x, (y + 1).clamp(-1, 1))
371    }
372
373    /// Moves the current position to the left in the 3x3 grid while staying within bounds.
374    ///
375    /// # Returns
376    ///
377    /// The `Balance` variant representing the position directly to the left of the current one.
378    /// If the current position is at the left edge, the result will stay at the edge.
379    ///
380    /// # Examples
381    ///
382    /// ```
383    /// use balanced_direction::Balance;
384    ///
385    /// let balance = Balance::Center;
386    /// assert_eq!(balance.left(), Balance::Left);
387    ///
388    /// let balance = Balance::Left;
389    /// assert_eq!(balance.left(), Balance::Left);
390    /// ```
391    pub fn left(self) -> Self {
392        let (x, y) = self.to_vector();
393        Self::from_vector((x - 1).clamp(-1, 1), y)
394    }
395
396    /// Moves the current position to the right in the 3x3 grid while staying within bounds.
397    ///
398    /// # Returns
399    ///
400    /// The `Balance` variant representing the position directly to the right of the current one.
401    /// If the current position is at the right edge, the result will stay at the edge.
402    ///
403    /// # Examples
404    ///
405    /// ```
406    /// use balanced_direction::Balance;
407    ///
408    /// let balance = Balance::Center;
409    /// assert_eq!(balance.right(), Balance::Right);
410    ///
411    /// let balance = Balance::Right;
412    /// assert_eq!(balance.right(), Balance::Right);
413    /// ```
414    pub fn right(self) -> Self {
415        let (x, y) = self.to_vector();
416        Self::from_vector((x + 1).clamp(-1, 1), y)
417    }
418
419    /// Moves the position upwards in the 3x3 grid with wrapping behavior.
420    pub fn up_wrap(self) -> Self {
421        let (x, y) = self.to_vector();
422        Self::from_vector(x, if y == -1 { 1 } else { y - 1 })
423    }
424
425    /// Moves the position downwards in the 3x3 grid with wrapping behavior.
426    pub fn down_wrap(self) -> Self {
427        let (x, y) = self.to_vector();
428        Self::from_vector(x, if y == 1 { -1 } else { y + 1 })
429    }
430
431    /// Moves the position leftwards in the 3x3 grid with wrapping behavior.
432    pub fn left_wrap(self) -> Self {
433        let (x, y) = self.to_vector();
434        Self::from_vector(if x == -1 { 1 } else { x - 1 }, y)
435    }
436
437    /// Moves the position rightwards in the 3x3 grid with wrapping behavior.
438    pub fn right_wrap(self) -> Self {
439        let (x, y) = self.to_vector();
440        Self::from_vector(if x == 1 { -1 } else { x + 1 }, y)
441    }
442
443    /// Flips the current position horizontally in the 3x3 grid.
444    ///
445    /// # Returns
446    ///
447    /// The `Balance` variant that is mirrored horizontally across
448    /// the vertical axis. For example, flipping `Balance::Left` results
449    /// in `Balance::Right`, and vice-versa. Positions on the vertical axis
450    /// (like `Balance::Center` or `Balance::Top`) remain unchanged.
451    ///
452    /// # Examples
453    ///
454    /// ```
455    /// use balanced_direction::Balance;
456    ///
457    /// let balance = Balance::Left;
458    /// assert_eq!(balance.flip_h(), Balance::Right);
459    ///
460    /// let balance = Balance::Center;
461    /// assert_eq!(balance.flip_h(), Balance::Center);
462    /// ```
463    pub fn flip_h(self) -> Self {
464        let (x, y) = self.to_vector();
465        Self::from_vector(-x, y)
466    }
467
468    /// Flips the current position vertically in the 3x3 grid.
469    ///
470    /// # Returns
471    ///
472    /// The `Balance` variant that is mirrored vertically across
473    /// the horizontal axis. For example, flipping `Balance::Top`
474    /// results in `Balance::Bottom`, and vice-versa. Positions on the horizontal axis
475    /// (like `Balance::Center` or `Balance::Left`) remain unchanged.
476    ///
477    /// # Examples
478    ///
479    /// ```
480    /// use balanced_direction::Balance;
481    ///
482    /// let balance = Balance::Top;
483    /// assert_eq!(balance.flip_v(), Balance::Bottom);
484    ///
485    /// let balance = Balance::Center;
486    /// assert_eq!(balance.flip_v(), Balance::Center);
487    /// ```
488    pub fn flip_v(self) -> Self {
489        let (x, y) = self.to_vector();
490        Self::from_vector(x, -y)
491    }
492
493    /// Rotates the current position 90 degrees counterclockwise in the 3x3 grid.
494    ///
495    /// # Returns
496    ///
497    /// The `Balance` variant representing the position after a 90-degree counterclockwise
498    /// rotation around the center. For example, rotating `Balance::Right` counterclockwise
499    /// will result in `Balance::Top`, and `Balance::Top` will result in `Balance::Left`.
500    /// The center position (`Balance::Center`) remains unchanged.
501    ///
502    /// # Examples
503    ///
504    /// ```
505    /// use balanced_direction::Balance;
506    ///
507    /// let balance = Balance::Right;
508    /// assert_eq!(balance.rotate_left(), Balance::Top);
509    ///
510    /// let balance = Balance::Center;
511    /// assert_eq!(balance.rotate_left(), Balance::Center);
512    ///
513    /// let balance = Balance::Top;
514    /// assert_eq!(balance.rotate_left(), Balance::Left);
515    /// ```
516    pub fn rotate_left(self) -> Self {
517        let (x, y) = self.to_vector();
518        Self::from_vector(y, -x)
519    }
520
521    /// Rotates the current position 90 degrees clockwise in the 3x3 grid.
522    ///
523    /// # Returns
524    ///
525    /// The `Balance` variant representing the position after a 90-degree clockwise
526    /// rotation around the center. For example, rotating `Balance::Top` clockwise
527    /// results in `Balance::Right`, and `Balance::Right` will result in `Balance::Bottom`.
528    /// The center position (`Balance::Center`) remains unchanged.
529    ///
530    /// # Examples
531    ///
532    /// ```
533    /// use balanced_direction::Balance;
534    ///
535    /// let balance = Balance::Top;
536    /// assert_eq!(balance.rotate_right(), Balance::Right);
537    ///
538    /// let balance = Balance::Center;
539    /// assert_eq!(balance.rotate_right(), Balance::Center);
540    ///
541    /// let balance = Balance::Right;
542    /// assert_eq!(balance.rotate_right(), Balance::Bottom);
543    /// ```
544    pub fn rotate_right(self) -> Self {
545        let (x, y) = self.to_vector();
546        Self::from_vector(-y, x)
547    }
548
549    /// Checks if the current position has the `Balance::Top` variant or any variant
550    /// that includes the top row in the 3x3 grid.
551    pub fn has_top(self) -> bool {
552        matches!(self, Balance::Top | Balance::TopLeft | Balance::TopRight)
553    }
554
555    /// Checks if the current position has the `Balance::Bottom` variant or any variant
556    /// that includes the bottom row in the 3x3 grid.
557    pub fn has_bottom(self) -> bool {
558        matches!(
559            self,
560            Balance::Bottom | Balance::BottomLeft | Balance::BottomRight
561        )
562    }
563
564    /// Checks if the current position has the `Balance::Bottom` variant or any variant
565    /// that includes the bottom row in the 3x3 grid.
566    pub fn has_left(self) -> bool {
567        matches!(self, Balance::Left | Balance::TopLeft | Balance::BottomLeft)
568    }
569
570    /// Checks if the current position has the `Balance::Left` variant or any variant
571    /// that includes the left column in the 3x3 grid.
572    pub fn has_right(self) -> bool {
573        matches!(
574            self,
575            Balance::Right | Balance::TopRight | Balance::BottomRight
576        )
577    }
578
579    /// Checks if the current position includes the center or any direct neighbor
580    /// (top, bottom, left, or right) in the 3x3 grid.
581    pub fn is_orthogonal(self) -> bool {
582        matches!(
583            self,
584            Balance::Center | Balance::Top | Balance::Bottom | Balance::Left | Balance::Right
585        )
586    }
587
588    /// Checks if the current position includes the center or any indirect neighbor
589    /// (corners) in the 3x3 grid.
590    pub fn is_diagonal(self) -> bool {
591        matches!(
592            self,
593            Balance::Center
594                | Balance::TopLeft
595                | Balance::TopRight
596                | Balance::BottomLeft
597                | Balance::BottomRight
598        )
599    }
600
601    /// Determines whether the current position is one of the edge positions
602    /// (top, bottom, left, or right) in the 3x3 grid.
603    pub fn is_edge(self) -> bool {
604        matches!(
605            self,
606            Balance::Top | Balance::Bottom | Balance::Left | Balance::Right
607        )
608    }
609
610    /// Checks if the current position is one of the corner positions
611    /// (top-left, top-right, bottom-left, or bottom-right) in the 3x3 grid.
612    pub fn is_corner(self) -> bool {
613        matches!(
614            self,
615            Balance::TopLeft | Balance::TopRight | Balance::BottomLeft | Balance::BottomRight
616        )
617    }
618}
619
620impl Not for Balance {
621    type Output = Self;
622
623    fn not(self) -> Self::Output {
624        let (x, y) = self.to_vector();
625        Self::from_vector(y, x)
626    }
627}
628
629impl Neg for Balance {
630    type Output = Self;
631
632    fn neg(self) -> Self::Output {
633        let (x, y) = self.to_vector();
634        Balance::from_vector(-x, -y)
635    }
636}
637
638impl Add for Balance {
639    type Output = Self;
640
641    fn add(self, rhs: Self) -> Self::Output {
642        let (x1, y1) = self.to_vector();
643        let (x2, y2) = rhs.to_vector();
644        Balance::from_vector((x1 + x2).clamp(-1, 1), (y1 + y2).clamp(-1, 1))
645    }
646}
647
648impl Mul for Balance {
649    type Output = Self;
650    fn mul(self, rhs: Self) -> Self::Output {
651        let (x1, y1) = self.to_vector();
652        let (x2, y2) = rhs.to_vector();
653        Balance::from_vector(x1 * x2, y1 * y2)
654    }
655}
656
657impl Sub for Balance {
658    type Output = Self;
659    fn sub(self, rhs: Self) -> Self::Output {
660        let (x1, y1) = self.to_vector();
661        let (x2, y2) = rhs.to_vector();
662        Self::from_vector((x1 - x2).clamp(-1, 1), (y1 - y2).clamp(-1, 1))
663    }
664}
665
666#[cfg(feature = "ternary")]
667mod ternary {
668    use super::Balance;
669    use balanced_ternary::Digit;
670    use core::ops::{BitAnd, BitOr, BitXor};
671
672    impl BitAnd for Balance {
673        type Output = Self;
674        fn bitand(self, rhs: Self) -> Self::Output {
675            let (x1, y1) = self.to_ternary_pair();
676            let (x2, y2) = rhs.to_ternary_pair();
677            Balance::from_ternary_pair(x1 & x2, y1 & y2)
678        }
679    }
680
681    impl BitOr for Balance {
682        type Output = Self;
683        fn bitor(self, rhs: Self) -> Self::Output {
684            let (x1, y1) = self.to_ternary_pair();
685            let (x2, y2) = rhs.to_ternary_pair();
686            Balance::from_ternary_pair(x1 | x2, y1 | y2)
687        }
688    }
689
690    impl BitXor for Balance {
691        type Output = Self;
692        fn bitxor(self, rhs: Self) -> Self::Output {
693            let (x1, y1) = self.to_ternary_pair();
694            let (x2, y2) = rhs.to_ternary_pair();
695            Balance::from_ternary_pair(x1 ^ x2, y1 ^ y2)
696        }
697    }
698
699    impl Balance {
700        /// Converts the `Balance` position into a pair of ternary digits.
701        ///
702        /// # Returns
703        ///
704        /// A tuple containing two `Digit` values. The first element represents
705        /// the x-coordinate and the second represents the y-coordinate of the `Balance`
706        /// position in the ternary numeral system.
707        ///
708        /// The `Digit` values can range from `Neg` (-1), `Zero` (0), to `Pos` (1),
709        /// matching the 3x3 balanced grid's coordinate representation.
710        ///
711        /// # Examples
712        ///
713        /// ```
714        /// use balanced_direction::Balance;
715        /// use balanced_ternary::Digit;
716        ///
717        /// let balance = Balance::Top;
718        /// assert_eq!(balance.to_ternary_pair(), (Digit::Zero, Digit::Neg));
719        ///
720        /// let balance = Balance::Right;
721        /// assert_eq!(balance.to_ternary_pair(), (Digit::Pos, Digit::Zero));
722        /// ```
723        pub fn to_ternary_pair(self) -> (Digit, Digit) {
724            (Digit::from_i8(self.x()), Digit::from_i8(self.y()))
725        }
726
727        /// Creates a `Balance` instance from a pair of ternary digits.
728        ///
729        /// # Arguments
730        ///
731        /// * `a` - A `Digit` representing the x-coordinate in the ternary numeral system.
732        /// * `b` - A `Digit` representing the y-coordinate in the ternary numeral system.
733        ///
734        /// # Returns
735        ///
736        /// A new `Balance` instance corresponding to the provided ternary coordinates.
737        ///
738        /// The values of `a` and `b` should be valid ternary digits within the range of:
739        /// - `Neg` (-1), `Zero` (0), and `Pos` (1).
740        ///
741        /// This allows for mapping coordinates within the 3x3 grid system used by the `Balance` enum, ensuring
742        /// that any valid pair of ternary digits maps directly to a specific `Balance` position.
743        ///
744        /// # Examples
745        ///
746        /// ```
747        /// use balanced_direction::Balance;
748        /// use balanced_ternary::Digit;
749        ///
750        /// let balance = Balance::from_ternary_pair(Digit::Zero, Digit::Neg);
751        /// assert_eq!(balance, Balance::Top);
752        ///
753        /// let balance = Balance::from_ternary_pair(Digit::Pos, Digit::Zero);
754        /// assert_eq!(balance, Balance::Right);
755        /// ```
756        pub fn from_ternary_pair(a: Digit, b: Digit) -> Self {
757            Self::from_vector(a.to_i8(), b.to_i8())
758        }
759    }
760}
761
762/// Represents a sequence of movements in a grid, where each movement
763/// is represented by a `Balance` value indicating the direction of one step.
764///
765/// The `Path` struct provides various utilities to manipulate and analyze the sequence
766/// of movements, including iteration, transformation, normalization, and reversal.
767///
768/// # Examples
769///
770/// Creating a new `Path`:
771/// ```
772/// use balanced_direction::{Balance, Path};
773///
774/// let movements = vec![Balance::Top, Balance::Right, Balance::Bottom];
775/// let path = Path::new(movements);
776/// assert_eq!(path.len(), 3);
777/// ```
778///
779/// Normalizing a `Path`:
780/// ```
781/// use balanced_direction::{Balance, Path};
782///
783/// let movements = vec![Balance::Top, Balance::Top];
784/// let path = Path::new(movements).normalized();
785/// assert_eq!(path.to_vector(), (0, -2));
786/// ```
787///
788/// Reversing a `Path`:
789/// ```
790/// use balanced_direction::{Balance, Path};
791///
792/// let movements = vec![Balance::Top, Balance::Right];
793/// let path = Path::new(movements).reversed();
794/// assert_eq!(path.to_vector(), (1, -1));
795/// ```
796#[derive(Clone, Debug, PartialEq)]
797pub struct Path {
798    raw: Vec<Balance>,
799}
800
801impl Path {
802    /// Creates a new `Path` from a vector of movements.
803    ///
804    /// Each movement in the vector represents a step in a 3x3 grid,
805    /// where each step is a `Balance` value indicating direction or position.
806    ///
807    /// # Arguments
808    ///
809    /// * `movements` - A `Vec` of `Balance` values representing the sequence of movements.
810    ///
811    /// # Returns
812    ///
813    /// A new `Path` instance containing the provided sequence of movements.
814    ///
815    /// # Examples
816    ///
817    /// ```
818    /// use balanced_direction::{Balance, Path};
819    ///
820    /// let movements = vec![Balance::Top, Balance::Right, Balance::Bottom];
821    /// let path = Path::new(movements);
822    /// assert_eq!(path.len(), 3);
823    /// ```
824    pub fn new(movements: Vec<Balance>) -> Self {
825        Self { raw: movements }
826    }
827
828    /// Returns the number of movements in the `Path`.
829    ///
830    /// # Returns
831    ///
832    /// An integer representing the number of elements in the `Path`.
833    pub fn len(&self) -> usize {
834        self.raw.len()
835    }
836
837    /// Checks whether the `Path` is empty.
838    ///
839    /// # Returns
840    ///
841    /// `true` if the `Path` contains no movements, `false` otherwise.
842    pub fn is_empty(&self) -> bool {
843        self.raw.is_empty()
844    }
845
846    /// Retrieves the `Balance` at the specified index in the `Path`.
847    ///
848    /// # Arguments
849    ///
850    /// * `index` - The position of the `Balance` in the `Path` to retrieve.
851    ///
852    /// # Returns
853    ///
854    /// An `Option` containing a reference to the `Balance` if the index is valid, or `None` otherwise.
855    pub fn get(&self, index: usize) -> Option<&Balance> {
856        self.raw.get(index)
857    }
858
859    /// Returns an iterator over immutable references to the `Balance` values in the `Path`.
860    ///
861    /// The iterator allows traversing the sequence of movements without modifying it.
862    pub fn iter(&self) -> impl Iterator<Item = &Balance> {
863        self.raw.iter()
864    }
865
866    /// Returns an iterator over mutable references to the `Balance` values in the `Path`.
867    ///
868    /// The iterator allows traversing the sequence of movements modifying it.
869    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Balance> {
870        self.raw.iter_mut()
871    }
872
873    /// Appends a new movement to the end of the `Path`.
874    ///
875    /// # Arguments
876    ///
877    /// * `movement` - The `Balance` value to be added to the `Path`.
878    pub fn push(&mut self, movement: Balance) {
879        self.raw.push(movement);
880    }
881
882    /// Removes the last movement from the `Path`, if any, and returns it.
883    ///
884    /// # Returns
885    ///
886    /// An `Option` containing the `Balance` value that was removed, or `None` if the `Path` is empty.
887    pub fn pop(&mut self) -> Option<Balance> {
888        self.raw.pop()
889    }
890
891    /// Clears all movements from the `Path`, leaving it empty.
892    pub fn clear(&mut self) {
893        self.raw.clear();
894    }
895
896    /// Converts the sequence of movements in the `Path` to a vector representation.
897    ///
898    /// Each `Balance` value in the `Path` contributes a two-dimensional `(i8, i8)` vector,
899    /// which represents its direction or position in the grid. The resulting vector
900    /// is the cumulative sum of all movements in the sequence.
901    ///
902    /// # Returns
903    ///
904    /// A tuple `(i8, i8)` where:
905    /// - The first element is the cumulative movement along the x-axis.
906    /// - The second element is the cumulative movement along the y-axis.
907    ///
908    /// # Examples
909    ///
910    /// ```
911    /// use balanced_direction::{Balance, Path};
912    ///
913    /// let movements = vec![Balance::Top, Balance::Right, Balance::Top];
914    /// let path = Path::new(movements);
915    /// let vector = path.to_vector();
916    /// assert_eq!(vector, (1, -2)); // 1 step right, 2 steps up
917    /// ```
918    pub fn to_vector(&self) -> (i8, i8) {
919        let mut x = 0;
920        let mut y = 0;
921        for movement in self.raw.iter() {
922            let (a, b) = movement.to_vector();
923            x += a;
924            y += b;
925        }
926        (x, y)
927    }
928
929    /// Converts a vector representation `(x, y)` into a `Path`.
930    ///
931    /// This function takes two integers, `x` and `y`, representing cumulative movements along
932    /// the x-axis and y-axis, respectively, in a 2D grid. It decomposes these movements into
933    /// individual steps represented as a sequence of `Balance` values, which are stored in a `Path`.
934    ///
935    /// Movements are calculated progressively by reducing the values of `x` and `y` by their sign
936    /// in each step until both reach 0. Each step corresponds to a direction as determined
937    /// by `Balance::from_vector`.
938    ///
939    /// # Arguments
940    ///
941    /// * `x` - An `i8` representing the movement along the x-axis.
942    /// * `y` - An `i8` representing the movement along the y-axis.
943    ///
944    /// # Returns
945    ///
946    /// A `Path` instance containing a sequence of movements that achieve the given `x` and `y` displacements.
947    ///
948    /// # Examples
949    ///
950    /// ```
951    /// use balanced_direction::{Balance, Path};
952    ///
953    /// let path = Path::from_vector(2, -1);
954    /// assert_eq!(path.to_vector(), (2, -1));
955    /// ```
956    pub fn from_vector(x: i8, y: i8) -> Self {
957        let mut movements = Vec::new();
958        let mut x = x;
959        let mut y = y;
960        while x != 0 || y != 0 {
961            let (a, b) = (x.signum(), y.signum());
962            x -= a;
963            y -= b;
964            movements.push(Balance::from_vector(a, b));
965        }
966        Self { raw: movements }
967    }
968
969    /// Returns a normalized `Path`.
970    ///
971    /// The normalized `Path` is constructed by converting the sequence of movements
972    /// in the current `Path` into their cumulative vector representation using `to_vector`
973    /// and then converting this vector back into a `Path` using `from_vector`.
974    ///
975    /// This effectively removes redundant steps in the `Path` that cancel each other out,
976    /// resulting in a minimal representation of the net movement.
977    ///
978    /// # Examples
979    ///
980    /// ```
981    /// use balanced_direction::{Balance, Path};
982    ///
983    /// let movements = vec![Balance::Top, Balance::Bottom, Balance::Right, Balance::Right];
984    /// let path = Path::new(movements);
985    /// let normalized_path = path.normalized();
986    /// assert_eq!(normalized_path.to_vector(), (2, 0)); // Two steps right
987    /// ```
988    pub fn normalized(&self) -> Self {
989        let (x, y) = self.to_vector();
990        Self::from_vector(x, y)
991    }
992
993    /// Reverses the sequence of movements in the `Path`.
994    ///
995    /// The reversed `Path` will have its movements ordered in the opposite direction
996    /// compared to the original `Path`. The order of the movements is inverted,
997    /// but the movements themselves remain unchanged.
998    ///
999    /// # Returns
1000    ///
1001    /// A new `Path` instance containing the reversed sequence of movements.
1002    ///
1003    /// # Examples
1004    ///
1005    /// ```
1006    /// use balanced_direction::{Balance, Path};
1007    ///
1008    /// let movements = vec![Balance::Top, Balance::Right, Balance::Left];
1009    /// let path = Path::new(movements);
1010    /// let reversed_path = path.reversed();
1011    /// assert_eq!(path.to_vector(), (0, -1));
1012    /// assert_eq!(reversed_path.to_vector(), (0, -1));
1013    /// ```
1014    pub fn reversed(&self) -> Self {
1015        let mut movements = Vec::new();
1016        for movement in self.raw.iter().rev() {
1017            movements.push(*movement);
1018        }
1019        Self { raw: movements }
1020    }
1021
1022    /// Applies a function `f` to each `Balance` in the `Path` and returns a new `Path` containing the results.
1023    ///
1024    /// This method iterates over all movements in the `Path`, applies the function `f` to each movement,
1025    /// and collects the resulting `Balance` values into a new `Path`.
1026    ///
1027    /// # Arguments
1028    ///
1029    /// * `f` - A function or closure of type `Fn(Balance) -> Balance` that takes a `Balance` as input
1030    ///         and returns a transformed `Balance`.
1031    ///
1032    /// # Returns
1033    ///
1034    /// A new `Path` where each `Balance` is the result of applying `f` to the corresponding
1035    /// `Balance` in the original `Path`.
1036    ///
1037    /// # Example
1038    ///
1039    /// ```
1040    /// use balanced_direction::{Balance, Path};
1041    ///
1042    /// let movements = vec![Balance::Top, Balance::Right, Balance::Left];
1043    /// let path = Path::new(movements);
1044    /// let transformed_path = path.each(Balance::up);
1045    /// assert_eq!(
1046    ///     transformed_path.to_vector(),
1047    ///     (0, -3)
1048    /// );
1049    /// ```
1050    pub fn each(&self, f: impl Fn(Balance) -> Balance) -> Self {
1051        let mut movements = Vec::with_capacity(self.raw.len());
1052        for movement in self.raw.iter() {
1053            movements.push(f(*movement));
1054        }
1055        Self { raw: movements }
1056    }
1057
1058    /// Applies a function `f`, which takes two arguments of type `Balance`,
1059    /// and returns a transformed `Balance`, to each `Balance` in the current `Path`,
1060    /// using the provided `other` value as the second argument.
1061    ///
1062    /// This method iterates over all movements in the `Path` and applies the function `f`
1063    /// to each movement and the `other` value. The results of the function application
1064    /// are collected into a new `Path`.
1065    ///
1066    /// # Arguments
1067    ///
1068    /// * `f` - A function or closure of type `Fn(Balance, Balance) -> Balance` that
1069    ///         takes two `Balance` arguments and returns a transformed `Balance`.
1070    /// * `other` - A `Balance` value that is passed as the second argument to the function `f`.
1071    ///
1072    /// # Returns
1073    ///
1074    /// A new `Path` instance where each `Balance` is the result of applying `f`
1075    /// to the corresponding `Balance` in the original `Path` and the `other` value.
1076    ///
1077    /// # Example
1078    ///
1079    /// ```
1080    /// use std::ops::Add;
1081    /// use balanced_direction::{Balance, Path};
1082    ///
1083    /// let movements = vec![Balance::Left, Balance::TopLeft];
1084    /// let path = Path::new(movements);
1085    /// let modified_path = path.each_with(Balance::add, Balance::Right);
1086    /// assert_eq!(modified_path.to_vector(), (0, -1));
1087    /// ```
1088    pub fn each_with(&self, f: impl Fn(Balance, Balance) -> Balance, other: Balance) -> Self {
1089        let mut movements = Vec::with_capacity(self.raw.len());
1090        for movement in self.raw.iter() {
1091            movements.push(f(*movement, other));
1092        }
1093        Self { raw: movements }
1094    }
1095
1096    /// Applies a function `f` to corresponding pairs of `Balance` values from the current `Path`
1097    /// and the `other` `Path`, and returns a new `Path` containing the results.
1098    ///
1099    /// This method iterates over the paired elements of the current `Path` and the provided `other`
1100    /// `Path`, applies the function `f` to each pair, and collects the results into a new `Path`.
1101    ///
1102    /// # Arguments
1103    ///
1104    /// * `f` - A function or closure of type `Fn(Balance, Balance) -> Balance` that takes two
1105    ///         `Balance` arguments (one from each `Path`) and returns a transformed `Balance`.
1106    /// * `other` - A reference to another `Path` whose `Balance` values will be paired with those of
1107    ///             the current `Path`.
1108    ///
1109    /// # Returns
1110    ///
1111    /// A new `Path` where each `Balance` is the result of applying `f` to corresponding pairs of
1112    /// `Balance` values from the current `Path` and the `other` `Path`.
1113    ///
1114    /// # Panics
1115    ///
1116    /// Panics if the lengths of the two `Path`s are not equal, as the method expects both `Path`s
1117    /// to contain the same number of movements.
1118    ///
1119    /// # Examples
1120    ///
1121    /// ```
1122    /// use std::ops::Add;
1123    /// use balanced_direction::{Balance, Path};
1124    ///
1125    /// let path1 = Path::new(vec![Balance::Top, Balance::Right]);
1126    /// let path2 = Path::new(vec![Balance::Bottom, Balance::Left]);
1127    ///
1128    /// let result = path1.each_zip(Balance::add, &path2);
1129    /// assert_eq!(result.to_vector(), (0, 0));
1130    /// ```
1131    pub fn each_zip(&self, f: impl Fn(Balance, Balance) -> Balance, other: &Self) -> Self {
1132        let mut movements = Vec::with_capacity(self.raw.len());
1133        for (a, b) in self.raw.iter().zip(other.raw.iter()) {
1134            movements.push(f(*a, *b));
1135        }
1136        Self { raw: movements }
1137    }
1138}