1use crate::input::Input;
2use std::cmp::{max, min};
3#[cfg(feature = "debug-output")]
4use std::env;
5
6fn parse_point_interval(s: &str) -> Result<(u16, u16), String> {
7 if s.contains("..") {
8 let parts: Vec<&str> = s.split("..").collect();
9 if parts.len() != 2 {
10 return Err("Invalid input".to_string());
11 }
12 Ok((
13 parts[0].parse::<u16>().map_err(|_| "Invalid input")?,
14 parts[1].parse::<u16>().map_err(|_| "Invalid input")?,
15 ))
16 } else {
17 let point = s.parse::<u16>().map_err(|_| "Invalid input")?;
18 Ok((point, point))
19 }
20}
21
22struct Grid {
23 cells: Vec<u8>,
24 width: usize,
25 height: usize,
26}
27
28impl Grid {
29 fn from(input_string: &str) -> Result<Self, String> {
30 let mut points: Vec<(u16, u16)> = Vec::new();
31 let mut x_range = (u16::MAX, u16::MIN);
32 let mut y_range = (u16::MAX, u16::MIN);
33
34 for line in input_string.lines() {
35 let mut parts: Vec<&str> = line.split(", ").collect();
36 if parts.len() != 2 || parts[0].len() < 3 || parts[1].len() < 3 {
37 return Err("Invalid input".to_string());
38 }
39 parts.sort_unstable();
40 let (x_start, x_end) = parse_point_interval(&parts[0][2..])?;
41 let (y_start, y_end) = parse_point_interval(&parts[1][2..])?;
42
43 x_range = (min(x_range.0, x_start), max(x_range.1, x_end));
44 y_range = (min(y_range.0, y_start), max(y_range.1, y_end));
45
46 for x in x_start..=x_end {
47 for y in y_start..=y_end {
48 points.push((x, y));
49 }
50 }
51 }
52
53 x_range = (x_range.0 - 1, x_range.1 + 1);
55
56 let width = ((x_range.1 - x_range.0) + 1) as usize;
58 let height = ((y_range.1 - y_range.0) + 1) as usize;
59
60 let mut cells = vec![b'.'; width * height];
61 for point in points {
62 let x = point.0 - x_range.0;
63 let y = point.1 - y_range.0;
64 cells[y as usize * width + x as usize] = b'#';
65 }
66
67 let water_x = 500;
68 if !(x_range.0..x_range.1).contains(&water_x) {
69 return Err("Spring outside scanned area".into());
70 }
71 let water_x_relative = water_x - x_range.0;
72 cells[water_x_relative as usize] = b'|';
75
76 Ok(Self {
77 cells,
78 width,
79 height,
80 })
81 }
82
83 #[cfg(feature = "debug-output")]
84 fn print(&self, name: &str) {
85 if env::var("ADVENT_DEBUG").is_err() {
86 return;
87 }
88 println!("### {}", name);
89 for y in 0..self.height {
90 println!(
91 "{}",
92 String::from_utf8(self.cells[y * self.width..(y + 1) * self.width].to_vec())
93 .unwrap_or_else(|_| "Invalid utf-8".to_string())
94 .replace('.', " ")
95 .replace('#', "█")
96 );
97 }
98 println!();
99 }
100
101 fn at(&self, x: u16, y: u16) -> u8 {
102 self.cells[y as usize * self.width + x as usize]
103 }
104
105 fn set_water_at(&mut self, x: u16, y: u16, solid: bool) {
106 self.cells[y as usize * self.width + x as usize] = if solid { b'w' } else { b'|' };
107 }
108
109 fn dry_at(&mut self, x: u16, y: u16) {
110 self.cells[y as usize * self.width + x as usize] = b'.';
111 }
112
113 fn wall_in_direction(&self, x_start: u16, y: u16, x_direction: i32) -> bool {
114 let mut x = (i32::from(x_start) + x_direction) as u16;
115 loop {
116 let cell = self.at(x, y);
117 if !(cell == b'.' || cell == b'w' || cell == b'|') {
118 break;
119 }
120 let below = self.at(x, y + 1);
121 if !(below == b'#' || below == b'w') {
122 return false;
123 }
124 x = (i32::from(x) + x_direction) as u16;
125 }
126 self.at(x, y) == b'#'
127 }
128
129 fn fill_in_direction(&mut self, x_start: u16, y: u16, x_direction: i32, solid: bool) {
130 let mut x = (i32::from(x_start) + x_direction) as u16;
131 loop {
132 let cell = self.at(x, y);
133 if !(cell == b'.' || cell == b'w' || cell == b'|') {
134 break;
135 }
136 self.set_water_at(x, y, solid);
137 let below = self.at(x, y + 1);
138 if !(below == b'#' || below == b'w') {
139 return;
140 }
141 x = (i32::from(x) + x_direction) as u16;
142 }
143 }
144
145 fn spread_water_at(&mut self, x: u16, y: u16) -> u16 {
146 self.set_water_at(x, y, false);
147
148 if (y as usize) < self.height - 1 {
149 let below = self.at(x, y + 1);
150 if below == b'#' || below == b'w' {
151 let left_wall = self.wall_in_direction(x, y, -1);
152 let right_wall = self.wall_in_direction(x, y, 1);
153 let surrounded_by_walls = left_wall && right_wall;
154 self.fill_in_direction(x, y, -1, surrounded_by_walls);
155 self.fill_in_direction(x, y, 1, surrounded_by_walls);
156 if surrounded_by_walls {
157 self.set_water_at(x, y, true);
158 }
159 if surrounded_by_walls && y > 0 {
160 return self.spread_water_at(x, y - 1);
161 }
162 }
163 }
164 y
165 }
166
167 fn pour_water(&mut self) {
168 let mut line = 1;
169 while line < self.height {
170 let mut top_y = line;
171 let mut to_fill = Vec::new();
172 for x in 0..self.width {
173 if self.at(x as u16, line as u16 - 1) == b'|'
174 && self.at(x as u16, line as u16) == b'.'
175 {
176 to_fill.push(x);
177 }
178 }
179 for x in to_fill {
180 top_y = min(top_y, self.spread_water_at(x as u16, line as u16) as usize);
181 }
182 line = top_y + 1;
183 }
184 }
185
186 fn holds_in_direction(&self, x_start: u16, y: u16, x_direction: i32) -> bool {
187 let mut x = (i32::from(x_start) + x_direction) as u16;
188 loop {
189 let cell = self.at(x, y);
190 let below = self.at(x, y + 1);
191 if !(below == b'#' || below == b'w') {
192 return false;
193 }
194 if cell == b'#' {
195 return true;
196 }
197 x = (i32::from(x) + x_direction) as u16;
198 }
199 }
200
201 fn dry_up(&mut self) {
202 let mut line = self.height as u16;
203 while line > 0 {
204 line -= 1;
205 for x in 0..self.width {
206 if self.at(x as u16, line) == b'w' {
207 let below = if line == (self.height as u16 - 1) {
208 b'.'
209 } else {
210 self.at(x as u16, line + 1)
211 };
212
213 if !((below == b'#' || below == b'w')
214 && self.holds_in_direction(x as u16, line, -1)
215 && self.holds_in_direction(x as u16, line, 1))
216 {
217 self.dry_at(x as u16, line);
218 }
219 }
220 }
221 }
222 }
223
224 fn count_water(&self) -> usize {
225 self.cells
226 .iter()
227 .fold(0, |n, c| n + usize::from(*c == b'w' || *c == b'|'))
228 }
229
230 fn count_drained_water(&self) -> usize {
231 self.cells
232 .iter()
233 .fold(0, |n, c| n + usize::from(*c == b'w'))
234 }
235}
236
237pub fn solve(input: &Input) -> Result<usize, String> {
238 let mut grid = Grid::from(input.text)?;
239 #[cfg(feature = "debug-output")]
240 grid.print("Initial");
241
242 grid.pour_water();
243 #[cfg(feature = "debug-output")]
244 grid.print("After pouring");
245
246 if input.is_part_one() {
247 Ok(grid.count_water())
248 } else {
249 grid.dry_up();
250 #[cfg(feature = "debug-output")]
251 grid.print("After drying up");
252 Ok(grid.count_drained_water())
253 }
254}
255
256#[test]
257fn tests() {
258 test_part_one!(
259 "x=495, y=2..7
260y=7, x=495..501
261x=501, y=3..7
262x=498, y=2..4
263x=506, y=1..2
264x=498, y=10..13
265x=504, y=10..13
266y=13, x=498..504"
267 => 57
268 );
269
270 test_part_two!(
271 "x=495, y=2..7
272y=7, x=495..501
273x=501, y=3..7
274x=498, y=2..4
275x=506, y=1..2
276x=498, y=10..13
277x=504, y=10..13
278y=13, x=498..504"
279 => 29
280 );
281
282 let input = include_str!("day17_input.txt");
283 test_part_one!(input => 31949);
284 test_part_two!(input => 26384);
285}