year2017/
day22.rs

1use utils::grid;
2use utils::prelude::*;
3
4/// Simulating virus spread through a grid.
5#[derive(Clone, Debug)]
6pub struct Day22 {
7    size: usize,
8    grid: Vec<State>,
9}
10
11#[derive(Copy, Clone, Debug, PartialEq)]
12enum State {
13    // The enum discriminants are set such that:
14    // - The next direction can be found by adding the current state and direction (mod 4)
15    // - The next state in part 1 is found by adding 2 (mod 4), cycling between Clean and Infected
16    // - The next state in part 2 is found by adding 1 (mod 4)
17    // They must also match the From implementation
18    Clean = 3,
19    Weakened = 0,
20    Infected = 1,
21    Flagged = 2,
22}
23
24#[derive(Copy, Clone, Debug)]
25enum Direction {
26    // Discriminants must match the From implementation
27    Up = 0,
28    Right,
29    Down,
30    Left,
31}
32
33const PADDING: usize = 250;
34
35impl Day22 {
36    pub fn new(input: &str, _: InputType) -> Result<Self, InputError> {
37        let (rows, cols, grid) = grid::from_str(input, |b| match b {
38            b'.' => Some(State::Clean),
39            b'#' => Some(State::Infected),
40            _ => None,
41        })?;
42        if rows != cols || rows % 2 == 0 {
43            return Err(InputError::new(input, 0, "expected odd size square grid"));
44        }
45        Ok(Self { size: rows, grid })
46    }
47
48    #[must_use]
49    pub fn part1(&self) -> u32 {
50        self.simulate(10_000, |state| State::from(state as usize + 2))
51    }
52
53    #[must_use]
54    pub fn part2(&self) -> u32 {
55        self.simulate(10_000_000, |state| State::from(state as usize + 1))
56    }
57
58    fn simulate(&self, bursts: u32, next_state: impl Fn(State) -> State) -> u32 {
59        let size = self.size + (2 * PADDING);
60        let mut grid = vec![State::Clean; size * size];
61
62        for row in 0..self.size {
63            let offset = ((PADDING + row) * size) + PADDING;
64            grid[offset..offset + self.size]
65                .copy_from_slice(&self.grid[row * self.size..(row + 1) * self.size]);
66        }
67
68        let direction_offsets = [-(size as isize), 1, size as isize, -1];
69        let mut direction = Direction::Up;
70        let mut index = grid.len() / 2;
71
72        let mut infected_transitions = 0;
73        for _ in 0..bursts {
74            let state = grid[index];
75            let next = next_state(state);
76
77            direction = Direction::from(state as usize + direction as usize);
78            grid[index] = next;
79            index = index.wrapping_add_signed(direction_offsets[direction as usize]);
80
81            infected_transitions += u32::from(next == State::Infected);
82        }
83
84        infected_transitions
85    }
86}
87
88impl From<usize> for State {
89    #[inline]
90    fn from(value: usize) -> Self {
91        match value % 4 {
92            3 => State::Clean,
93            0 => State::Weakened,
94            1 => State::Infected,
95            2 => State::Flagged,
96            _ => unreachable!(),
97        }
98    }
99}
100
101impl From<usize> for Direction {
102    #[inline]
103    fn from(value: usize) -> Self {
104        match value % 4 {
105            0 => Direction::Up,
106            1 => Direction::Right,
107            2 => Direction::Down,
108            3 => Direction::Left,
109            _ => unreachable!(),
110        }
111    }
112}
113
114examples!(Day22 -> (u32, u32) [
115    {input: "..#\n#..\n...", part1: 5587, part2: 2511944},
116]);