use std::iter::repeat;
pub struct Input {
rules: Vec<usize>,
state: Tunnel,
}
#[derive(Clone)]
pub struct Tunnel {
plants: Vec<usize>,
start: i64,
sum: i64,
}
pub fn parse(input: &str) -> Input {
let lines: Vec<_> = input.lines().map(str::as_bytes).collect();
let plants: Vec<_> = lines[0][15..].iter().map(|b| (b & 1) as usize).collect();
let mut rules = vec![0; 32];
for line in &lines[2..] {
let binary = line.iter().fold(0, |acc, b| (acc << 1) | (b & 1) as usize);
rules[binary >> 5] = binary & 1;
}
Input { rules, state: Tunnel { plants, start: 0, sum: 0 } }
}
pub fn part1(input: &Input) -> i64 {
let mut current = input.state.clone();
for _ in 0..20 {
current = step(&input.rules, ¤t);
}
current.sum
}
pub fn part2(input: &Input) -> i64 {
let mut current = input.state.clone();
let mut delta = 0;
let mut generations = 0;
loop {
let next = step(&input.rules, ¤t);
let next_delta = next.sum - current.sum;
if delta == next_delta {
break current.sum + delta * (50_000_000_000 - generations);
}
current = next;
delta = next_delta;
generations += 1;
}
}
fn step(rules: &[usize], tunnel: &Tunnel) -> Tunnel {
let mut index = 0;
let mut sum = 0;
let mut position = tunnel.start - 2;
let mut plants = Vec::with_capacity(1_000);
for plant in tunnel.plants.iter().chain(repeat(&0).take(4)) {
index = ((index << 1) | plant) & 0b11111;
sum += position * rules[index] as i64;
position += 1;
plants.push(rules[index]);
}
Tunnel { plants, start: tunnel.start - 2, sum }
}