This day showed me that when the input instruction was introduced and said "write addresses will never be in immediate mode", that didn't mean "so don't bother handling modes for input addresses", it meant "handle the mode, but assert if it's immediate mode". It was super helpful that this program contained a bootstrap sequence to validate each instruction. Memory expansion came with a few caveats: obviously reads and writes needed to handle expanding the memory space, but a Reset also can no longer get away with simply copying the program into memory again because we need to ensure that any additional memory is cut off (or at least zeroed), so the quickest way to handle that in Go is to simply allocate a new buffer; I'd rather manipulate the existing buffer, but I'm having a hard time finding the best way to do that. And finally, make sure you reset your relativeBase when resetting the program...that one was ugly to track down.
251 lines
5.9 KiB
Go
251 lines
5.9 KiB
Go
package utilities
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
const (
|
|
opAdd = 1
|
|
opMultiply = 2
|
|
opInput = 3
|
|
opOutput = 4
|
|
opJumpIfTrue = 5
|
|
opJumpIfFalse = 6
|
|
opLessThan = 7
|
|
opEquals = 8
|
|
opRelativeBase = 9
|
|
opHalt = 99
|
|
|
|
modePosition = 0
|
|
modeImmediate = 1
|
|
modeRelative = 2
|
|
)
|
|
|
|
type IntcodeProgram struct {
|
|
memory []int64
|
|
program []int64
|
|
relativeBase int
|
|
}
|
|
|
|
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 := IntcodeProgram{
|
|
program: make([]int64, len(nums)),
|
|
}
|
|
for idx, num := range nums {
|
|
iNum, err := strconv.ParseInt(num, 10, 64)
|
|
if err != nil {
|
|
panic(err)
|
|
}
|
|
program.program[idx] = iNum
|
|
}
|
|
|
|
return program
|
|
}
|
|
|
|
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))
|
|
copy(p.memory, p.program)
|
|
}
|
|
}
|
|
|
|
func (p *IntcodeProgram) getParamValue(param, mode int) int64 {
|
|
switch mode {
|
|
case modePosition:
|
|
return p.GetMemory(param)
|
|
|
|
case modeImmediate:
|
|
return int64(param)
|
|
|
|
case modeRelative:
|
|
return p.GetMemory(param + p.relativeBase)
|
|
}
|
|
|
|
panic("unhandled param mode")
|
|
}
|
|
|
|
func (p *IntcodeProgram) GetMemory(idx int) int64 {
|
|
p.ensureMemoryCapacity(idx)
|
|
return p.memory[idx]
|
|
}
|
|
|
|
func (p *IntcodeProgram) SetMemory(idx int, val int64) {
|
|
p.init()
|
|
p.ensureMemoryCapacity(idx)
|
|
p.memory[idx] = val
|
|
}
|
|
|
|
func (p *IntcodeProgram) setMemory(idx int, val int64, mode int) {
|
|
if mode == modeImmediate {
|
|
panic("exception executing program - write parameter must never be in immediate mode")
|
|
}
|
|
if mode == modeRelative {
|
|
idx = idx + p.relativeBase
|
|
}
|
|
|
|
p.SetMemory(idx, val)
|
|
}
|
|
|
|
func (p *IntcodeProgram) ensureMemoryCapacity(address int) {
|
|
if len(p.memory) > address {
|
|
return
|
|
}
|
|
|
|
p.memory = append(p.memory, make([]int64, address+1-len(p.memory))...)
|
|
}
|
|
|
|
func (p *IntcodeProgram) Reset() {
|
|
p.memory = nil
|
|
p.init()
|
|
copy(p.memory, p.program)
|
|
p.relativeBase = 0
|
|
}
|
|
|
|
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.GetMemory(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 := p.GetMemory(instructionPointer)
|
|
param2 := p.GetMemory(instructionPointer + 1)
|
|
param3 := p.GetMemory(instructionPointer + 2)
|
|
p.setMemory(int(param3), p.getParamValue(int(param1), paramModes[0])+p.getParamValue(int(param2), paramModes[1]), paramModes[2])
|
|
|
|
instructionPointer += 3
|
|
|
|
case opMultiply:
|
|
param1 := p.GetMemory(instructionPointer)
|
|
param2 := p.GetMemory(instructionPointer + 1)
|
|
param3 := p.GetMemory(instructionPointer + 2)
|
|
p.setMemory(int(param3), p.getParamValue(int(param1), paramModes[0])*p.getParamValue(int(param2), paramModes[1]), paramModes[2])
|
|
|
|
instructionPointer += 3
|
|
|
|
case opInput:
|
|
inputsRequested++
|
|
param1 := p.GetMemory(instructionPointer)
|
|
p.setMemory(int(param1), inputFunc(inputsRequested), paramModes[0])
|
|
|
|
instructionPointer += 1
|
|
|
|
case opOutput:
|
|
param1 := p.GetMemory(instructionPointer)
|
|
outputFunc(p.getParamValue(int(param1), paramModes[0]), p.makeState(instructionPointer))
|
|
|
|
instructionPointer += 1
|
|
|
|
case opJumpIfTrue:
|
|
param1 := p.GetMemory(instructionPointer)
|
|
param2 := p.GetMemory(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.GetMemory(instructionPointer)
|
|
param2 := p.GetMemory(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.GetMemory(instructionPointer)
|
|
param2 := p.GetMemory(instructionPointer + 1)
|
|
param3 := p.GetMemory(instructionPointer + 2)
|
|
|
|
if p.getParamValue(int(param1), paramModes[0]) < p.getParamValue(int(param2), paramModes[1]) {
|
|
p.setMemory(int(param3), 1, paramModes[2])
|
|
} else {
|
|
p.setMemory(int(param3), 0, paramModes[2])
|
|
}
|
|
|
|
instructionPointer += 3
|
|
|
|
case opEquals:
|
|
param1 := p.GetMemory(instructionPointer)
|
|
param2 := p.GetMemory(instructionPointer + 1)
|
|
param3 := p.GetMemory(instructionPointer + 2)
|
|
|
|
if p.getParamValue(int(param1), paramModes[0]) == p.getParamValue(int(param2), paramModes[1]) {
|
|
p.setMemory(int(param3), 1, paramModes[2])
|
|
} else {
|
|
p.setMemory(int(param3), 0, paramModes[2])
|
|
}
|
|
|
|
instructionPointer += 3
|
|
|
|
case opRelativeBase:
|
|
param1 := p.GetMemory(instructionPointer)
|
|
|
|
p.relativeBase += int(p.getParamValue(int(param1), paramModes[0]))
|
|
|
|
instructionPointer += 1
|
|
|
|
case opHalt:
|
|
instructionPointer = len(p.program)
|
|
|
|
default:
|
|
panic(fmt.Sprintf("exception executing program - unhandled opcode %d", opcode))
|
|
}
|
|
}
|
|
}
|