diff --git a/day24/Cargo.toml b/day24/Cargo.toml index 363fc6b..5dd216e 100644 --- a/day24/Cargo.toml +++ b/day24/Cargo.toml @@ -7,3 +7,4 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +ndarray = "0.14.0" diff --git a/day24/src/game.rs b/day24/src/game.rs new file mode 100644 index 0000000..39e4c59 --- /dev/null +++ b/day24/src/game.rs @@ -0,0 +1,121 @@ +use ndarray::Array2; + +use crate::locations::{CellLocation, MINICUBE_DIFF_ARRAY}; + +struct CellInfo { + is_alive: bool, // alive = black, dead = white + alive_minicube_count: usize, +} + +impl Default for CellInfo { + fn default() -> CellInfo { + CellInfo { + is_alive: false, + alive_minicube_count: 0, + } + } +} + +impl CellInfo { + pub fn set_alive(&mut self) { + self.is_alive = true; + } + + pub fn add_alive_minicube(&mut self) { + self.alive_minicube_count += 1; + } + + pub fn set_dead(&mut self) { + self.is_alive = false; + } + + pub fn add_dead_minicube(&mut self) { + self.alive_minicube_count -= 1; + } +} + +pub struct Game { + board: Array2, + corner: CellLocation, +} + +impl Game { + fn make_alive(&mut self, cell_location: CellLocation) { + self.board[cell_location].set_alive(); + for &diff in &MINICUBE_DIFF_ARRAY { + self.board[cell_location + diff].add_alive_minicube(); + } + } + + fn make_dead(&mut self, cell_location: CellLocation) { + self.board[cell_location].set_dead(); + for &diff in &MINICUBE_DIFF_ARRAY { + self.board[cell_location + diff].add_dead_minicube(); + } + } + + pub fn next_step(&mut self) -> usize { + let mut new_alive: Vec<_> = vec![]; + let mut new_dead: Vec<_> = vec![]; + for x in 0..self.corner.x { + for y in 0..self.corner.y { + let location = CellLocation { x, y }; + match &self.board[location] { + CellInfo { is_alive: true, alive_minicube_count } if *alive_minicube_count == 0 || *alive_minicube_count > 2 => { + new_dead.push(location); + }, + CellInfo { is_alive: false, alive_minicube_count: 2 } => { + new_alive.push(location); + }, + _ => {} + } + } + } + + let changes_count = new_alive.len() + new_dead.len(); + for location in new_alive { + self.make_alive(location); + } + for location in new_dead { + self.make_dead(location); + } + + changes_count + } + + pub fn from_input(input_data: &[(i16, i16)], max_number_of_steps: usize) -> Self { + let min_x = input_data.iter().map(|(x, _y)| *x).min().unwrap(); + let max_x = input_data.iter().map(|(x, _y)| *x).max().unwrap(); + let min_y = input_data.iter().map(|(_x, y)| *y).min().unwrap(); + let max_y = input_data.iter().map(|(_x, y)| *y).max().unwrap(); + let input_size_x = max_x - min_x; + let input_size_y = max_y - min_y; + let size_x = (input_size_x as usize) + max_number_of_steps * 2; + let size_y = (input_size_y as usize) + max_number_of_steps * 2; + let offset_x = (max_number_of_steps as i16) - min_x; + let offset_y = (max_number_of_steps as i16) - min_y; + let corner = CellLocation { + x: size_x, + y: size_y, + }; + let board = Array2::default((corner.x, corner.y)); + let mut game = Game { + board, + corner, + }; + + for (input_x, input_y) in input_data { + game.make_alive(CellLocation { + x: (input_x + offset_x) as usize, + y: (input_y + offset_y) as usize, + }); + } + + return game; + } + + pub fn get_alive_count(&self) -> usize { + (&self.board).iter().filter(|&cell| cell.is_alive).count() + } +} + diff --git a/day24/src/locations.rs b/day24/src/locations.rs new file mode 100644 index 0000000..5e76345 --- /dev/null +++ b/day24/src/locations.rs @@ -0,0 +1,49 @@ +use std::ops::{Add, Index, IndexMut}; + +use ndarray::Array2; + +#[derive(Copy, Clone)] +pub struct CellDiff { + x: i8, + y: i8, +} + +pub const MINICUBE_DIFF_ARRAY: [CellDiff; 6] = [ + CellDiff { x: -1, y: 0 }, + CellDiff { x: 0, y: 1 }, + CellDiff { x: 1, y: 1 }, + CellDiff { x: 1, y: 0 }, + CellDiff { x: 0, y: -1 }, + CellDiff { x: -1, y: -1 }, +]; + +#[derive(Copy, Clone)] +pub struct CellLocation { + pub x: usize, + pub y: usize, +} + +impl Add for CellLocation { + type Output = Self; + + fn add(self, other: CellDiff) -> Self { + CellLocation { + x: (self.x as isize + other.x as isize) as usize, + y: (self.y as isize + other.y as isize) as usize, + } + } +} + +impl Index for Array2 { + type Output = T; + fn index<'a>(&'a self, cell_location: CellLocation) -> &'a T { + &self[[cell_location.x, cell_location.y]] + } +} + +impl IndexMut for Array2 { + fn index_mut<'a>(&'a mut self, cell_location: CellLocation) -> &'a mut T { + &mut self[[cell_location.x, cell_location.y]] + } +} + diff --git a/day24/src/main.rs b/day24/src/main.rs index fe7cff4..35415e5 100644 --- a/day24/src/main.rs +++ b/day24/src/main.rs @@ -1,35 +1,47 @@ use std::collections::HashSet; use std::io::{self, BufRead}; +mod game; +mod locations; + +use crate::game::Game; + +const MAX_NUMBER_OF_STEPS: usize = 100; + fn main() { let stdin = io::stdin(); - let mut changed_tiles = HashSet::new(); + let mut alive_tiles = HashSet::new(); for line_result in stdin.lock().lines() { - let processed_line = line_result.unwrap().replace("nw", "7").replace("ne", "9").replace("sw", "1").replace("se", "3"); - let mut x = 0; - let mut y = 0; + let processed_line = line_result.unwrap().replace("nw", "n").replace("se", "s"); + let mut x = 0i16; + let mut y = 0i16; for ch in processed_line.chars() { x += match ch { - 'e' => 2, - 'w' => -2, - '9' | '3' => 1, - '7' | '1' => -1, + 'e' => 1, + 'w' => -1, _ => 0, }; y += match ch { - '9' | '7' => 1, - '3' | '1' => -1, + 'n' => 1, + 's' => -1, _ => 0, }; } let tile = (x, y); - if changed_tiles.contains(&tile) { - changed_tiles.remove(&tile); + if alive_tiles.contains(&tile) { + alive_tiles.remove(&tile); } else { - changed_tiles.insert(tile); + alive_tiles.insert(tile); } } - println!("{}", changed_tiles.len()); + let mut game = Game::from_input(&alive_tiles.iter().cloned().collect::>(), MAX_NUMBER_OF_STEPS); + + println!("{}", game.get_alive_count()); + + for i in 1..=MAX_NUMBER_OF_STEPS { + game.next_step(); + println!("Day {}: {}", i, game.get_alive_count()); + } }