diff --git a/day13/Cargo.lock b/day13/Cargo.lock new file mode 100644 index 0000000..9863545 --- /dev/null +++ b/day13/Cargo.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 4 + +[[package]] +name = "day13" +version = "0.1.0" diff --git a/day13/Cargo.toml b/day13/Cargo.toml new file mode 100644 index 0000000..e059355 --- /dev/null +++ b/day13/Cargo.toml @@ -0,0 +1,6 @@ +[package] +name = "day13" +version = "0.1.0" +edition = "2024" + +[dependencies] diff --git a/day13/src/main.rs b/day13/src/main.rs new file mode 100644 index 0000000..2550a48 --- /dev/null +++ b/day13/src/main.rs @@ -0,0 +1,135 @@ +use std::{ + collections::{HashSet, VecDeque}, + vec, +}; + +#[cfg(debug_assertions)] +const INPUT: usize = 10; +#[cfg(not(debug_assertions))] +const INPUT: usize = 1362; + +const MAX_X: usize = 100; +const MAX_Y: usize = 100; + +fn main() { + let maze = generate_maze(INPUT); + + let result = bfs(&maze, (1, 1), (31, 39)); + + println!("{result}"); +} + +#[allow(clippy::needless_range_loop)] +fn generate_maze(input: usize) -> Vec> { + let mut result = vec![vec![WallOrSpace::default(); MAX_Y]; MAX_X]; + for x in 0..MAX_X { + for y in 0..MAX_Y { + let equation = calculate_equation(x, y); + let sum = equation + input; + let binary = format!("{:b}", sum); + let ones = binary.chars().filter(|c| *c == '1').count(); + if ones % 2 == 1 { + result[x][y] = WallOrSpace::Wall; + } + } + } + + result +} + +fn bfs(maze: &[Vec], start: (usize, usize), goal: (usize, usize)) -> usize { + let mut queue = VecDeque::new(); + + let mut visited = HashSet::new(); + visited.insert(start); + + let mut reachable_in_50_steps = HashSet::new(); + reachable_in_50_steps.insert(start); + + queue.push_back((start, 0_usize)); + + while let Some(((xi, yi), distance)) = queue.pop_front() { + if (xi, yi) == goal { + println!("{}", reachable_in_50_steps.len()); + return distance; + } + + if distance <= 50 { + reachable_in_50_steps.insert((xi, yi)); + } + + for neighbor in get_valid_neighbors(maze, (xi, yi)) { + if !visited.contains(&neighbor) { + visited.insert(neighbor); + queue.push_back((neighbor, distance + 1)); + } + } + } + + panic!("goal unreachable"); +} + +fn get_valid_neighbors( + maze: &[Vec], + (x, y): (usize, usize), +) -> impl Iterator { + let mut result = vec![]; + + if x == 0 && y == 0 { + result.push((x + 1, y)); + result.push((x, y + 1)); + } else if x == 0 && y != 0 { + result.push((x + 1, y)); + result.push((x, y - 1)); + result.push((x, y + 1)); + } else if x != 0 && y == 0 { + result.push((x - 1, y)); + result.push((x + 1, y)); + result.push((x, y + 1)); + } else { + // x != 0 && y != 0 + result.push((x - 1, y)); + result.push((x + 1, y)); + result.push((x, y - 1)); + result.push((x, y + 1)); + } + + result + .into_iter() + .filter(|(x, y)| *x < MAX_X && *y < MAX_Y) + .filter(|(x, y)| maze[*x][*y].is_space()) +} + +fn calculate_equation(x: usize, y: usize) -> usize { + x * x + 3 * x + 2 * x * y + y + y * y +} + +#[derive(Clone, Debug, Default)] +enum WallOrSpace { + #[default] + Space, + Wall, +} + +impl WallOrSpace { + fn is_space(&self) -> bool { + match self { + WallOrSpace::Space => true, + WallOrSpace::Wall => false, + } + } +} + +#[allow(clippy::needless_range_loop, dead_code)] +fn print_maze(maze: &[Vec]) { + for x in 0..MAX_X { + for y in 0..MAX_Y { + let c = match maze[x][y] { + WallOrSpace::Space => '.', + WallOrSpace::Wall => '#', + }; + print!("{c}"); + } + println!() + } +}