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
//! # Like a GIF For Your Yard
//!
//! To solve this efficiently we use a [SWAR](https://en.wikipedia.org/wiki/SWAR) approach,
//! packing 16 lights into a `u64` taking 4 bits each. We calculate the next generation using no
//! conditional statements with the following steps.
//!
//! 1. Pack the input bytes into register values that can be represented as hex digits.
//!
//! ```none
//!     #...#    10001
//!     .#.#. => 01010
//!     ###.#    11101
//! ```
//!
//! 2. Add left and right neighbors to each column horizontally, shifting in zeroes at the edge.
//!
//! ```none
//!     11011
//!     11211
//!     23221
//! ```
//!
//! 3. Add 3 rows together to give the total sum including the light itself:
//!
//! ```none
//!     45443
//! ```
//!
//! 4. Subtract the middle row to get neighbors only.
//!
//! ```none
//!     44433
//! ```
//!
//! 5. Apply the rules using only bitwise logic.
//!
//! Consider the binary representation of a 4 bit hex digit.
//! * A cell stays on if it has 2 or 3 neigbours, binary `0010` or binary `0011`.
//! * A cell turns on if it has exactly 3 neighbors, binary `0011`.
//!
//! If we `OR` the neighbor count with the current cell, either `0000` or `0001` then the
//! binary representation of a lit cell will always be `0011`.
//!
//! Labelling the bits `abcd` then the next cell is `!a & !b & c & d`.
type Lights = [[u64; 7]; 100];

/// Pack the lights into 4 bits each in [big-endian order](https://en.wikipedia.org/wiki/Endianness).
pub fn parse(input: &str) -> Lights {
    let mut grid = default();

    for (y, row) in input.lines().enumerate() {
        for (x, col) in row.bytes().enumerate() {
            let index = x / 16;
            let offset = 4 * (15 - (x % 16));
            let bit = (col & 1) as u64;
            grid[y][index] |= bit << offset;
        }
    }

    grid
}

pub fn part1(input: &Lights) -> u32 {
    game_of_life(input, false)
}

pub fn part2(input: &Lights) -> u32 {
    game_of_life(input, true)
}

fn game_of_life(input: &Lights, part_two: bool) -> u32 {
    let mut grid = *input;
    let mut temp = default();
    let mut next = default();

    for _ in 0..100 {
        for y in 0..100 {
            for x in 0..7 {
                // Add left and right neighbors from this block.
                let mut sum = grid[y][x] + (grid[y][x] >> 4) + (grid[y][x] << 4);

                // Add immediate right or left neighbor from previous or next block.
                if x > 0 {
                    sum += grid[y][x - 1] << 60;
                }
                if x < 6 {
                    sum += grid[y][x + 1] >> 60;
                }

                temp[y][x] = sum;
            }
        }

        for y in 0..100 {
            for x in 0..7 {
                // Get neighbor count by summing the rows above and below the light
                // then subtracting the light itself.
                let mut sum = temp[y][x] - grid[y][x];

                if y > 0 {
                    sum += temp[y - 1][x];
                }
                if y < 99 {
                    sum += temp[y + 1][x];
                }

                // Calculate the next generation with no conditional statements.
                let a = sum >> 3;
                let b = sum >> 2;
                let c = sum >> 1;
                let d = sum | grid[y][x];

                next[y][x] = (!a & !b & c & d) & 0x1111111111111111;
            }

            // 100 = 16 * 6 + 4 = so only use the first 4 places of the last element.
            next[y][6] &= 0x1111000000000000;
        }

        // Set corner lights to always on.
        if part_two {
            next[0][0] |= 1 << 60;
            next[0][6] |= 1 << 48;
            next[99][0] |= 1 << 60;
            next[99][6] |= 1 << 48;
        }

        (grid, next) = (next, grid);
    }

    grid.iter().map(|row| row.iter().map(|n| n.count_ones()).sum::<u32>()).sum()
}

fn default() -> Lights {
    [[0; 7]; 100]
}