163 lines
3.7 KiB
Rust
163 lines
3.7 KiB
Rust
|
#[derive(Debug, Eq, PartialEq, Clone)]
|
||
|
pub enum Cell {
|
||
|
Edge,
|
||
|
Floor,
|
||
|
Empty,
|
||
|
Occupied,
|
||
|
}
|
||
|
|
||
|
#[derive(Debug, Eq, PartialEq)]
|
||
|
pub enum Ruleset {
|
||
|
PartOne,
|
||
|
PartTwo,
|
||
|
}
|
||
|
|
||
|
impl From<char> 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<Cell>,
|
||
|
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<Cell> {
|
||
|
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
|
||
|
}
|
||
|
}
|