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}