#[derive(Debug, Eq, PartialEq, Clone)] pub enum Cell { Edge, Floor, Empty, Occupied, } #[derive(Debug, Eq, PartialEq)] pub enum Ruleset { PartOne, PartTwo, } impl From for Cell { fn from(input: char) -> Self { match input { '@' => Self::Edge, '.' => Self::Floor, 'L' => Self::Empty, '#' => Self::Occupied, _ => unreachable!("{input}"), } } } #[derive(Debug)] pub struct Grid { pub line_length: isize, pub occupied_cell_count: usize, pub occupied_cell_tolerance: usize, pub cells: Vec, pub ruleset: Ruleset, } impl Grid { pub fn new(input: &str, ruleset: Ruleset) -> Self { let line_length = input.find('\n').unwrap() + 2; // Deal with edge cases by just adding an extra cell to the outer edges. let extra_floor = "@".repeat(line_length - 2); let data = format!("{}\n{}\n{}", extra_floor, input, extra_floor); let cells = data .trim() .replace('\n', "@@") .chars() .map(Into::into) .collect(); let occupied_cell_tolerance = match ruleset { Ruleset::PartOne => 4, Ruleset::PartTwo => 5, }; Self { line_length: line_length as isize, occupied_cell_count: 0, occupied_cell_tolerance, cells, ruleset, } } pub fn simulate_step(&self) -> Vec { let mut cells = self.cells.clone(); for (index, cell) in self.cells.iter().enumerate() { let index = index as isize; let mut occupied_cells = 0; let adjacencies = vec![ // Top Left -self.line_length - 1, // Above -self.line_length, // Top Right -self.line_length + 1, // Left -1, // Right 1, // Bottom Left self.line_length - 1, // Below self.line_length, // Bottom Right self.line_length + 1, ]; if self.ruleset == Ruleset::PartOne { for mut adjacency in adjacencies { adjacency += index; if adjacency < 0 { continue; } if self.cells.get(adjacency as usize) == Some(&Cell::Occupied) { occupied_cells += 1; } } } else if self.ruleset == Ruleset::PartTwo { for adjacency in adjacencies { let mut target_index = index; 'inner: loop { target_index += adjacency; if target_index < 0 || target_index as usize > self.cells.len() { break 'inner; } match self.cells.get(target_index as usize) { Some(&Cell::Empty) | Some(&Cell::Edge) => break 'inner, Some(&Cell::Occupied) => { occupied_cells += 1; break 'inner; } _ => (), }; } } } if cell == &Cell::Edge || cell == &Cell::Floor { continue; } if cell == &Cell::Empty && occupied_cells == 0 { cells[index as usize] = Cell::Occupied; } if cell == &Cell::Occupied && occupied_cells >= self.occupied_cell_tolerance { cells[index as usize] = Cell::Empty; } } cells } pub fn count_cells(&self, target: Cell) -> usize { self.cells.iter().filter(|cell| cell == &&target).count() } // Useful for debugging. pub fn _draw(&self) -> String { let mut result = String::new(); for (index, cell) in self.cells.iter().enumerate() { if index % self.line_length as usize == 0 { result += "\n"; } result += match cell { Cell::Edge => "@", Cell::Floor => ".", Cell::Empty => "L", Cell::Occupied => "#", }; } result } }