advent-of-code-2024/day-17/main.c

243 lines
6.5 KiB
C

#include "aoc.h"
typedef struct {
u64 a, b, c;
i64 ip;
} CPU;
static u64
parse_register(str line, Arena arena) {
str_find_result colon = str_find_left(line, STR(":"));
ASSERT(colon.found);
u64 result = (u64) parse_i64(str_trim(str_sub(line, colon.offset + 1, line.len)), arena);
return result;
}
static u64
get_combo(CPU *cpu, u64 operand) {
if (operand <= 3) return operand;
else if (operand == 4) return cpu->a;
else if (operand == 5) return cpu->b;
else if (operand == 6) return cpu->c;
else NOT_REACHABLE();
}
typedef DYNAMIC_ARRAY(u8) Program;
static void
run(Arena *arena, CPU * restrict cpu, Program program, Program *output) {
output->len = 0;
while (cpu->ip >= 0 && cpu->ip < program.len) {
ASSERT(cpu->ip % 2 == 0);
u8 op = program.data[cpu->ip];
ASSERT(op >= 0);
ASSERT(op < 8);
u8 arg = program.data[cpu->ip + 1];
bool took_branch = false;
if (op == 0) {
// adv
cpu->a = cpu->a / (1ull << get_combo(cpu, (u64) arg));
}
else if (op == 1) {
// bxl
cpu->b = cpu->b ^ (u64) arg;
}
else if (op == 2) {
// bst
cpu->b = get_combo(cpu, (u64) arg) % 8;
}
else if (op == 3) {
// jnz
if (cpu->a != 0) {
cpu->ip = (u64) arg;
took_branch = true;
}
cpu->b = 0;
cpu->c = 0;
}
else if (op == 4) {
// bxc
cpu->b ^= cpu->c;
}
else if (op == 5) {
// out
u8 value = get_combo(cpu, (u64) arg) % 8;
*push(output, arena) = value;
}
else if (op == 6) {
// bdv
cpu->b = cpu->a / (1ull << get_combo(cpu, (u64) arg));
}
else if (op == 7) {
// cdv
cpu->c = cpu->a / (1ull << get_combo(cpu, (u64) arg));
}
else NOT_REACHABLE();
if (!took_branch) {
cpu->ip += 2;
}
}
}
static void
dump_program(Program program) {
for (int i = 0; i < program.len; i++) {
printf("%d", program.data[i]);
if (i < program.len - 1) {
putchar(',');
}
}
puts("");
}
static u8
process(u64 *a) {
u64 b, c;
b = *a % 8;
b ^= 2;
c = *a >> b;
b ^= c;
*a >>= 3;
b ^= 7;
return b % 8;
}
static bool
check(Program *target, u64 a) {
isize index = 0;
for (;index < target->len; index++) {
if (process(&a) != target->data[index]) {
return false;
}
}
return index == target->len;
}
static bool
check_suffix(Program *target, u64 a, i32 suffix_len) {
isize index = 0;
for (; index < target->len - suffix_len; index++) {
process(&a);
}
for (; index < target->len; index++) {
if (process(&a) != target->data[index]) {
return false;
}
}
return index == target->len;
}
static i64
solve(Program *target, i32 index, u64 a) {
if (index == -1) {
return a;
}
else {
for (u64 i = 0; i < 8; i++) {
u64 new_a = a;
new_a |= (i << (3 * index));
if (check_suffix(target, new_a, target->len - index)) {
i64 result;
if ((result = solve(target, index - 1, new_a)) != -1) {
return result;
}
}
}
return -1;
}
}
int main(int argc, char **argv) {
Arena *arena = make_arena(Megabytes(1));
Tokens lines = read_lines(arena, argv[1]);
CPU cpu = {0};
cpu.a = parse_register(lines.tokens[0], *arena);
cpu.b = parse_register(lines.tokens[1], *arena);
cpu.c = parse_register(lines.tokens[2], *arena);
CPU initial = cpu;
Program program = {0};
str token;
str inp = lines.tokens[lines.len - 1];
str_find_result colon = str_find_left(inp, STR(":"));
inp = str_trim(str_sub(inp, colon.offset + 1, inp.len));
while ((token = str_next_token(&inp, STR(","))).len > 0) {
token = str_trim(token);
*push(&program, arena) = (u8) parse_i64(token, *arena);
}
/* dump_program(program); */
for (i32 i = 0; i < program.len; i += 2) {
u8 op = program.data[i];
u8 arg = program.data[i + 1];
char combo[16] = {0};
if (arg <= 3) {
snprintf(combo, sizeof(combo), "%d", arg);
} else if (arg == 4) {
snprintf(combo, sizeof(combo), "A");
} else if (arg == 5) {
snprintf(combo, sizeof(combo), "B");
} else if (arg == 6) {
snprintf(combo, sizeof(combo), "C");
}
switch (op) {
case 0: printf("A = A / (1 << %s)\n", combo); break;
case 1: printf("B = B ^ %d\n", arg); break;
case 2: printf("B = %s %% 8\n", combo); break;
case 3: printf("JNZ A, %d\n", arg); break;
case 4: printf("B = B ^ C\n"); break;
case 5: printf("OUT %s %% 8\n", combo); break;
case 6: printf("B = A / (1 << %s)\n", combo); break;
case 7: printf("C = A / (1 << %s)\n", combo); break;
default: NOT_REACHABLE();
}
}
Program output = {0};
run(arena, &cpu, program, &output);
dump_program(output);
// Part 2
/**
* Our input program in disassembled form:
* B = A % 8 ; equivalent to B = A & 0b111
* B = B ^ 2 ; flips the 2nd bit of B
* C = A / (1 << B) ; equivalent to C = A >> B
* B = B ^ C ; flips the bits of B that are set in C
* A = A / (1 << 3) ; equivalent to A = A >> 3
* B = B ^ 7 ; flips lower 3 bits of B
* OUT B % 8 ; outputs lower 3 bits of B
* JNZ A, 0
*
* Therefore, our input program processes A in chunks of 3 bits at a time,
* until A becomes 0.
* Registers B and C do not retain state between iterations of the program.
* Hence, the length of the program's output is the number of octals in A.
*
* Combined:
* B1 = A % 8
* B2 = B1 ^ 2
* C1 = A >> B2
* B3 = B2 ^ C1
* A2 = A >> 3
* B4 = B3 ^ 7
* OUT (B4 % 8)
* JNZ A2, 0
*
* OUT (B4 % 8) = OUT ((B3 ^ 7) % 8)
* = OUT (((B2 ^ C1) ^ 7) % 8)
* = OUT (((B2 ^ (A >> B2)) ^ 7) % 8)
* = OUT (((((A % 8) ^ 2) ^ (A >> ((A % 8) ^ 2))) ^ 7) % 8)
*
*/
i64 part_2 = solve(&program, program.len - 1, 0);
ASSERT(part_2 != -1);
printf("%ld\n", part_2);
return 0;
}