diff --git a/README.md b/README.md index 0dd826d..da6715b 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ I'm also adding notes that may be useful if you're learning Ruby. Notes for solving: * [2015, complete](year_2015.md) -* [2016, up to day 22](year_2016.md) +* [2016, up to day 23](year_2016.md) * [2023, up to day 04](year_2023.md) # How to use diff --git a/spec/year_2016/day_23_input b/spec/year_2016/day_23_input new file mode 100644 index 0000000..c6257db --- /dev/null +++ b/spec/year_2016/day_23_input @@ -0,0 +1,26 @@ +cpy a b +dec b +cpy a d +cpy 0 a +cpy b c +inc a +dec c +jnz c -2 +dec d +jnz d -5 +dec b +cpy b c +cpy c d +dec d +inc c +jnz d -2 +tgl c +cpy -16 c +jnz 1 c +cpy 96 c +jnz 91 d +inc a +inc d +jnz d -2 +inc c +jnz c -5 \ No newline at end of file diff --git a/spec/year_2016/day_23_spec.rb b/spec/year_2016/day_23_spec.rb new file mode 100644 index 0000000..43837ae --- /dev/null +++ b/spec/year_2016/day_23_spec.rb @@ -0,0 +1,37 @@ +require 'year_2016/day_23' + +describe Year2016::Day23 do + context 'when Part 1' do + subject(:sample_one) do + sample_data = <<~HEREDOC + cpy 2 a + tgl a + tgl a + tgl a + cpy 1 a + dec a + dec a + HEREDOC + described_class.new(sample_data).program + end + + it 'gives a final result' do + sample_one.execute + expect(sample_one.registers['a']).to eq(3) + end + end + + context 'when Results' do + subject(:input_data) do + File.read('spec/year_2016/day_23_input') + end + + it 'correctly answers part 1' do + expect(described_class.new(input_data).part_one).to eq(13_776) + end + + it 'correctly answers part 2', skip: 'Test is too slow for CI' do + expect(described_class.new(input_data).part_two).to eq(479_010_336) + end + end +end diff --git a/year_2016.md b/year_2016.md index 176ce60..547eb95 100644 --- a/year_2016.md +++ b/year_2016.md @@ -320,3 +320,17 @@ Year2016::Day22 There is something about re-using [Breadth-first search](https://en.wikipedia.org/wiki/Breadth-first_search) all the time that is just very annoying. In that case, the algorithm is about getting our payload to the empty spot, and then moving the data around it until we reach the goal. + + +## Day 23: Safe Cracking + +``` +Year2016::Day21 + when Part 1 + gives a final result + when Results + correctly answers part 1 + correctly answers part 2 (PENDING: Test is too slow for CI) +``` + +Nothing to say, I'm just a sucker for virtual machines. That said, the test almost takes as long as an episode of Dragon Ball. diff --git a/year_2016/day_23.rb b/year_2016/day_23.rb new file mode 100644 index 0000000..f68ce0d --- /dev/null +++ b/year_2016/day_23.rb @@ -0,0 +1,93 @@ +class Year2016 + class Day23 + attr_accessor :program + + class Program + attr_accessor :instructions, :pc, :registers + + def initialize(input_instructions) + @instructions = input_instructions.map do |instruction| + [instruction, compile(instruction)] + end + end + + def inc(register) + @pc += 1 + @registers[register] += 1 + end + + def dec(register) + @pc += 1 + @registers[register] -= 1 + end + + def cpy(from, to) + @pc += 1 + return unless @registers[to] + + @registers[to] = @registers[from] || from.to_i + end + + def jnz(value, jmp) + return @pc += 1 if (@registers[value] || value.to_i).zero? + + @pc += @registers[jmp] || jmp.to_i + end + + # rubocop:disable Metrics/MethodLength + def tgl(register) + jump = @registers[register] || jump.to_i + code, tgl_instruction = @instructions[@pc + jump] + return @pc += 1 unless tgl_instruction + + case tgl_instruction.length + when 2 + tgl_instruction[0] = tgl_instruction[0] == :inc ? :dec : :inc + when 3 + tgl_instruction[0] = tgl_instruction[0] == :jnz ? :cpy : :jnz + end + @instructions[@pc + jump] = [code, tgl_instruction] + + @pc += 1 + end + + def compile(instruction) + case instruction + when /^cpy (-?\d+|[abcd]) ([abcd])$/ + [:cpy, $1, $2] + when /^inc ([abcd])$/ + [:inc, $1] + when /^dec ([abcd])$/ + [:dec, $1] + when /^jnz (-?\d+|[abcd]) (-?\d+|[abcd])$/ + [:jnz, $1, $2] + when /^tgl ([abcd])$/ + [:tgl, $1] + else + raise "Invalid code: #{instruction}" + end + end + # rubocop:enable Metrics/MethodLength + + def execute(init_registers = { 'a' => 0, 'b' => 0, 'c' => 0, 'd' => 0 }) + @pc = 0 + @registers = init_registers + send(*instructions[@pc].last) while instructions[@pc] + end + end + + def initialize(input_data) + @program = Program.new(input_data.chomp.split("\n")) + end + + def part_one + @program.execute('a' => 7, 'b' => 0, 'c' => 0, 'd' => 0) + @program.registers['a'] + end + + def part_two + @program.execute('a' => 12, 'b' => 0, 'c' => 0, 'd' => 0) + @program.registers['a'] + end + end +end