From acef5fdc12def42a74f4eec4f97b8d9dc2312497 Mon Sep 17 00:00:00 2001 From: Parnic Date: Mon, 13 Jun 2022 15:29:18 -0500 Subject: [PATCH] Day 5 solution This required an overhaul of the intcode machine to actually be its own type that could operate on its own memory and stuff. So I had to touch day 2 to make it adhere to the new API. Feeling good about this foundation now. Until I get gobsmacked at some point later, which I expect to happen. --- days/02.go | 28 +++--- days/05.go | 188 +++++++++++++++++++++++++++++++++++++++ inputs/05p.txt | 1 + main.go | 1 + utilities/intcode.go | 206 ++++++++++++++++++++++++++++++++++++++----- 5 files changed, 390 insertions(+), 34 deletions(-) create mode 100644 days/05.go create mode 100644 inputs/05p.txt diff --git a/days/02.go b/days/02.go index 6742aec..894570f 100644 --- a/days/02.go +++ b/days/02.go @@ -18,19 +18,20 @@ func (d Day02) Num() int { return 2 } -func (d *Day02) getProgramWithParams(param1, param2 int64) utilities.IntcodeProgram { - program := make(utilities.IntcodeProgram, len(d.program)) - copy(program, d.program) - program[1] = param1 - program[2] = param2 - return program +func (d *Day02) setParams(param1, param2 int64) { + d.program.Reset() + d.program.SetMemory(1, param1) + d.program.SetMemory(2, param2) } func (d *Day02) Part1() string { - program := d.getProgramWithParams(12, 2) - program.Run() + d.setParams(12, 2) + d.program.Run() - return fmt.Sprintf("Position 0 = %s%d%s", utilities.TextBold, program[0], utilities.TextReset) + if d.program.GetMemory(0) != 4138658 { + panic("") + } + return fmt.Sprintf("Position 0 = %s%d%s", utilities.TextBold, d.program.GetMemory(0), utilities.TextReset) } func (d *Day02) Part2() string { @@ -41,10 +42,10 @@ func (d *Day02) Part2() string { found := false for noun = 0; noun <= 99; noun++ { for verb = 0; verb <= 99; verb++ { - program := d.getProgramWithParams(noun, verb) - program.Run() + d.setParams(noun, verb) + d.program.Run() - if program[0] == sentinel { + if d.program.GetMemory(0) == sentinel { found = true break } @@ -58,6 +59,9 @@ func (d *Day02) Part2() string { if !found { panic("!found") } + if noun != 72 || verb != 64 { + panic("") + } return fmt.Sprintf("%d created by noun=%d, verb=%d. 100 * noun + verb = %s%d%s", sentinel, diff --git a/days/05.go b/days/05.go new file mode 100644 index 0000000..1d4ea56 --- /dev/null +++ b/days/05.go @@ -0,0 +1,188 @@ +package days + +import ( + "fmt" + + "parnic.com/aoc2019/utilities" +) + +type Day05 struct { + program utilities.IntcodeProgram +} + +func (d *Day05) Parse() { + d.program = utilities.LoadIntcodeProgram("05p") + d.test() +} + +func (d Day05) Num() int { + return 5 +} + +func (d Day05) test() { + // Using position mode, consider whether the input is equal to 8; output 1 (if it is) or 0 (if it is not). + program := utilities.ParseIntcodeProgram("3,9,8,9,10,9,4,9,99,-1,8") + program.RunIn(func(int) int64 { + return 0 + }, func(val int64, state utilities.IntcodeProgramState) { + if val != 0 { + panic("") + } + }) + program.Reset() + program.RunIn(func(int) int64 { + return 8 + }, func(val int64, state utilities.IntcodeProgramState) { + if val != 1 { + panic("") + } + }) + + // Using position mode, consider whether the input is less than 8; output 1 (if it is) or 0 (if it is not). + program = utilities.ParseIntcodeProgram("3,9,7,9,10,9,4,9,99,-1,8") + program.RunIn(func(int) int64 { + return 0 + }, func(val int64, state utilities.IntcodeProgramState) { + if val != 1 { + panic("") + } + }) + program.Reset() + program.RunIn(func(int) int64 { + return 8 + }, func(val int64, state utilities.IntcodeProgramState) { + if val != 0 { + panic("") + } + }) + + // Using immediate mode, consider whether the input is equal to 8; output 1 (if it is) or 0 (if it is not). + program = utilities.ParseIntcodeProgram("3,3,1108,-1,8,3,4,3,99") + program.RunIn(func(int) int64 { + return 0 + }, func(val int64, state utilities.IntcodeProgramState) { + if val != 0 { + panic("") + } + }) + program.Reset() + program.RunIn(func(int) int64 { + return 8 + }, func(val int64, state utilities.IntcodeProgramState) { + if val != 1 { + panic("") + } + }) + + // Using immediate mode, consider whether the input is less than 8; output 1 (if it is) or 0 (if it is not). + program = utilities.ParseIntcodeProgram("3,3,1107,-1,8,3,4,3,99") + program.RunIn(func(int) int64 { + return 0 + }, func(val int64, state utilities.IntcodeProgramState) { + if val != 1 { + panic("") + } + }) + program.Reset() + program.RunIn(func(int) int64 { + return 8 + }, func(val int64, state utilities.IntcodeProgramState) { + if val != 0 { + panic("") + } + }) + + // jump tests that take an input, then output 0 if the input was zero or 1 if the input was non-zero + // position mode + program = utilities.ParseIntcodeProgram("3,12,6,12,15,1,13,14,13,4,13,99,-1,0,1,9") + program.RunIn(func(int) int64 { + return 0 + }, func(val int64, state utilities.IntcodeProgramState) { + if val != 0 { + panic("") + } + }) + program.Reset() + program.RunIn(func(int) int64 { + return 8 + }, func(val int64, state utilities.IntcodeProgramState) { + if val != 1 { + panic("") + } + }) + + // jump tests that take an input, then output 0 if the input was zero or 1 if the input was non-zero + // immediate mode + program = utilities.ParseIntcodeProgram("3,3,1105,-1,9,1101,0,0,12,4,12,99,1") + program.RunIn(func(int) int64 { + return 0 + }, func(val int64, state utilities.IntcodeProgramState) { + if val != 0 { + panic("") + } + }) + program.Reset() + program.RunIn(func(int) int64 { + return 8 + }, func(val int64, state utilities.IntcodeProgramState) { + if val != 1 { + panic("") + } + }) + + // uses an input instruction to ask for a single number. The program will then output 999 if the input value is below 8, output 1000 if the input value is equal to 8, or output 1001 if the input value is greater than 8. + program = utilities.ParseIntcodeProgram("3,21,1008,21,8,20,1005,20,22,107,8,21,20,1006,20,31,1106,0,36,98,0,0,1002,21,125,20,4,20,1105,1,46,104,999,1105,1,46,1101,1000,1,20,4,20,1105,1,46,98,99") + program.RunIn(func(int) int64 { + return 0 + }, func(val int64, state utilities.IntcodeProgramState) { + if val != 999 { + panic("") + } + }) + program.Reset() + program.RunIn(func(int) int64 { + return 8 + }, func(val int64, state utilities.IntcodeProgramState) { + if val != 1000 { + panic("") + } + }) + program.Reset() + program.RunIn(func(int) int64 { + return 9 + }, func(val int64, state utilities.IntcodeProgramState) { + if val != 1001 { + panic("") + } + }) +} + +func (d *Day05) Part1() string { + diagCode := int64(-1) + d.program.RunIn(func(int) int64 { + return 1 + }, func(val int64, state utilities.IntcodeProgramState) { + if state.IsHalting() { + diagCode = val + } else if val != 0 { + panic("test failed") + } + }) + + return fmt.Sprintf("Diagnostic code: %s%d%s", utilities.TextBold, diagCode, utilities.TextReset) +} + +func (d *Day05) Part2() string { + d.program.Reset() + diagCode := int64(-1) + d.program.RunIn(func(int) int64 { + return 5 + }, func(val int64, state utilities.IntcodeProgramState) { + if !state.IsHalting() { + panic("unexpected output received") + } + diagCode = val + }) + + return fmt.Sprintf("Diagnostic code: %s%d%s", utilities.TextBold, diagCode, utilities.TextReset) +} diff --git a/inputs/05p.txt b/inputs/05p.txt new file mode 100644 index 0000000..8024ae7 --- /dev/null +++ b/inputs/05p.txt @@ -0,0 +1 @@ +3,225,1,225,6,6,1100,1,238,225,104,0,1102,78,40,225,1102,52,43,224,1001,224,-2236,224,4,224,102,8,223,223,101,4,224,224,1,224,223,223,1,191,61,224,1001,224,-131,224,4,224,102,8,223,223,101,4,224,224,1,223,224,223,1101,86,74,225,1102,14,76,225,1101,73,83,224,101,-156,224,224,4,224,102,8,223,223,101,6,224,224,1,224,223,223,1102,43,82,225,2,196,13,224,101,-6162,224,224,4,224,102,8,223,223,101,5,224,224,1,223,224,223,1001,161,51,224,101,-70,224,224,4,224,102,8,223,223,1001,224,1,224,1,224,223,223,102,52,187,224,1001,224,-832,224,4,224,102,8,223,223,101,1,224,224,1,224,223,223,1102,19,79,225,101,65,92,224,1001,224,-147,224,4,224,1002,223,8,223,101,4,224,224,1,223,224,223,1102,16,90,225,1102,45,44,225,1102,92,79,225,1002,65,34,224,101,-476,224,224,4,224,102,8,223,223,1001,224,5,224,1,224,223,223,4,223,99,0,0,0,677,0,0,0,0,0,0,0,0,0,0,0,1105,0,99999,1105,227,247,1105,1,99999,1005,227,99999,1005,0,256,1105,1,99999,1106,227,99999,1106,0,265,1105,1,99999,1006,0,99999,1006,227,274,1105,1,99999,1105,1,280,1105,1,99999,1,225,225,225,1101,294,0,0,105,1,0,1105,1,99999,1106,0,300,1105,1,99999,1,225,225,225,1101,314,0,0,106,0,0,1105,1,99999,107,226,226,224,1002,223,2,223,1005,224,329,1001,223,1,223,1007,226,226,224,102,2,223,223,1005,224,344,101,1,223,223,1008,226,226,224,102,2,223,223,1005,224,359,1001,223,1,223,8,226,677,224,102,2,223,223,1006,224,374,101,1,223,223,1107,226,677,224,1002,223,2,223,1006,224,389,101,1,223,223,1108,226,677,224,102,2,223,223,1005,224,404,101,1,223,223,107,677,677,224,102,2,223,223,1006,224,419,1001,223,1,223,7,677,226,224,102,2,223,223,1005,224,434,101,1,223,223,1007,677,677,224,102,2,223,223,1005,224,449,1001,223,1,223,108,226,677,224,102,2,223,223,1005,224,464,1001,223,1,223,108,226,226,224,102,2,223,223,1006,224,479,101,1,223,223,107,226,677,224,102,2,223,223,1006,224,494,1001,223,1,223,7,226,226,224,1002,223,2,223,1006,224,509,101,1,223,223,1108,677,226,224,102,2,223,223,1005,224,524,101,1,223,223,1107,677,226,224,102,2,223,223,1005,224,539,101,1,223,223,1008,677,226,224,102,2,223,223,1005,224,554,101,1,223,223,1008,677,677,224,1002,223,2,223,1006,224,569,101,1,223,223,1107,677,677,224,102,2,223,223,1006,224,584,1001,223,1,223,1108,226,226,224,1002,223,2,223,1006,224,599,101,1,223,223,7,226,677,224,102,2,223,223,1006,224,614,101,1,223,223,108,677,677,224,1002,223,2,223,1006,224,629,101,1,223,223,1007,677,226,224,102,2,223,223,1006,224,644,101,1,223,223,8,677,677,224,1002,223,2,223,1006,224,659,101,1,223,223,8,677,226,224,102,2,223,223,1005,224,674,101,1,223,223,4,223,99,226 \ No newline at end of file diff --git a/main.go b/main.go index f136aa9..1ddd03a 100644 --- a/main.go +++ b/main.go @@ -35,6 +35,7 @@ var dayMap = []day{ &days.Day02{}, &days.Day03{}, &days.Day04{}, + &days.Day05{}, } func main() { diff --git a/utilities/intcode.go b/utilities/intcode.go index 8922fed..f13196d 100644 --- a/utilities/intcode.go +++ b/utilities/intcode.go @@ -1,52 +1,214 @@ package utilities import ( + "fmt" "strconv" "strings" ) const ( - opAdd = 1 - opMul = 2 - opEnd = 99 + opAdd = 1 + opMultiply = 2 + opInput = 3 + opOutput = 4 + opJumpIfTrue = 5 + opJumpIfFalse = 6 + opLessThan = 7 + opEquals = 8 + opHalt = 99 + + modePosition = 0 + modeImmediate = 1 ) -type IntcodeProgram []int64 +type IntcodeProgram struct { + memory []int64 + program []int64 +} + +type IntcodeProgramState struct { + program *IntcodeProgram + CurrentInstruction int + NextInstruction int +} + +func (s IntcodeProgramState) IsHalting() bool { + return s.program.GetMemory(s.NextInstruction) == opHalt +} + +type ProvideInputFunc func(inputStep int) int64 +type ReceiveOutputFunc func(val int64, state IntcodeProgramState) func ParseIntcodeProgram(programStr string) IntcodeProgram { nums := strings.Split(programStr, ",") - program := make(IntcodeProgram, len(nums)) + program := IntcodeProgram{ + program: make([]int64, len(nums)), + } for idx, num := range nums { iNum, err := strconv.ParseInt(num, 10, 64) if err != nil { panic(err) } - program[idx] = iNum + program.program[idx] = iNum } return program } -func (program IntcodeProgram) Run() { - for instructionPointer := 0; instructionPointer < len(program); { - opcode := program[instructionPointer] +func (p *IntcodeProgram) makeState(instructionPointer int) IntcodeProgramState { + return IntcodeProgramState{ + program: p, + CurrentInstruction: instructionPointer, + NextInstruction: instructionPointer + 1, + } +} + +func (p *IntcodeProgram) Copy() IntcodeProgram { + ret := IntcodeProgram{ + program: make([]int64, len(p.program)), + } + copy(ret.program, p.program) + return ret +} + +func (p *IntcodeProgram) init() { + if p.memory == nil { + p.memory = make([]int64, len(p.program)) + p.Reset() + } +} + +func (p *IntcodeProgram) getParamValue(param, mode int) int64 { + switch mode { + case modePosition: + return p.memory[param] + + case modeImmediate: + return int64(param) + } + + panic("unhandled param mode") +} + +func (p *IntcodeProgram) GetMemory(idx int) int64 { + return p.memory[idx] +} + +func (p *IntcodeProgram) SetMemory(idx int, val int64) { + p.init() + p.memory[idx] = val +} + +func (p *IntcodeProgram) Reset() { + p.init() + copy(p.memory, p.program) +} + +func (p *IntcodeProgram) Run() { + p.RunIn(func(int) int64 { return 0 }, func(int64, IntcodeProgramState) {}) +} + +func (p *IntcodeProgram) RunIn(inputFunc ProvideInputFunc, outputFunc ReceiveOutputFunc) { + p.init() + + inputsRequested := 0 + for instructionPointer := 0; instructionPointer < len(p.program); { + instruction := p.memory[instructionPointer] + instructionPointer++ + + paramModes := [3]int{ + modePosition, + modePosition, + modePosition, + } + modes := instruction / 100 + for i := 0; modes > 0; i++ { + paramModes[i] = int(modes % 10) + modes = modes / 10 + } + + opcode := instruction % 100 switch opcode { case opAdd: - param1 := program[instructionPointer+1] - param2 := program[instructionPointer+2] - param3 := program[instructionPointer+3] - program[param3] = program[param1] + program[param2] + param1 := p.memory[instructionPointer] + param2 := p.memory[instructionPointer+1] + param3 := p.memory[instructionPointer+2] + p.memory[param3] = p.getParamValue(int(param1), paramModes[0]) + p.getParamValue(int(param2), paramModes[1]) - instructionPointer += 4 - case opMul: - param1 := program[instructionPointer+1] - param2 := program[instructionPointer+2] - param3 := program[instructionPointer+3] - program[param3] = program[param1] * program[param2] + instructionPointer += 3 - instructionPointer += 4 - case opEnd: - instructionPointer = len(program) + case opMultiply: + param1 := p.memory[instructionPointer] + param2 := p.memory[instructionPointer+1] + param3 := p.memory[instructionPointer+2] + p.memory[param3] = p.getParamValue(int(param1), paramModes[0]) * p.getParamValue(int(param2), paramModes[1]) + + instructionPointer += 3 + + case opInput: + inputsRequested++ + param1 := p.memory[instructionPointer] + p.memory[param1] = inputFunc(inputsRequested) + + instructionPointer += 1 + + case opOutput: + param1 := p.memory[instructionPointer] + outputFunc(p.getParamValue(int(param1), paramModes[0]), p.makeState(instructionPointer)) + + instructionPointer += 1 + + case opJumpIfTrue: + param1 := p.memory[instructionPointer] + param2 := p.memory[instructionPointer+1] + + if p.getParamValue(int(param1), paramModes[0]) != 0 { + instructionPointer = int(p.getParamValue(int(param2), paramModes[1])) + } else { + instructionPointer += 2 + } + + case opJumpIfFalse: + param1 := p.memory[instructionPointer] + param2 := p.memory[instructionPointer+1] + + if p.getParamValue(int(param1), paramModes[0]) == 0 { + instructionPointer = int(p.getParamValue(int(param2), paramModes[1])) + } else { + instructionPointer += 2 + } + + case opLessThan: + param1 := p.memory[instructionPointer] + param2 := p.memory[instructionPointer+1] + param3 := p.memory[instructionPointer+2] + + if p.getParamValue(int(param1), paramModes[0]) < p.getParamValue(int(param2), paramModes[1]) { + p.memory[param3] = 1 + } else { + p.memory[param3] = 0 + } + + instructionPointer += 3 + + case opEquals: + param1 := p.memory[instructionPointer] + param2 := p.memory[instructionPointer+1] + param3 := p.memory[instructionPointer+2] + + if p.getParamValue(int(param1), paramModes[0]) == p.getParamValue(int(param2), paramModes[1]) { + p.memory[param3] = 1 + } else { + p.memory[param3] = 0 + } + + instructionPointer += 3 + + case opHalt: + instructionPointer = len(p.program) + + default: + panic(fmt.Sprintf("exception executing program - unhandled opcode %d", opcode)) } } }