diff --git a/userspace/aoc2024/CMakeLists.txt b/userspace/aoc2024/CMakeLists.txt index c0bbed1b..d5065881 100644 --- a/userspace/aoc2024/CMakeLists.txt +++ b/userspace/aoc2024/CMakeLists.txt @@ -15,6 +15,7 @@ set(AOC2024_PROJECTS day14 day15 day16 + day17 full ) diff --git a/userspace/aoc2024/day17/CMakeLists.txt b/userspace/aoc2024/day17/CMakeLists.txt new file mode 100644 index 00000000..6c68eb3e --- /dev/null +++ b/userspace/aoc2024/day17/CMakeLists.txt @@ -0,0 +1,9 @@ +set(SOURCES + main.cpp +) + +add_executable(aoc2024_day17 ${SOURCES}) +banan_include_headers(aoc2024_day17 ban) +banan_link_library(aoc2024_day17 libc) + +install(TARGETS aoc2024_day17 OPTIONAL) diff --git a/userspace/aoc2024/day17/main.cpp b/userspace/aoc2024/day17/main.cpp new file mode 100644 index 00000000..c120df5f --- /dev/null +++ b/userspace/aoc2024/day17/main.cpp @@ -0,0 +1,223 @@ +#include +#include + +#include +#include +#include + +using i8 = int8_t; +using i16 = int16_t; +using i32 = int32_t; +using i64 = int64_t; +using isize = ssize_t; + +using u8 = uint8_t; +using u16 = uint16_t; +using u32 = uint32_t; +using u64 = uint64_t; +using usize = size_t; + +struct Registers +{ + u64 a, b, c; + usize ip; +}; + +enum Opcodes +{ + ADV = 0, + BXL = 1, + BST = 2, + JNZ = 3, + BXC = 4, + OUT = 5, + BDV = 6, + CDV = 7, +}; + +using Program = BAN::Vector; + +struct ParseInputResult +{ + Registers registers; + Program program; +}; + +static ParseInputResult parse_input(FILE* fp) +{ + Registers registers; + ASSERT(fscanf(fp, + "Register A: %" SCNu64 "\n" + "Register B: %" SCNu64 "\n" + "Register C: %" SCNu64 "\n" + "\n" + "Program: ", + ®isters.a, ®isters.b, ®isters.c + ) == 3); + registers.ip = 0; + + char buffer[128]; + ASSERT(fgets(buffer, sizeof(buffer), fp)); + + Program program; + for (usize i = 0; buffer[i]; i++) + if (isdigit(buffer[i])) + MUST(program.push_back(buffer[i] - '0')); + + return ParseInputResult { + .registers = BAN::move(registers), + .program = BAN::move(program) + }; +} + +BAN::Vector emulate_program(Registers registers, const Program& program) +{ + const auto combo = + [®isters](u8 combo) -> u64 + { + switch (combo) + { + case 0: case 1: case 2: case 3: return combo; + case 4: return registers.a; + case 5: return registers.b; + case 6: return registers.c; + } + ASSERT_NOT_REACHED(); + }; + + BAN::Vector output; + while (registers.ip < program.size()) + { + const u8 opcode = program[registers.ip + 0]; + const u8 operand = program[registers.ip + 1]; + registers.ip += 2; + + switch (opcode) + { + case Opcodes::ADV: + registers.a = registers.a >> combo(operand); + break; + case Opcodes::BXL: + registers.b = registers.b ^ operand; + break; + case Opcodes::BST: + registers.b = combo(operand) & 0x07; + break; + case Opcodes::JNZ: + if (registers.a != 0) + registers.ip = operand; + break; + case Opcodes::BXC: + registers.b = registers.b ^ registers.c; + break; + case Opcodes::OUT: + MUST(output.push_back(combo(operand) & 0x07)); + break; + case Opcodes::BDV: + registers.b = registers.a >> combo(operand); + break; + case Opcodes::CDV: + registers.c = registers.a >> combo(operand); + break; + } + } + + return output; +} + +BAN::String part1(FILE* fp) +{ + auto [registers, program] = parse_input(fp); + auto output = emulate_program(registers, program); + + BAN::String result; + MUST(result.resize(output.size() * 2 - 1)); + + for (usize i = 0; i < result.size(); i++) + result[i] = (i % 2) ? ',' : output[i / 2] + '0'; + return result; +} + +static BAN::Optional recurse_part2(Registers initial_registers, u64 curr_a, usize curr_bits, usize output_done, const Program& program) +{ + if (output_done >= program.size()) + return {}; + + BAN::Optional result; + for (u64 val = 0; val < 8; val++) + { + const u64 next_a = curr_a | (val << curr_bits); + + auto registers = initial_registers; + registers.a = next_a; + + auto output = emulate_program(registers, program); + if (output.size() < output_done + 1) + continue; + if (output.size() > program.size()) + continue; + + bool match = true; + for (usize i = 0; i < output_done + 1 && match; i++) + if (output[i] != program[i]) + match = false; + if (!match) + continue; + + if (output_done + 1 == program.size()) + return next_a; + + auto temp = recurse_part2(registers, next_a, curr_bits + 3, output_done + 1, program); + if (temp.has_value()) + result = BAN::Math::min(result.value_or(BAN::numeric_limits::max()), temp.value()); + } + + return result; +} + +i64 part2(FILE* fp) +{ + auto [initial_registers, program] = parse_input(fp); + + BAN::Optional result; + for (u64 val = 0; val < 1024; val++) + { + auto registers = initial_registers; + registers.a = val; + + auto output = emulate_program(registers, program); + if (output.empty() || output.size() > program.size()) + continue; + if (output[0] != program[0]) + continue; + + auto temp = recurse_part2(registers, val, 10, 1, program); + if (temp.has_value()) + result = BAN::Math::min(result.value_or(BAN::numeric_limits::max()), temp.value()); + } + + return result.value(); +} + +int main(int argc, char** argv) +{ + const char* file_path = "/usr/share/aoc2024/day17_input.txt"; + + if (argc >= 2) + file_path = argv[1]; + + FILE* fp = fopen(file_path, "r"); + if (fp == nullptr) + { + perror("fopen"); + return 1; + } + + printf("part1: %s\n", part1(fp).data()); + + fseek(fp, 0, SEEK_SET); + + printf("part2: %" PRId64 "\n", part2(fp)); + + fclose(fp); +}