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
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
//! # Unstable Diffusion
//!
//! We represent elves as bits in a integer then use bitwise operations to efficiently figure
//! out the movement for multiple elves at once.
use self::Direction::*;
use std::ops::{BitAnd, BitAndAssign, BitOr, Not};

/// The initial grid is 70 x 70. Elves stop moving when no other elf is adjacent so the grid
/// will expand at most 70 in any direction, giving 70 + 70 + 70 = 210 total.
const HEIGHT: usize = 210;

/// Duct tape two `u128`s together.
#[derive(Clone, Copy, Default)]
pub struct U256 {
    left: u128,
    right: u128,
}

impl U256 {
    fn bit_set(&mut self, offset: usize) {
        if offset < 128 {
            self.left |= 1 << (127 - offset);
        } else {
            self.right |= 1 << (255 - offset);
        }
    }

    fn count_ones(&self) -> u32 {
        self.left.count_ones() + self.right.count_ones()
    }

    fn non_zero(&self) -> bool {
        self.left != 0 || self.right != 0
    }

    /// Used to find the bounding rectangle for part one.
    fn min_set(&self) -> Option<u32> {
        if self.left != 0 {
            Some(self.left.leading_zeros())
        } else if self.right != 0 {
            Some(128 + self.right.leading_zeros())
        } else {
            None
        }
    }

    /// Used to find the bounding rectangle for part one.
    fn max_set(&self) -> Option<u32> {
        if self.right != 0 {
            Some(255 - self.right.trailing_zeros())
        } else if self.left != 0 {
            Some(127 - self.left.trailing_zeros())
        } else {
            None
        }
    }

    fn left_shift(&self) -> U256 {
        U256 { left: (self.left << 1) | (self.right >> 127), right: (self.right << 1) }
    }

    fn right_shift(&self) -> U256 {
        U256 { left: (self.left >> 1), right: (self.left << 127) | (self.right >> 1) }
    }
}

/// Syntactic sugar to provide the regular `&`, `|` and `!` bitwise operator notation.
impl BitAnd for U256 {
    type Output = U256;

    fn bitand(self, rhs: U256) -> U256 {
        U256 { left: self.left & rhs.left, right: self.right & rhs.right }
    }
}

impl BitOr for U256 {
    type Output = U256;

    fn bitor(self, rhs: U256) -> U256 {
        U256 { left: self.left | rhs.left, right: self.right | rhs.right }
    }
}

impl Not for U256 {
    type Output = U256;

    fn not(self) -> U256 {
        U256 { left: !self.left, right: !self.right }
    }
}

impl BitAndAssign for U256 {
    fn bitand_assign(&mut self, rhs: U256) {
        self.left &= rhs.left;
        self.right &= rhs.right;
    }
}

enum Direction {
    North,
    South,
    West,
    East,
}

#[derive(Clone, Copy)]
pub struct Input {
    grid: [U256; HEIGHT],
    north: [U256; HEIGHT],
    south: [U256; HEIGHT],
    west: [U256; HEIGHT],
    east: [U256; HEIGHT],
}

/// Converts the ASCII grid into a bit per elf.
pub fn parse(input: &str) -> Input {
    // Enough buffer so that elves won't overflow the edges of the grid.
    let offset = 70;
    let raw: Vec<_> = input.lines().map(str::as_bytes).collect();
    let default = [U256::default(); HEIGHT];
    let mut grid = default;

    for (y, row) in raw.iter().enumerate() {
        for (x, col) in row.iter().enumerate() {
            if *col == b'#' {
                grid[offset + y].bit_set(offset + x);
            }
        }
    }

    Input { grid, north: default, south: default, west: default, east: default }
}

pub fn part1(input: &Input) -> u32 {
    let mut input = *input;
    let mut order = [North, South, West, East];

    for _ in 0..10 {
        step(&mut input, &mut order);
    }

    // Find the bounding rectangle.
    let grid = input.grid;
    let elves: u32 = grid.iter().map(U256::count_ones).sum();
    let min_x = grid.iter().filter_map(U256::min_set).min().unwrap();
    let max_x = grid.iter().filter_map(U256::max_set).max().unwrap();
    let min_y = grid.iter().position(U256::non_zero).unwrap() as u32;
    let max_y = grid.iter().rposition(U256::non_zero).unwrap() as u32;

    (max_x - min_x + 1) * (max_y - min_y + 1) - elves
}

pub fn part2(input: &Input) -> u32 {
    let mut input = *input;
    let mut order = [North, South, West, East];
    let mut moved = true;
    let mut count = 0;

    while moved {
        moved = step(&mut input, &mut order);
        count += 1;
    }

    count
}

fn step(input: &mut Input, order: &mut [Direction]) -> bool {
    let Input { grid, north, south, west, east } = input;
    // Optimization to avoid processing empty rows.
    let start = grid.iter().position(U256::non_zero).unwrap() - 1;
    let end = grid.iter().rposition(U256::non_zero).unwrap() + 2;

    let mut moved = false;

    let mut prev;
    // Find horizontal neighbors in each row. To make movement calculations easier
    // we invert so that a bit is 1 is movement is *possible*.
    let mut cur = !(grid[0].right_shift() | grid[0] | grid[0].left_shift());
    let mut next = !(grid[1].right_shift() | grid[1] | grid[1].left_shift());

    for i in start..end {
        // Calculating neighbors is relatively expensive so re-use results between rows.
        prev = cur;
        cur = next;
        next = !(grid[i + 1].right_shift() | grid[i + 1] | grid[i + 1].left_shift());

        let mut up = prev;
        let mut down = next;
        // Find neighours in vertical columns.
        let vertical = !(grid[i - 1] | grid[i] | grid[i + 1]);
        let mut left = vertical.right_shift();
        let mut right = vertical.left_shift();
        // Elves need at least 1 neighbor to propose moving.
        let mut remaining = grid[i] & !(up & down & left & right);

        // Consider each direction one at a time, removing any elves who propose it.
        for direction in &*order {
            match direction {
                North => {
                    up &= remaining;
                    remaining &= !up;
                }
                South => {
                    down &= remaining;
                    remaining &= !down;
                }
                West => {
                    left &= remaining;
                    remaining &= !left;
                }
                East => {
                    right &= remaining;
                    remaining &= !right;
                }
            }
        }

        // Copy final proposals to an array for each direction.
        north[i - 1] = up;
        south[i + 1] = down;
        west[i] = left.left_shift();
        east[i] = right.right_shift();
    }

    // Elves that propose moving to the same spot cancel each other out and no-one moves.
    // Due to the movement rules we only need to check horizontal and vertical movement into
    // the same spot (horizontal and vertical movement can never collide with each other).
    for i in start..end {
        let up = north[i];
        let down = south[i];
        let left = west[i];
        let right = east[i];
        north[i] &= !down;
        south[i] &= !up;
        west[i] &= !right;
        east[i] &= !left;
    }

    for i in start..end {
        // Stationary elves.
        let same =
            grid[i] & !(north[i - 1] | south[i + 1] | west[i].right_shift() | east[i].left_shift());
        // Moving elves.
        let change = north[i] | south[i] | west[i] | east[i];
        grid[i] = same | change;
        moved |= change.non_zero();
    }

    // Rotate the order of movement proposals for the next turn.
    order.rotate_left(1);
    moved
}