diff --git a/day23/input b/day23/input new file mode 100644 index 0000000..d506938 --- /dev/null +++ b/day23/input @@ -0,0 +1,75 @@ +.#..###.##..##.####.#.....##.##.#..#.##.#..#.#.#.####..#####.#......#...#.. +###...#.#..##...#.##...#.###.###....##..##..######.#.#..#.#...#.##...#.#.#. +.......#.###.....#.##.....##..#.#...##..#####.###.##......#.###.##.#####.## +##.##...##..####.##.........#.##.#.#..####.##....#.#####.####.####..##..##. +#..#.###...###...#..#..#..#.#.##.######.###..##########.#..#.##.#.#.##....# +.#..#..##..#.#.......#.#....#######..#.##.##..##.#.#.#..#.##....#..##.##### +.#.##..##.##...#.##.##..##.##.##..##...###..###.#...##..##....#.##....##### +.###.###.#.##.###....#..###..###..##.#.#.#.#....#####.###.######...###.###. +#...####.###.....#.....###...#..##.##...########.#.#.######...#..#.###....# +..###.#.###..#.#.........#....#.#.#####.#.#.#....###.##..###...####.#.#...# +###.#.######.##.#.##....##.##..##.#.#..#.#..##..#..##.###..##.########.#.#. +.#...##.....###...##..###.#.##.....#.##..#.#.#..##.#...#.#.#.###.#....#.##. +..####.#.#.###....###.#.....##...#.##.#...#..#.#..#####...######....#..##.# +.##.#..##.###.###..#.#.#####.#.###......###..#.#..#..###..###..#.....#..##. +#.#.#####..##.##...#...#...#..#...#.#.#..#........######.##.#...#..#.#..##. +.#.####....#.#..##.##..#...##.##...##.....###.###.###..#.#....#.#.####.#..# +##.#..#.###..###........#...##.#..##...####.##..#..#.##.#....##..##..#####. +...#..##.#..##.####..###...#.#.#.......#####..######....###..#.#..###.###.. +.#.##...#.#.#........##..#.#####.#####...##......#.##.....#..####..#####... +.#.#..#..#.#.#.####.#...###.###.#..###...###.##..##...##..####.#.....#.#.## +##...#.#...##..##..##..##.####..######...##..#...##..##.##....##...#.#..... +..####.#####..##.####...#...#...#.#.#..##.##..#...###.#..#...#...#.#...#.## +..###.#....#.########...##....#..#.#.#..#.##..#...#.##.....#.###..#..#..#.# +#.##....###.#..##.#.##..####..###......####.###.##..###....#..#.#.#...####. +.######.#....#....#####...#..#..###.#..#...#.###.#.##..#.###..#..###......# +#..##.#.#####.#.#.##.#..###.#..#..#.#..#.#..#.#.....##########.#..#..#..#.# +###.##....#...####.###..##..#....#.#......#.#.#......#..##.#..####...#..#.# +..#.###..#.###.##.#..#....#..#####.#.#.####.#...#....#.##.#.#..###.######## +..#.#.....##.##...#..##...#.#..##.####.###.#.#..#..####...#..##..##..#.###. +.###..#.###....#.####.#..#...###.#....#...#....###.#.######.####..#.#..###. +.#.##.##.....#.###.#...#..#.#.##.##..#.#..###.##..#.##....##....#.#...#.##. +#.##.#..#.#.#....##...####.#..#.###..######.###.#..##.#.#.#####...##..#.##. +#.###.##.#..#...#..##..#.##.....#...#....#...##....#..#..##..##.##....##.## +..###..#...#.#..##.##..#####.#..#.#..#...#...##.#.#..#..#.###....#.####.### +###.###...####..###..#..#.##..#.#.#.....#....#.#####.#..###.#..####....##.# +#.###.#.#.##.###.##.........###.#..#######.####..........#.#.###..#.#.##.#. +####.##.#####.##.#.#.##......#...#.#.##..###..#...#...###..###.#####..##... +.##...#####.##########.#....##.......#.#.#.#.###..#####....#..#.####..##### +####..#..####.###..###..#####.###....#.#.#.....#.####..#..###.#..##.##..##. +###.#.#....##..#.##.#.....##..##.##..##.###.##.#.#...####...#.##..####..##. +#..#..##.#....###..##.#..#...#.#.#.#.######.##..#...##.#.#.....####..##.##. +....####..###.#.##..#...#..##..#####.....##.##..#.##..##...#####.....##..#. +.##...##...######..##..#.##..##.#.#..##.##..###.#.####...#######..##....#.. +.###.##.#......###....#####.#.#.##...#####..#..##.###...##...###.#.##...##. +.#....####..#..#..##...##....#.##...###..##.#..#.#.#.#.####.....###...#.### +##.###..#....##....#.##..#.#.....#...###..##..###....#.....###...##.....### +##......#.##.#.###...##..###.....#.##.#..#.###..###.#.##...#####..###..#.## +##.##.#####........#..##........#..##..##......#.###.#.#..##.######.#....## +##.....#.##...#.####.####...#.###.#....#.######.#....#.#.##....#...##..##.# +#.##.....#####.###.###.##.#.#.#..#....#.....#.#..###.####.#####...#.......# +...#######......###.##.#.#.#.....######.#.##.#.#...#..#.#....#.#.#.##..###. +#.###..#.##.#####..#.#...##.####.###.#####..#.....#...#.#..##.#.####.##.#.. +.##..##...##....#####.#...####..#....#..##.#...#####.#.##...#.####.###...## +..####.###...##.##.#..#.###.##..#....#.#....###.#..##.#.....#.###....##.#.. +#.##.#...#..##.####.######.##..#.#......#.###...#..##.#..##..###.#..#.#.#.# +..##.##..#..#.####.###.#...##.###.###.##..##.###....##...##.#..##.###...##. +.####..#....#####..#..##....#.#.##.#####.#####..#.....###.#..###.####...... +##...#.#.#..##..##....##.###...#.##..#..#....###....#####.##..##.....####.# +#..####...####..##..#..#..#.#.....##.#..#####..#.#.###.#..###.....#.#.###.# +.##..####.####...#.#....##..#####..#...#...#..###..##......#...#..#.##.###. +#..##..###.###.#.#..##..#.#.###....##..##.###.#..#....#.#.#..#..##.#..####. +#.####..##....#...#.#...###..#..##...##.#.#.#.#...#..#...##...#...#.#....#. +#..###.###...###.#.###.#....#..##....#.#.#..#.##.###.#####..##.###...#.#... +.#..###.#.#..#.#.#...####.#.#.##.###.#..##.##.......#.#.#.#.#...#..#.#...## +######.....###...#..#..###.#.#...#...##..##.##.#..#.#...###.##.##....#.#... +#..##...#...##...###..#....#...###.#.###.###..#..##.##..####..#.###.##....# +..##...#..#.#.#######..#.###..#.#.#.#.##.#..#.....#.##.###.#####.#.######.. +#.#.#..##.#.###.#....#..#..######.#....##.#..###.####.#......#..##.##.##.## +######....##......#..#...####..###..######.#.#.##....#...##.#.#..#.#.#.##.. +###.####.###.#####.....######..#...#.##.#....##.#.##..#.#.##.#.##..###..### +..######....####..###..##..##.##...#.###..#.##..#.#...#.##.##.....#..#..### +.##..##..#.##....#...##.###.##..#...##..##.##.#.#..###...##....#.#.#..#...# +..#..##.########.##..#####.#....#..##.##.##.#...#.##..##.#..#..###...#.#.## +#.##....#..#.#..##.#......#.#..##.#.............#.##.#...##.#..#...##.#.### +#..#..###.#.##.#..#....##..#..#..##.##..#.##...###.......#..###..####...##. diff --git a/day23/input.test b/day23/input.test new file mode 100644 index 0000000..14005cf --- /dev/null +++ b/day23/input.test @@ -0,0 +1,7 @@ +....#.. +..###.# +#...#.# +.#...## +#.###.. +##.#.## +.#..#.. diff --git a/day23/src/main.rs b/day23/src/main.rs index e7a11a9..76ac8b6 100644 --- a/day23/src/main.rs +++ b/day23/src/main.rs @@ -1,3 +1,257 @@ -fn main() { - println!("Hello, world!"); +use std::{ + collections::{HashMap, HashSet}, + fs::File, + io::{BufRead, BufReader}, + ops::Index, +}; + +type Elf = (isize, isize); + +const ACTIONS: [Dir; 4] = [Dir::North, Dir::South, Dir::West, Dir::East]; + +#[derive(Debug, Default, PartialEq, Eq)] +enum SolvePuzzle { + First, + #[default] + Second, +} + +struct Playground { + elves: HashSet, + first_action_index: usize, +} + +impl Playground { + fn parse_input(reader: R) -> Self { + let elves = reader + .lines() + .map(Result::unwrap) + .enumerate() + .flat_map(|(y, line)| { + line.into_bytes() + .into_iter() + .enumerate() + .filter_map(move |(x, byte)| { + if byte == b'#' { + Some((x as isize, y as isize)) + } else { + None + } + }) + }) + .collect(); + Self { + elves, + first_action_index: 0, + } + } + + fn simulate_round(&mut self) -> HasMoved { + let mut any_moved = false; + let proposed_moves: HashMap = self + .elves + .iter() + .copied() + .filter_map(|elf| { + let surr = Surrounding::for_elf(&self.elves, &elf); + // Do nothing if there's noone around + if surr.is_empty() { + None + } else { + let mut proposed_move = None; + for action_idx in self.first_action_index..self.first_action_index + 4 { + let action = ACTIONS[action_idx % ACTIONS.len()]; + if surr.is_dir_empty(action) { + proposed_move = Some(action); + break; + } + } + proposed_move.map(|dir| (elf, dir.apply_to(elf))) + } + }) + .collect(); + let mut illegal_moves: HashSet = HashSet::new(); + proposed_moves + .iter() + .fold(HashSet::new(), |mut set, (_from, to)| { + if set.contains(&to) { + illegal_moves.insert(*to); + } else { + set.insert(to); + } + set + }); + proposed_moves + .into_iter() + .filter(|(_from, to)| !illegal_moves.contains(to)) + .for_each(|(from, to)| { + self.elves.remove(&from); + self.elves.insert(to); + any_moved = true; + }); + self.first_action_index = (self.first_action_index + 1) % ACTIONS.len(); + match any_moved { + true => HasMoved::Yes, + false => HasMoved::No, + } + } + + fn count_empty_tiles(self) -> usize { + // Calculate empty tiles + let (min_x, max_x, min_y, max_y, total_elf_count) = self.elves.into_iter().fold( + (isize::MAX, isize::MIN, isize::MAX, isize::MIN, 0), + |(min_x, max_x, min_y, max_y, count), (x, y)| { + ( + min_x.min(x), + max_x.max(x), + min_y.min(y), + max_y.max(y), + count + 1, + ) + }, + ); + ((max_x - min_x + 1) * (max_y - min_y + 1) - total_elf_count) as usize + } +} + +#[derive(Debug, Clone, Copy)] +enum Dir { + North, + East, + South, + West, +} + +impl Dir { + fn apply_to(&self, (x, y): Elf) -> Elf { + match self { + Dir::North => (x, y - 1), + Dir::East => (x + 1, y), + Dir::South => (x, y + 1), + Dir::West => (x - 1, y), + } + } +} + +#[derive(Debug, Clone, Copy)] +enum DirDiag { + North = 0, + NorthEast, + East, + SouthEast, + South, + SouthWest, + West, + NorthWest, +} + +#[derive(Debug)] +struct Surrounding { + raw: [bool; 8], +} + +impl Surrounding { + fn for_elf(elves: &HashSet, &(x, y): &Elf) -> Self { + let raw = [ + (x, y - 1), + (x + 1, y - 1), + (x + 1, y), + (x + 1, y + 1), + (x, y + 1), + (x - 1, y + 1), + (x - 1, y), + (x - 1, y - 1), + ] + .into_iter() + .map(|(x, y)| elves.get(&(x, y)).is_some()) + .collect::>() + .try_into() + .unwrap(); + Self { raw } + } + + fn is_empty(&self) -> bool { + !self.raw.iter().any(|&b| b) + } + + fn is_dir_empty(&self, action: Dir) -> bool { + use DirDiag::*; + let any = match action { + Dir::North => self[North] || self[NorthWest] || self[NorthEast], + Dir::East => self[East] || self[NorthEast] || self[SouthEast], + Dir::South => self[South] || self[SouthWest] || self[SouthEast], + Dir::West => self[West] || self[NorthWest] || self[SouthWest], + }; + !any + } +} + +enum HasMoved { + Yes, + No, +} + +fn solve_first_puzzle(reader: R) -> usize { + let mut playground = Playground::parse_input(reader); + for _ in 1..11 { + playground.simulate_round(); + } + playground.count_empty_tiles() +} + +fn solve_second_puzzle(reader: R) -> usize { + let mut playground = Playground::parse_input(reader); + let mut curr_round = 1; + loop { + let has_moved = playground.simulate_round(); + if let HasMoved::No = has_moved { + break curr_round; + } + curr_round += 1; + } +} + +fn main() { + let mut args = ::std::env::args().skip(1); + let file = args.next().unwrap_or_else(|| String::from("./input")); + let solve = args + .next() + .map(|arg| match arg.as_str() { + "first" => SolvePuzzle::First, + "second" => SolvePuzzle::Second, + _ => unreachable!(), + }) + .unwrap_or_default(); + let file = BufReader::new(File::open(file).expect("Opening file")); + match solve { + SolvePuzzle::First => println!("{}", solve_first_puzzle(file)), + SolvePuzzle::Second => println!("{}", solve_second_puzzle(file)), + }; +} + +impl Index for Surrounding { + type Output = bool; + + fn index(&self, index: DirDiag) -> &Self::Output { + &self.raw[index as usize] + } +} + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use super::*; + + #[test] + fn example_on_first() { + let reader = Cursor::new(include_str!("../input.test")); + assert_eq!(solve_first_puzzle(reader), 110); + } + + #[test] + fn example_on_second() { + let reader = Cursor::new(include_str!("../input.test")); + assert_eq!(solve_second_puzzle(reader), 20); + } }