use crate::common::array_deque::ArrayDeque;
use crate::common::array_stack::ArrayStack;
use crate::input::{Input, on_error};
pub fn solve(input: &Input) -> Result<String, String> {
let mut program = ArrayStack::<32, u8>::new();
let mut registers = [0_u64; 3];
let mut current_register = 0;
for line in input.text.lines() {
if let Some(reg_value) = line.strip_prefix("Register ") {
registers[current_register] = reg_value[3..].parse().map_err(|_| on_error())?;
current_register += 1;
} else if let Some(program_str) = line.strip_prefix("Program: ") {
for n in program_str.split(',') {
program.push(n.parse().map_err(|_| on_error())?)?;
}
}
}
let mut computer = Computer {
program: program.slice(),
instruction_pointer: 0,
registers,
};
if input.is_part_one() {
let mut result = String::new();
while let Some(output) = computer.run_for_output() {
if !result.is_empty() {
result.push(',');
}
result.push_str(&format!("{output}"));
}
Ok(result)
} else {
if (
computer.program[computer.program.len() - 2],
computer.program[computer.program.len() - 1],
) != (3, 0)
{
return Err("Program does not end with 'jnz 0'".to_string());
}
let mut stack = ArrayDeque::<320, (u64, u64)>::new();
stack.push_back((computer.program.len() as u64, 0))?;
while let Some((offset_from_end, register_a_bits_so_far)) = stack.pop_front() {
for first_three_bits in 0..=0b111 {
let register_a_next_three_bits = (register_a_bits_so_far << 3) | first_three_bits;
computer.registers[0] = register_a_next_three_bits;
computer.instruction_pointer = 0;
if computer.run_for_output()
== Some(computer.program[offset_from_end as usize - 1] as u64)
{
if offset_from_end - 1 == 0 {
return Ok(format!("{register_a_next_three_bits}"));
}
stack.push_back((offset_from_end - 1, register_a_next_three_bits))?;
}
}
}
Err("No solution found".to_string())
}
}
struct Computer<'a> {
instruction_pointer: usize,
registers: [u64; 3],
program: &'a [u8],
}
impl Computer<'_> {
fn combo_operand(&self) -> u64 {
let val = self.program[self.instruction_pointer + 1];
match val {
0..=3 => val as u64,
4..=6 => self.registers[val as usize - 4],
_ => {
unreachable!();
}
}
}
const fn literal_operand(&self) -> u64 {
self.program[self.instruction_pointer + 1] as u64
}
fn run_for_output(&mut self) -> Option<u64> {
loop {
if (self.instruction_pointer + 1) >= self.program.len() {
return None;
}
let instruction_val = self.program[self.instruction_pointer];
match instruction_val {
0 => {
self.registers[0] >>= self.combo_operand() as u32;
}
1 => {
self.registers[1] ^= self.literal_operand();
}
2 => {
self.registers[1] = self.combo_operand() % 8;
}
3 => {
if self.registers[0] != 0 {
self.instruction_pointer = self.literal_operand() as usize;
continue;
}
}
4 => {
self.registers[1] ^= self.registers[2];
}
5 => {
let result = Some(self.combo_operand() % 8);
self.instruction_pointer += 2;
return result;
}
6 => {
self.registers[1] = self.registers[0] >> self.combo_operand() as u32;
}
7 => {
self.registers[2] = self.registers[0] >> self.combo_operand() as u32;
}
_ => {
return None;
}
}
self.instruction_pointer += 2;
}
}
}
#[test]
pub fn tests() {
let test_input = "Register A: 729
Register B: 0
Register C: 0
Program: 0,1,5,4,3,0";
test_part_one!(test_input => "4,6,3,5,6,3,5,2,1,0".to_string());
let real_input = include_str!("day17_input.txt");
test_part_one!(real_input => "3,7,1,7,2,1,0,6,3".to_string());
test_part_two!(real_input => "37221334433268".to_string());
}