Skip to content

Commit

Permalink
Day 17, Part 1
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeladler committed Dec 17, 2024
1 parent be09754 commit 826faa0
Show file tree
Hide file tree
Showing 3 changed files with 151 additions and 0 deletions.
124 changes: 124 additions & 0 deletions src/day17/day17.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
-- Author: Michael Adler
local M = {}

local bit = require("bit")
local bxor = bit.bxor
local pow = math.pow

--- @param input string
M.solve = function(input)
local prog = {}
local prog_count = 0
local reg_A, reg_B, reg_C

local parse = function()
local i = 1
for line in input:gmatch("[^\r\n]+") do
if i == 1 then
local num = line:match("Register A: (%d+)")
reg_A = tonumber(num)
elseif i == 2 then
local num = line:match("Register B: (%d+)")
reg_B = tonumber(num)
elseif i == 3 then
local num = line:match("Register C: (%d+)")
reg_C = tonumber(num)
else
for num in line:gmatch("(%d+)") do
prog[prog_count] = tonumber(num)
prog_count = prog_count + 1
end
end
i = i + 1
end
end
parse()

local function combo_operand(operand)
-- Combo operands 0 through 3 represent literal values 0 through 3.
if operand >= 0 and operand <= 3 then
return operand
end
-- Combo operand 4 represents the value of register A.
if operand == 4 then
return reg_A
end
-- Combo operand 5 represents the value of register B.
if operand == 5 then
return reg_B
end
-- Combo operand 6 represents the value of register C.
if operand == 6 then
return reg_C
end
-- Combo operand 7 is reserved and will not appear in valid programs.
error(string.format("invalid operand: %d", operand))
end

-- run program
local ip = 0
local output = {}
while ip < prog_count do
local opcode, operand = prog[ip], prog[ip + 1]
if opcode == 0 then
-- The adv instruction (opcode 0) performs division. The numerator
-- is the value in the A register. The denominator is found by
-- raising 2 to the power of the instruction's combo operand.
-- The result of the division operation is truncated to an integer
-- and then written to the A register.
local numerator, denominator = reg_A, pow(2, combo_operand(operand))
reg_A = math.floor(numerator / denominator)
elseif opcode == 1 then
-- The bxl instruction (opcode 1) calculates the bitwise XOR of register B and
-- the instruction's literal operand, then stores the result in register B.
reg_B = bxor(reg_B, operand)
elseif opcode == 2 then
-- The bst instruction (opcode 2) calculates the value of its combo
-- operand modulo 8 (thereby keeping only its lowest 3 bits), then
-- writes that value to the B register.
reg_B = combo_operand(operand) % 8
elseif opcode == 3 then
-- The jnz instruction (opcode 3) does nothing if the A register is
-- 0. However, if the A register is not zero, it jumps by setting
-- the instruction pointer to the value of its literal operand; if
-- this instruction jumps, the instruction pointer is not increased
-- by 2 after this instruction.
if reg_A ~= 0 then
ip = operand
goto continue
end
elseif opcode == 4 then
-- The bxc instruction (opcode 4) calculates the bitwise XOR of
-- register B and register C, then stores the result in register B.
-- (For legacy reasons, this instruction reads an operand but
-- ignores it.)
reg_B = bxor(reg_B, reg_C)
elseif opcode == 5 then
-- The out instruction (opcode 5) calculates the value of its combo
-- operand modulo 8, then outputs that value. (If a program outputs
-- multiple values, they are separated by commas.)
local out = combo_operand(operand) % 8
table.insert(output, out)
elseif opcode == 6 then
-- The bdv instruction (opcode 6) works exactly like the adv
-- instruction except that the result is stored in the B register.
-- (The numerator is still read from the A register.)
local numerator, denominator = reg_A, pow(2, combo_operand(operand))
reg_B = math.floor(numerator / denominator)
elseif opcode == 7 then
-- The cdv instruction (opcode 7) works exactly like the adv
-- instruction except that the result is stored in the C register.
-- (The numerator is still read from the A register.)
local numerator, denominator = reg_A, pow(2, combo_operand(operand))
reg_C = math.floor(numerator / denominator)
end

ip = ip + 2
::continue::
end
local part1 = table.concat(output, ",")

return part1, 0
end

return M
19 changes: 19 additions & 0 deletions src/day17/day17_spec.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
local day17 = require("day17")

describe("day17", function()
describe("example 1", function()
local input = [[Register A: 729
Register B: 0
Register C: 0
Program: 0,1,5,4,3,0
]]
local part1, part2 = day17.solve(input)
it("part1", function()
assert.are.equal("4,6,3,5,6,3,5,2,1,0", part1)
end)
it("part2", function()
assert.are.equal(0, part2)
end)
end)
end)
8 changes: 8 additions & 0 deletions src/day17/main.lua
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/usr/bin/env luajit
local day17 = require("day17")
local fname = arg[1] or "input.txt"
local f = assert(io.open(fname, "r"), fname .. " missing")
local input = f:read("*a")
f:close()
local part1, part2 = day17.solve(input)
print(string.format("Part 1: %s\nPart 2: %d", part1, part2))

0 comments on commit 826faa0

Please sign in to comment.