Skip to main content

advent_of_code/year2018/
day17.rs

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        // Water pouring down at sides:
54        x_range = (x_range.0 - 1, x_range.1 + 1);
55
56        // +1 since ranges are inclusive:
57        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        // Place water on top (may exist above as well coming from the spring, but ignore as that's
73        // above the minimum y value).
74        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}