//! [`Grid`], [`Cell`] and [`Ruleset`] code. /// The different types of cell available in the [`Grid`]. #[derive(Debug, Eq, PartialEq, Clone)] pub enum Cell { /// The cell is an edge of the grid. Edge, /// The cell is a floor tile. Floor, /// The cell is an empty seat. Empty, /// The cell is an occupied seat. Occupied, } /// The different rulesets for part one and two. #[derive(Debug, Eq, PartialEq)] pub enum Ruleset { /// The ruleset for part one. PartOne, /// The ruleset for part two. PartTwo, } impl From for Cell { fn from(input: char) -> Self { match input { '@' => Self::Edge, '.' => Self::Floor, 'L' => Self::Empty, '#' => Self::Occupied, _ => unreachable!("{input}"), } } } /// The grid of seats. #[derive(Debug)] pub struct Grid { /// The length of lines from the puzzle input. pub line_length: isize, /// The amount of [`Cell`]s that are occupied. pub occupied_cell_count: usize, /// How tolerant people are for seats to become empty. pub occupied_cell_tolerance: usize, /// The list of [`Cell`]s. pub cells: Vec, /// Which [`Ruleset`] is being used. pub ruleset: Ruleset, } impl Grid { /// Create a new grid based on the puzzle input and which [`Ruleset`] to use. 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, } } /// Simulate a single step and return the new list of [`Cell`]s. 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 } /// Count the amount of [`Cell`]s matching a given target. pub fn count_cells(&self, target: Cell) -> usize { self.cells.iter().filter(|cell| cell == &&target).count() } /// Draw the grid, used 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 } }