Advent of Code solutions
1use std::ops::Neg;
2
3use crate::{dir::Direction, pos::Position};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
6/// A line between two points.
7///
8/// This line is represented by two [Position]s.
9///
10/// # Examples
11///
12/// ```
13/// use utils::prelude::*;
14///
15/// let line = Line::new(Position::new(0, 0), Position::new(1, 1));
16/// assert_eq!(line.get_slope(), 1.0);
17/// assert_eq!(line.get_intercept(), 0.0);
18/// ```
19///
20pub struct Line(Position, Position);
21
22impl Line {
23 /// Create a new line between two points.
24 ///
25 /// # Examples
26 ///
27 /// ```
28 /// use utils::prelude::*;
29 ///
30 /// let line = Line::new(Position::new(0, 0), Position::new(1, 1));
31 /// assert_eq!(line.end().x, 1);
32 /// ```
33 ///
34 pub fn new(start: Position, end: Position) -> Self {
35 Self(start, end)
36 }
37
38 /// Create a new line from a starting point and a direction.
39 ///
40 /// # Examples
41 ///
42 /// ```
43 /// use utils::prelude::*;
44 ///
45 /// let line = Line::from_dir(Position::new(0, 0), Direction::East);
46 /// assert_eq!(line.end().x, 1);
47 /// ```
48 ///
49 pub fn from_dir(start: Position, dir: Direction) -> Self {
50 Self(start, start.move_dir(dir))
51 }
52
53 /// Get the linear slope of the line.
54 ///
55 /// # Examples
56 ///
57 /// ```
58 /// use utils::prelude::*;
59 ///
60 /// let line = Line::new(Position::new(0, 0), Position::new(1, 1));
61 /// assert_eq!(line.get_slope(), 1.0);
62 ///
63 /// let line = Line::new(Position::new(0, 0), Position::new(2, 1));
64 /// assert_eq!(line.get_slope(), 0.5);
65 /// ```
66 ///
67 pub fn get_slope(&self) -> f64 {
68 let dx = self.1.x - self.0.x;
69 let dy = self.1.y - self.0.y;
70 dy as f64 / dx as f64
71 }
72
73 /// Get the y-intercept of the line.
74 ///
75 /// # Examples
76 ///
77 /// ```
78 /// use utils::prelude::*;
79 ///
80 /// let line = Line::new(Position::new(0, 0), Position::new(1, 1));
81 /// assert_eq!(line.get_intercept(), 0.0);
82 ///
83 /// let line = Line::new(Position::new(0, 5), Position::new(2, 1));
84 /// assert_eq!(line.get_intercept(), 5.0);
85 /// ```
86 ///
87 pub fn get_intercept(&self) -> f64 {
88 let slope = self.get_slope();
89 self.0.y as f64 - slope * self.0.x as f64
90 }
91
92 /// Check that the given point is *after* the start position on the line.
93 ///
94 /// Note this doesn't check if the point is on the line, just that it is
95 /// on the same side of the line as the end point.
96 ///
97 /// # Examples
98 ///
99 /// ```
100 /// use utils::prelude::*;
101 ///
102 /// let line = Line::new(Position::new(0, 0), Position::new(2, 2));
103 /// assert_eq!(line.check_after(&Position::new(1, 1)), true);
104 ///
105 /// let line = Line::new(Position::new(0, 0), Position::new(2, 2));
106 /// assert_eq!(line.check_after(&Position::new(-1, -1)), false);
107 /// ```
108 ///
109 pub fn check_after(&self, pos: &Position) -> bool {
110 let relative = pos.sub(&self.0);
111 let d = self.1.sub(&self.0);
112 relative.normalize() == d.normalize()
113 }
114
115 /// Get the intersection point between this line and another.
116 ///
117 /// Pass `check_after` as `true` to ensure that the intersection point is
118 /// after the start of both lines.
119 ///
120 /// # Examples
121 ///
122 /// ```
123 /// use utils::prelude::*;
124 ///
125 /// let line1 = Line::new(Position::new(2, -2), Position::new(-2, 2));
126 /// let line2 = Line::new(Position::new(2, 2), Position::new(-2, -2));
127 /// assert_eq!(line1.get_intersection(&line2, true), Some(Position::new(0, 0)));
128 ///
129 /// let line1 = Line::new(Position::new(5, 0), Position::new(6, 8));
130 /// let line2 = Line::new(Position::new(0, 1), Position::new(-4, -3));
131 /// assert_eq!(line1.get_intersection(&line2, true), None);
132 /// ```
133 ///
134 pub fn get_intersection(&self, other: &Self, check_after: bool) -> Option<Position> {
135 let slope = self.get_slope();
136 let intercept = self.get_intercept();
137 let other_slope = other.get_slope();
138 let other_intercept = other.get_intercept();
139
140 if slope == other_slope {
141 return None;
142 }
143
144 let x = (other_intercept - intercept) / (slope - other_slope);
145 let y = slope * x + intercept;
146
147 let point = Position::new(x as isize, y as isize);
148
149 if !check_after || self.check_after(&point) && other.check_after(&point) {
150 Some(point)
151 } else {
152 None
153 }
154 }
155
156 pub fn start(&self) -> Position {
157 self.0
158 }
159
160 pub fn end(&self) -> Position {
161 self.1
162 }
163}
164
165impl Neg for Line {
166 type Output = Self;
167
168 fn neg(self) -> Self::Output {
169 Self(self.1, self.0)
170 }
171}