2019-12-05 15:53:40 +00:00
|
|
|
package aoc2019
|
|
|
|
|
|
|
|
import (
|
2019-12-15 15:22:24 +00:00
|
|
|
"context"
|
2019-12-13 17:21:19 +00:00
|
|
|
"log"
|
2019-12-05 15:53:40 +00:00
|
|
|
"reflect"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
2019-12-13 17:21:19 +00:00
|
|
|
var intcodeDebugging = false
|
|
|
|
|
2019-12-09 17:48:40 +00:00
|
|
|
type opCodeFlag int64
|
2019-12-05 15:53:40 +00:00
|
|
|
|
|
|
|
const (
|
|
|
|
opCodeFlagPosition opCodeFlag = iota
|
|
|
|
opCodeFlagImmediate
|
2019-12-09 17:48:40 +00:00
|
|
|
opCodeFlagRelative
|
2019-12-05 15:53:40 +00:00
|
|
|
)
|
|
|
|
|
2019-12-09 17:48:40 +00:00
|
|
|
type opCodeType int64
|
2019-12-05 15:53:40 +00:00
|
|
|
|
|
|
|
const (
|
|
|
|
opCodeTypeAddition opCodeType = 1 // Day 02
|
|
|
|
opCodeTypeMultiplication opCodeType = 2 // Day 02
|
|
|
|
opCodeTypeInput opCodeType = 3 // Day 05 P1
|
|
|
|
opCodeTypeOutput opCodeType = 4 // Day 05 P1
|
|
|
|
opCodeTypeJumpIfTrue opCodeType = 5 // Day 05 P2
|
|
|
|
opCodeTypeJumpIfFalse opCodeType = 6 // Day 05 P2
|
|
|
|
opCodeTypeLessThan opCodeType = 7 // Day 05 P2
|
|
|
|
opCodeTypeEquals opCodeType = 8 // Day 05 P2
|
2019-12-09 17:48:40 +00:00
|
|
|
opCodeTypeAdjRelBase opCodeType = 9 // Day 09
|
2019-12-05 15:53:40 +00:00
|
|
|
opCodeTypeExit opCodeType = 99 // Day 02
|
|
|
|
)
|
|
|
|
|
|
|
|
type opCode struct {
|
|
|
|
Type opCodeType
|
|
|
|
flags []opCodeFlag
|
|
|
|
}
|
|
|
|
|
2019-12-09 17:48:40 +00:00
|
|
|
func (o opCode) GetFlag(param int64) opCodeFlag {
|
|
|
|
if param-1 >= int64(len(o.flags)) {
|
2019-12-05 15:53:40 +00:00
|
|
|
return opCodeFlagPosition
|
|
|
|
}
|
|
|
|
return o.flags[param-1]
|
|
|
|
}
|
|
|
|
|
|
|
|
func (o opCode) eq(in opCode) bool {
|
|
|
|
return o.Type == in.Type && reflect.DeepEqual(o.flags, in.flags)
|
|
|
|
}
|
|
|
|
|
2019-12-09 17:48:40 +00:00
|
|
|
func parseOpCode(in int64) opCode {
|
2019-12-05 15:53:40 +00:00
|
|
|
out := opCode{}
|
|
|
|
|
|
|
|
out.Type = opCodeType(in % 100)
|
|
|
|
|
2019-12-09 17:48:40 +00:00
|
|
|
var paramFactor int64 = 100
|
2019-12-05 15:53:40 +00:00
|
|
|
for {
|
|
|
|
if in < paramFactor {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
out.flags = append(out.flags, opCodeFlag((in % (paramFactor * 10) / paramFactor)))
|
|
|
|
paramFactor *= 10
|
|
|
|
}
|
|
|
|
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
2019-12-09 17:48:40 +00:00
|
|
|
func cloneIntcode(in []int64) []int64 {
|
|
|
|
out := make([]int64, len(in))
|
2019-12-07 21:57:10 +00:00
|
|
|
for i, v := range in {
|
|
|
|
out[i] = v
|
|
|
|
}
|
|
|
|
return out
|
|
|
|
}
|
|
|
|
|
2019-12-09 17:48:40 +00:00
|
|
|
func parseIntcode(code string) ([]int64, error) {
|
2019-12-05 15:53:40 +00:00
|
|
|
parts := strings.Split(code, ",")
|
|
|
|
|
2019-12-09 17:48:40 +00:00
|
|
|
var out []int64
|
2019-12-05 15:53:40 +00:00
|
|
|
for _, n := range parts {
|
2019-12-09 17:48:40 +00:00
|
|
|
v, err := strconv.ParseInt(n, 10, 64)
|
2019-12-05 15:53:40 +00:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
out = append(out, v)
|
|
|
|
}
|
|
|
|
|
|
|
|
return out, nil
|
|
|
|
}
|
|
|
|
|
2019-12-15 15:22:24 +00:00
|
|
|
type intcodeParams struct {
|
|
|
|
// Intcode program to execute
|
|
|
|
Code []int64
|
|
|
|
// Context to execute the program in (program might hang on input if context is closed during input directive)
|
|
|
|
Context context.Context
|
|
|
|
// Channel / Callback to query on input directive
|
|
|
|
In interface{}
|
|
|
|
// Channel to use for output directive
|
|
|
|
Out chan int64
|
|
|
|
}
|
|
|
|
|
2019-12-13 17:21:19 +00:00
|
|
|
func executeIntcode(code []int64, in interface{}, out chan int64) ([]int64, error) {
|
2019-12-15 15:22:24 +00:00
|
|
|
return executeIntcodeWithParams(intcodeParams{
|
|
|
|
Code: code,
|
|
|
|
Context: context.Background(),
|
|
|
|
In: in,
|
|
|
|
Out: out,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func executeIntcodeWithParams(params intcodeParams) ([]int64, error) {
|
2019-12-09 17:48:40 +00:00
|
|
|
var (
|
2019-12-15 15:22:24 +00:00
|
|
|
code = params.Code
|
2019-12-13 17:21:19 +00:00
|
|
|
inCB func() (int64, error)
|
2019-12-09 17:48:40 +00:00
|
|
|
pos int64
|
|
|
|
relativeBase int64
|
|
|
|
)
|
2019-12-05 15:53:40 +00:00
|
|
|
|
2019-12-15 15:22:24 +00:00
|
|
|
if params.Out != nil {
|
|
|
|
defer close(params.Out)
|
2019-12-05 15:53:40 +00:00
|
|
|
}
|
|
|
|
|
2019-12-15 15:22:24 +00:00
|
|
|
switch params.In.(type) {
|
2019-12-13 17:21:19 +00:00
|
|
|
case nil:
|
|
|
|
inCB = func() (int64, error) { return 0, errors.New("No input available") }
|
|
|
|
case chan int64:
|
2019-12-15 15:22:24 +00:00
|
|
|
inCB = func() (int64, error) { return <-(params.In.(chan int64)), nil }
|
2019-12-13 17:21:19 +00:00
|
|
|
case func() (int64, error):
|
2019-12-15 15:22:24 +00:00
|
|
|
inCB = params.In.(func() (int64, error))
|
2019-12-13 17:21:19 +00:00
|
|
|
default:
|
|
|
|
return nil, errors.New("Unsupported input type")
|
|
|
|
}
|
|
|
|
|
2019-12-09 17:48:40 +00:00
|
|
|
transformPos := func(param int64, op opCode, write bool) int64 {
|
|
|
|
var addr int64
|
|
|
|
|
2019-12-05 15:53:40 +00:00
|
|
|
switch op.GetFlag(param) {
|
|
|
|
|
|
|
|
case opCodeFlagImmediate:
|
2019-12-09 17:48:40 +00:00
|
|
|
if write {
|
|
|
|
addr = code[pos+param]
|
|
|
|
} else {
|
|
|
|
addr = pos + param
|
|
|
|
}
|
2019-12-05 15:53:40 +00:00
|
|
|
|
|
|
|
case opCodeFlagPosition:
|
2019-12-09 17:48:40 +00:00
|
|
|
addr = code[pos+param]
|
|
|
|
|
|
|
|
case opCodeFlagRelative:
|
|
|
|
addr = code[pos+param] + int64(relativeBase)
|
2019-12-05 15:53:40 +00:00
|
|
|
|
|
|
|
default:
|
|
|
|
panic(errors.Errorf("Unexpected opCodeFlag %d", op.GetFlag(param)))
|
|
|
|
|
|
|
|
}
|
2019-12-09 17:48:40 +00:00
|
|
|
|
|
|
|
return addr
|
|
|
|
}
|
|
|
|
|
|
|
|
getParamValue := func(param int64, op opCode) int64 {
|
|
|
|
var addr = transformPos(param, op, false)
|
|
|
|
|
|
|
|
if addr >= int64(len(code)) {
|
|
|
|
return 0
|
|
|
|
}
|
|
|
|
|
|
|
|
return code[addr]
|
|
|
|
}
|
|
|
|
|
|
|
|
setParamValue := func(param, value int64, op opCode) {
|
|
|
|
var addr = transformPos(param, op, false)
|
|
|
|
|
|
|
|
if addr >= int64(len(code)) {
|
|
|
|
// Write outside memory, increase memory
|
|
|
|
var tmp = make([]int64, addr+1)
|
|
|
|
for i, v := range code {
|
|
|
|
tmp[i] = v
|
|
|
|
}
|
|
|
|
code = tmp
|
|
|
|
}
|
|
|
|
|
|
|
|
code[addr] = value
|
2019-12-05 15:53:40 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
for {
|
2019-12-09 17:48:40 +00:00
|
|
|
if pos >= int64(len(code)) {
|
2019-12-05 15:53:40 +00:00
|
|
|
return nil, errors.Errorf("Code position out of bounds: %d (len=%d)", pos, len(code))
|
|
|
|
}
|
|
|
|
|
2019-12-15 15:22:24 +00:00
|
|
|
if err := params.Context.Err(); err != nil {
|
|
|
|
return nil, errors.Wrap(err, "Context closed")
|
|
|
|
}
|
|
|
|
|
2019-12-05 15:53:40 +00:00
|
|
|
// Position is expected to be an OpCode
|
|
|
|
op := parseOpCode(code[pos])
|
2019-12-13 17:21:19 +00:00
|
|
|
|
|
|
|
if intcodeDebugging {
|
|
|
|
log.Printf("OpCode execution: %#v", op)
|
|
|
|
}
|
|
|
|
|
2019-12-05 15:53:40 +00:00
|
|
|
switch op.Type {
|
|
|
|
|
|
|
|
case opCodeTypeAddition: // p1 + p2 => p3
|
2019-12-09 17:48:40 +00:00
|
|
|
setParamValue(3, getParamValue(1, op)+getParamValue(2, op), op)
|
2019-12-05 15:53:40 +00:00
|
|
|
pos += 4
|
|
|
|
|
|
|
|
case opCodeTypeMultiplication: // p1 * p2 => p3
|
2019-12-09 17:48:40 +00:00
|
|
|
setParamValue(3, getParamValue(1, op)*getParamValue(2, op), op)
|
2019-12-05 15:53:40 +00:00
|
|
|
pos += 4
|
|
|
|
|
|
|
|
case opCodeTypeInput: // in => p1
|
2019-12-13 17:21:19 +00:00
|
|
|
v, err := inCB()
|
|
|
|
if err != nil {
|
|
|
|
return nil, errors.Wrap(err, "Unable to read input")
|
|
|
|
}
|
|
|
|
setParamValue(1, v, op)
|
2019-12-05 15:53:40 +00:00
|
|
|
pos += 2
|
|
|
|
|
|
|
|
case opCodeTypeOutput: // p1 => out
|
2019-12-15 15:22:24 +00:00
|
|
|
params.Out <- getParamValue(1, op)
|
2019-12-05 15:53:40 +00:00
|
|
|
pos += 2
|
|
|
|
|
|
|
|
case opCodeTypeJumpIfTrue: // p1 != 0 => jmp
|
|
|
|
if getParamValue(1, op) != 0 {
|
|
|
|
pos = getParamValue(2, op)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
pos += 3
|
|
|
|
|
|
|
|
case opCodeTypeJumpIfFalse: // p1 == 0 => jmp
|
|
|
|
if getParamValue(1, op) == 0 {
|
|
|
|
pos = getParamValue(2, op)
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
pos += 3
|
|
|
|
|
|
|
|
case opCodeTypeLessThan: // p1 < p2 => p3
|
2019-12-09 17:48:40 +00:00
|
|
|
var res int64
|
2019-12-05 15:53:40 +00:00
|
|
|
if getParamValue(1, op) < getParamValue(2, op) {
|
|
|
|
res = 1
|
|
|
|
}
|
2019-12-09 17:48:40 +00:00
|
|
|
setParamValue(3, res, op)
|
2019-12-05 15:53:40 +00:00
|
|
|
pos += 4
|
|
|
|
|
|
|
|
case opCodeTypeEquals: // p1 == p2 => p3
|
2019-12-09 17:48:40 +00:00
|
|
|
var res int64
|
2019-12-05 15:53:40 +00:00
|
|
|
if getParamValue(1, op) == getParamValue(2, op) {
|
|
|
|
res = 1
|
|
|
|
}
|
2019-12-09 17:48:40 +00:00
|
|
|
setParamValue(3, res, op)
|
2019-12-05 15:53:40 +00:00
|
|
|
pos += 4
|
|
|
|
|
2019-12-09 17:48:40 +00:00
|
|
|
case opCodeTypeAdjRelBase:
|
|
|
|
relativeBase += getParamValue(1, op)
|
|
|
|
pos += 2
|
|
|
|
|
2019-12-05 15:53:40 +00:00
|
|
|
case opCodeTypeExit: // exit
|
|
|
|
return code, nil
|
|
|
|
|
|
|
|
default:
|
|
|
|
return nil, errors.Errorf("Encountered invalid operation %d (parsed %#v)", code[pos], op)
|
|
|
|
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|