use std::{collections::HashSet, error::Error}; use itertools::Itertools; pub fn process_part1(input: &str) -> u32 { let falling_bytes = &input .lines() .map(|line| { let (x, y) = line.split_once(",").unwrap(); (x.parse().unwrap(), y.parse().unwrap()) }) .collect_vec()[..1024]; let y_walls = vec![GridTile::Wall; 73]; let mut inner_rows = vec![GridTile::Path; 71]; inner_rows.insert(0, GridTile::Wall); inner_rows.push(GridTile::Wall); let mut grid = vec![inner_rows; 71]; grid.insert(0, y_walls.clone()); grid.push(y_walls); simulate(grid, falling_bytes).unwrap() } fn simulate(mut grid: Vec>, falling_bytes: &[(usize, usize)]) -> Option { grid[1][1] = GridTile::Start; let height = grid.len(); let width = grid[0].len(); grid[height - 2][width - 2] = GridTile::End; falling_bytes.iter().for_each(|(x, y)| { grid[*y + 1][*x + 1] = GridTile::Wall; }); let mut visited = HashSet::new(); visited.insert((1, 1)); let mut next_paths = vec![MazeRunner { coords: (1, 1), visited: visited.clone(), ..Default::default() }]; let mut arrived: Vec = Vec::new(); while !next_paths.is_empty() { next_paths = next_paths .iter() .map(|maze_runner| { let mut paths = Vec::new(); if let Some(path) = maze_runner.get_next(&grid, Direction::Up) { paths.push(path); } if let Some(path) = maze_runner.get_next(&grid, Direction::Down) { paths.push(path); } if let Some(path) = maze_runner.get_next(&grid, Direction::Left) { paths.push(path); } if let Some(path) = maze_runner.get_next(&grid, Direction::Right) { paths.push(path); } paths }) .collect_vec() .concat(); for (idx, maze_runner) in next_paths.clone().iter().enumerate().rev() { if maze_runner.visited.contains(&maze_runner.coords) || visited.contains(&maze_runner.coords) { next_paths.remove(idx); continue; } visited.insert(maze_runner.coords); next_paths[idx].visited.insert(maze_runner.coords); if maze_runner.state == State::Arrived { let arrived_reindeer = next_paths.remove(idx); arrived.push(arrived_reindeer); } } } //let visited = arrived // .iter() // .map(|reindeer| { // reindeer // .visited // .iter() // .map(|(coords, _)| coords) // .collect_vec() // }) // .collect_vec() // .concat(); //visited.iter().unique().count() arrived.sort_by(|a_runner, b_runner| a_runner.visited.len().cmp(&b_runner.visited.len())); if let Some(arrived) = arrived.first() { log_maze(&grid, arrived); Some(arrived.visited.len() as u32 - 1) } else { None } } pub fn process_part2(input: &str) -> String { let falling_bytes = &input .lines() .map(|line| { let (x, y) = line.split_once(",").unwrap(); (x.parse::().unwrap(), y.parse::().unwrap()) }) .collect_vec(); let y_walls = vec![GridTile::Wall; 73]; let mut inner_rows = vec![GridTile::Path; 71]; inner_rows.insert(0, GridTile::Wall); inner_rows.push(GridTile::Wall); let mut grid = vec![inner_rows; 71]; grid.insert(0, y_walls.clone()); grid.push(y_walls); let mut num_bytes = 1025; while simulate(grid.clone(), &falling_bytes[..num_bytes]).is_some() { num_bytes += 1; } let (x, y) = falling_bytes[num_bytes - 1]; format!("{x},{y}") } #[derive(Debug, Clone, PartialEq, Eq)] enum Direction { Up, Down, Left, Right, } #[derive(Debug, Default, Clone, PartialEq, Eq)] enum State { #[default] Going, Arrived, } #[derive(Debug, Default, Clone, PartialEq, Eq)] struct MazeRunner { coords: (usize, usize), visited: HashSet<(usize, usize)>, state: State, } impl MazeRunner { fn get_next(&self, grid: &[Vec], direction: Direction) -> Option { let tile = match direction { Direction::Up => grid[self.coords.1 - 1][self.coords.0], Direction::Down => grid[self.coords.1 + 1][self.coords.0], Direction::Right => grid[self.coords.1][self.coords.0 + 1], Direction::Left => grid[self.coords.1][self.coords.0 - 1], }; let coords = match direction { Direction::Up => (self.coords.0, self.coords.1 - 1), Direction::Down => (self.coords.0, self.coords.1 + 1), Direction::Left => (self.coords.0 - 1, self.coords.1), Direction::Right => (self.coords.0 + 1, self.coords.1), }; if tile == GridTile::Wall { None } else if tile == GridTile::End { Some(MazeRunner { state: State::Arrived, coords, ..self.clone() }) } else { Some(MazeRunner { coords, ..self.clone() }) } } } #[derive(Debug, Default, Clone, Copy, Hash, PartialEq, Eq)] enum GridTile { Wall, #[default] Path, Start, End, } impl TryFrom for GridTile { type Error = Box; fn try_from(value: char) -> std::result::Result> { match value { '#' => Ok(Self::Wall), '.' => Ok(Self::Path), 'S' => Ok(Self::Start), 'E' => Ok(Self::End), _ => Err(Box::from(format!("{value} is not a valid tile"))), } } } fn log_maze(grid: &[Vec], maze_runner: &MazeRunner) { for (yidx, row) in grid.iter().enumerate() { for (xidx, tile) in row.iter().enumerate() { let contains = { maze_runner.visited.contains(&(xidx, yidx)) || maze_runner.visited.contains(&(xidx, yidx)) || maze_runner.visited.contains(&(xidx, yidx)) || maze_runner.visited.contains(&(xidx, yidx)) }; if contains && !(*tile == GridTile::Start || *tile == GridTile::End) { print!("O"); } else if *tile == GridTile::Wall { print!("#"); } else if *tile == GridTile::Path { print!("."); } else if *tile == GridTile::Start { print!("S"); } else if *tile == GridTile::End { print!("E"); } } println!(); } } #[cfg(test)] mod tests { use super::*; const INPUT: &str = "5,4 4,2 4,5 3,0 2,1 6,3 2,4 1,5 0,6 3,3 2,6 5,1 1,2 5,5 2,5 6,5 1,4 0,4 6,4 1,1 6,1 1,0 0,5 1,6 2,0"; #[test] fn part1() { let falling_bytes = &INPUT .lines() .map(|line| { let (x, y) = line.split_once(",").unwrap(); (x.parse().unwrap(), y.parse().unwrap()) }) .collect_vec()[..12]; let y_walls = vec![GridTile::Wall; 9]; let mut inner_rows = vec![GridTile::Path; 7]; inner_rows.insert(0, GridTile::Wall); inner_rows.push(GridTile::Wall); let mut grid = vec![inner_rows; 7]; grid.insert(0, y_walls.clone()); grid.push(y_walls); let result = simulate(grid, falling_bytes); assert_eq!(result, Some(22)); } #[test] fn part2() { let falling_bytes = INPUT .lines() .map(|line| { let (x, y) = line.split_once(",").unwrap(); (x.parse::().unwrap(), y.parse::().unwrap()) }) .collect_vec(); let y_walls = vec![GridTile::Wall; 9]; let mut inner_rows = vec![GridTile::Path; 7]; inner_rows.insert(0, GridTile::Wall); inner_rows.push(GridTile::Wall); let mut grid = vec![inner_rows; 7]; grid.insert(0, y_walls.clone()); grid.push(y_walls); let mut num_bytes = 12; while simulate(grid.clone(), &falling_bytes[..num_bytes]).is_some() { num_bytes += 1; } let (x, y) = falling_bytes[num_bytes - 1]; let result = format!("{x},{y}"); assert_eq!(result, "6,1".to_string()); } }