1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
//! # Settlers of The North Pole
//!
//! This problem is a cellular automaton similar to the well known
//! [Game of Life](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life). To solve part two
//! we look for a [cycle](https://en.wikipedia.org/wiki/Cycle_detection) then
//! extrapolate forward a billion generations.
//!
//! To efficiently compute the next generation a [SWAR](https://en.wikipedia.org/wiki/SWAR)
//! approach is used. The count of trees and lumberyards is packed into a `u64` so that we can
//! process 8 acres at a time. Lumberyards are stored in the high nibble of each byte
//! and trees in the low nibble. For example:
//!
//! ```none
//!     .#.#...|
//!     .....#|# => 11 11 21 11 21 02 21 02 => 0x1111211121022102
//!     .|..|...
//! ```
//!
//! The total number of adjacent trees or lumberyards is then calculated in two passes.
//! First the horizontal sum of each row is computed by bit shifting left and right by 8.
//! Then the vertical sum of 3 horizontal sums gives the total.
//!
//! Bitwise logic then computes the next generation in batches of 8 acres at a time.
use crate::util::hash::*;
use std::hash::{Hash, Hasher};

/// Bitwise logic galore.
const OPEN: u64 = 0x00;
const TREE: u64 = 0x01;
const LUMBERYARD: u64 = 0x10;
const EDGE: u64 = 0xffff000000000000;
const LOWER: u64 = 0x0f0f0f0f0f0f0f0f;
const UPPER: u64 = 0xf0f0f0f0f0f0f0f0;
const THIRTEENS: u64 = 0x0d0d0d0d0d0d0d0d;
const FIFTEENS: u64 = 0x0f0f0f0f0f0f0f0f;

/// New type wrapper so that we can use a custom hash function.
#[derive(PartialEq, Eq)]
pub struct Key {
    area: [u64; 350],
}

/// Hash only two cells as a reasonable tradeoff between speed and collision resistance.
impl Hash for Key {
    fn hash<H: Hasher>(&self, state: &mut H) {
        self.area[100].hash(state);
        self.area[200].hash(state);
    }
}

/// Pack the input into an array of `u64`.
/// The input is 50 acres wide, so requires `ceil(50 / 8) = 7` elements for each row.
pub fn parse(input: &str) -> Key {
    let mut area = [0; 350];

    for (y, line) in input.lines().map(str::as_bytes).enumerate() {
        for (x, byte) in line.iter().enumerate() {
            let acre = match byte {
                b'|' => TREE,
                b'#' => LUMBERYARD,
                _ => OPEN,
            };
            let index = (y * 7) + (x / 8);
            let offset = 56 - 8 * (x % 8);
            area[index] |= acre << offset;
        }
    }

    Key { area }
}

/// Compute 10 generations.
pub fn part1(input: &Key) -> u32 {
    let mut area = input.area;
    let mut rows = [0; 364];

    for _ in 0..10 {
        step(&mut area, &mut rows);
    }

    resource_value(&area)
}

/// Compute generations until a cycle is detected.
pub fn part2(input: &Key) -> u32 {
    let mut area = input.area;
    let mut rows = [0; 364];
    let mut seen = FastMap::with_capacity(1_000);

    for minute in 1.. {
        step(&mut area, &mut rows);

        if let Some(previous) = seen.insert(Key { area }, minute) {
            // Find the index of the state after 1 billion repetitions.
            let offset = 1_000_000_000 - previous;
            let cycle_width = minute - previous;
            let remainder = offset % cycle_width;
            let target = previous + remainder;

            let (result, _) = seen.iter().find(|(_, &i)| i == target).unwrap();
            return resource_value(&result.area);
        }
    }

    unreachable!()
}

fn step(area: &mut [u64], rows: &mut [u64]) {
    // Compute the horizontal sum of each column with its immediate neighbors.
    for y in 0..50 {
        // Shadow slices at correct starting offset for convenience. We pad `rows` on the top and
        // bottom then shift index by 7 to avoid having to check for edge conditions.
        let area = &area[7 * y..];
        let rows = &mut rows[7 * (y + 1)..];

        rows[0] = horizontal_sum(0, area[0], area[1]);
        rows[1] = horizontal_sum(area[0], area[1], area[2]);
        rows[2] = horizontal_sum(area[1], area[2], area[3]);
        rows[3] = horizontal_sum(area[2], area[3], area[4]);
        rows[4] = horizontal_sum(area[3], area[4], area[5]);
        rows[5] = horizontal_sum(area[4], area[5], area[6]);
        rows[6] = horizontal_sum(area[5], area[6], 0);

        // The grid is 50 wide so the last 6 bytes in each row are unused and must be set to zero.
        rows[6] &= EDGE;
    }

    for i in 0..350 {
        // Sum of all adjacent trees and lumberyards, not including center acre.
        let acre = area[i];
        let sum = rows[i] + rows[i + 7] + rows[i + 14] - acre;

        // Add 13 so that any values 3 and higher overflow into high nibble.
        let mut to_tree = (sum & LOWER) + THIRTEENS;
        // Clear low nibble as this is irrelevant.
        to_tree &= UPPER;
        // To become a tree, we must be open space.
        to_tree &= !(acre | (acre << 4));
        // Shift result back to low nibble.
        to_tree >>= 4;

        // Check for any values 3 or higher.
        let mut to_lumberyard = ((sum >> 4) & LOWER) + THIRTEENS;
        // Clear low nibble.
        to_lumberyard &= UPPER;
        // To become a lumberyard, we must already be a tree.
        to_lumberyard &= acre << 4;
        // Spread result to both high and low nibble. We will later XOR this to flip correct bits.
        to_lumberyard |= to_lumberyard >> 4;

        // We must be a lumberyard.
        let mut to_open = acre & UPPER;
        // Check for at least one adjacent tree.
        to_open &= (sum & LOWER) + FIFTEENS;
        // Check for at least one adjacent lumberyard.
        to_open &= ((sum >> 4) & LOWER) + FIFTEENS;
        // Flip bit as we will later XOR.
        to_open ^= acre & UPPER;

        // Flip relevant bits to transition to next state.
        area[i] = acre ^ (to_tree | to_lumberyard | to_open);
    }
}

/// Convenience method that also takes correct byte from left and right neighbors.
#[inline]
fn horizontal_sum(left: u64, middle: u64, right: u64) -> u64 {
    (left << 56) + (middle >> 8) + middle + (middle << 8) + (right >> 56)
}

/// Each tree or lumberyard is represented by a single bit.
fn resource_value(area: &[u64]) -> u32 {
    let trees: u32 = area.iter().map(|n| (n & LOWER).count_ones()).sum();
    let lumberyards: u32 = area.iter().map(|n| (n & UPPER).count_ones()).sum();
    trees * lumberyards
}