package main
import (
"bufio"
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"strconv"
"strings"
)
type OpType int
const (
OpAdd OpType = iota + 1
OpMul
OpInput
OpOutput
OpJumpTrue
OpJumpFalse
OpLessThan
OpEquals
OpRelativeBase
OpEnd = 99
)
type ModeType int
const (
ModePosition ModeType = iota
ModeImmediate
ModeRelative
)
type Computer struct {
mem []int
pc int
rb int
Halted bool
}
func NewComputer(initial []int) *Computer {
com := Computer{}
com.mem = make([]int, 65535)
copy(com.mem, initial)
return &com
}
func (com *Computer) getArgPos(pos int, mode ModeType) (int, error) {
if pos < 0 || pos >= len(com.mem) {
return -1, fmt.Errorf("invalid index, out of range: %d", pos)
}
switch mode {
case ModePosition:
if com.mem[pos] < 0 || com.mem[pos] >= len(com.mem) {
return -1, fmt.Errorf("invalid position index, out of range: %d", com.mem[pos])
}
return com.mem[pos], nil
case ModeRelative:
rel := com.mem[pos] + com.rb
if rel < 0 || rel >= len(com.mem) {
return -1, fmt.Errorf("invalid relative index, out of range: %d", rel)
}
return rel, nil
}
return pos, nil
}
func (com *Computer) Iterate(input func() int, yieldOnInput bool, debug bool) (int, bool, error) {
log := ioutil.Discard
if debug {
log = os.Stdout
}
for com.pc < len(com.mem) {
op := OpType(com.mem[com.pc])
if op == OpEnd {
fmt.Fprintf(log, "Halt!\n")
com.Halted = true
return 0, true, nil
}
aMode := ModePosition
bMode := ModePosition
cMode := ModePosition
if op > 99 {
aMode = ModeType((op / 100) % 10)
bMode = ModeType((op / 1000) % 10)
cMode = ModeType((op / 10000) % 10)
op %= 100
}
switch op {
case OpAdd:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := com.getArgPos(com.pc+2, bMode)
if err != nil {
return 0, false, err
}
o, err := com.getArgPos(com.pc+3, cMode)
if err != nil {
return 0, false, err
}
com.mem[o] = com.mem[a] + com.mem[b]
fmt.Fprintf(log, "Setting %d @%d\n", com.mem[o], o)
com.pc += 4
case OpMul:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := com.getArgPos(com.pc+2, bMode)
if err != nil {
return 0, false, err
}
o, err := com.getArgPos(com.pc+3, cMode)
if err != nil {
return 0, false, err
}
com.mem[o] = com.mem[a] * com.mem[b]
fmt.Fprintf(log, "Setting %d @%d\n", com.mem[o], o)
com.pc += 4
case OpInput:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
com.mem[a] = input()
fmt.Fprintf(log, "Inputting: %d @%d\n", com.mem[a], a)
com.pc += 2
if yieldOnInput {
return 0, false, nil
}
case OpOutput:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
fmt.Fprintf(log, "Outputting: %d @%d\n", com.mem[a], a)
com.pc += 2
return com.mem[a], false, nil
case OpJumpTrue:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := com.getArgPos(com.pc+2, bMode)
if err != nil {
return 0, false, err
}
if com.mem[a] != 0 {
com.pc = com.mem[b]
fmt.Fprintf(log, "Jump to %d\n", com.pc)
} else {
com.pc += 3
}
case OpJumpFalse:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := com.getArgPos(com.pc+2, bMode)
if err != nil {
return 0, false, err
}
if com.mem[a] == 0 {
com.pc = com.mem[b]
fmt.Fprintf(log, "Jump to %d\n", com.pc)
} else {
com.pc += 3
}
case OpLessThan:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := com.getArgPos(com.pc+2, bMode)
if err != nil {
return 0, false, err
}
o, err := com.getArgPos(com.pc+3, cMode)
if err != nil {
return 0, false, err
}
if com.mem[a] < com.mem[b] {
com.mem[o] = 1
} else {
com.mem[o] = 0
}
com.pc += 4
case OpEquals:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := com.getArgPos(com.pc+2, bMode)
if err != nil {
return 0, false, err
}
o, err := com.getArgPos(com.pc+3, cMode)
if err != nil {
return 0, false, err
}
if com.mem[a] == com.mem[b] {
com.mem[o] = 1
} else {
com.mem[o] = 0
}
com.pc += 4
case OpRelativeBase:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
com.rb += com.mem[a]
com.pc += 2
default:
return 0, false, fmt.Errorf("invalid op: %d", op)
}
}
return 0, false, fmt.Errorf("fell out of memory.. pc@%d, max mem: %d", com.pc, len(com.mem))
}
func waitEnter() {
fmt.Printf("Press ENTER to continue\n")
bufio.NewReader(os.Stdin).ReadBytes('\n')
}
func drawGrid(grid []rune, width, height int) {
tiles := map[rune]rune{
'#': ' ',
'x': '█',
'^': '┴',
'v': '┬',
'<': '┤',
'>': '├',
'.': '░',
' ': '▓',
}
for y := 0; y < height; y++ {
i := y * width
for x := 0; x < width; x++ {
fmt.Printf("%c", tiles[grid[i+x]])
}
fmt.Println()
}
}
func FindAlignment(ops []int, debug bool) (int, []rune, int, int) {
var width, height int
grid := make([]rune, 0)
var x int
com := NewComputer(ops)
for !com.Halted {
out, halt, err := com.Iterate(func() int {
return 0
}, false, false)
if err != nil {
log.Fatal(fmt.Errorf("failure while executing computer: %w", err))
}
if halt {
break
}
fmt.Printf("%c", rune(out))
if rune(out) == '\n' {
if width == 0 {
width = x
}
height++
x = 0
} else {
grid = append(grid, rune(out))
x++
}
}
height--
temp := make([]rune, len(grid))
copy(temp, grid)
grid = make([]rune, (width+2)*(height+2))
for i := range grid {
grid[i] = ' '
}
for y := 0; y < height; y++ {
for x := 0; x < width; x++ {
grid[((y+1)*(width+2))+x+1] = temp[(y*width)+x]
}
}
width += 2
height += 2
if debug {
drawGrid(grid, width, height)
}
intersections := make([][2]int, 0)
for y := 0; y < height; y++ {
i := y * width
for x := 0; x < width; x++ {
if grid[i+x] != '#' {
continue
}
dirs := []int{
i + x + width,
i + x - width,
i + x - 1,
i + x + 1,
}
crossing := true
for _, d := range dirs {
if d < 0 || d >= len(grid) {
crossing = false
break
}
if grid[d] != '#' {
crossing = false
break
}
}
if crossing {
intersections = append(intersections, [2]int{x, y})
}
}
}
if debug {
fmt.Printf("%+v\n", intersections)
}
var result int
for i := range intersections {
result += intersections[i][0] * intersections[i][1]
}
return result, grid, width, height
}
type Step struct {
r rune
i int
}
func scanPath(grid []rune, width, height int, debug bool) []Step {
result := make([]Step, 0)
transitions := map[rune]map[rune]Step{
'^': {'<': {r: 'L', i: -1}, '>': {r: 'R', i: 1}},
'v': {'>': {r: 'L', i: 1}, '<': {r: 'R', i: -1}},
'<': {'v': {r: 'L', i: width}, '^': {r: 'R', i: -width}},
'>': {'^': {r: 'L', i: -width}, 'v': {r: 'R', i: width}},
}
px, py, pd := (func() (int, int, rune) {
for y := 0; y < height; y++ {
i := y * width
for x := 0; x < width; x++ {
r := grid[i+x]
if r == '^' || r == 'v' || r == '<' || r == '>' {
return x, y, r
}
}
}
return 0, 0, 0
})()
size := width * height
for {
fmt.Printf("@%dx%d,%c\n", px, py, pd)
i := (py * width) + px
var step Step
var found bool
for d, s := range transitions[pd] {
it := s.i + i
if it < 0 || it >= size {
continue
}
if grid[it] == '#' {
pd = d
step = s
found = true
break
}
}
if !found {
break
}
var num int
for {
it := (step.i * num) + i
if it < 0 || it >= size {
break
}
if grid[it] == '.' || grid[it] == ' ' {
break
} else {
px = it % width
py = it / width
num++
grid[it] = 'x'
}
}
num--
grid[(py*width)+px] = pd
result = append(result, Step{
r: step.r,
i: num,
})
fmt.Printf("Move: %c%d\n", step.r, num)
if debug {
drawGrid(grid, width, height)
waitEnter()
}
}
drawGrid(grid, width, height)
return result
}
func NotifyRobots(ops []int, grid []rune, width, height int, debug bool) int {
steps := scanPath(grid, width, height, false)
for i := range steps {
fmt.Printf("%c%d ", steps[i].r, steps[i].i)
}
fmt.Println()
// Manually calculated path is in steps.txt
// The method to calculate was straightforward enough, will automate/optimize at some point
inputs := []rune{
// Movement routine
'A', ',', 'B', ',', 'A', ',', 'C', ',', 'A', ',', 'B', ',', 'A', ',', 'C', ',', 'B', ',', 'C', '\n',
// A
'R', ',', '4', ',', 'L', ',', '1', '2', ',', 'L', ',', '8', ',', 'R', ',', '4', '\n',
// B
'L', ',', '8', ',', 'R', ',', '1', '0', ',', 'R', ',', '1', '0', ',', 'R', ',', '6', '\n',
// C
'R', ',', '4', ',', 'R', ',', '1', '0', ',', 'L', ',', '1', '2', '\n',
// Draw
'n', '\n',
}
if debug {
inputs[len(inputs)-2] = 'y'
}
com := NewComputer(ops)
com.mem[0] = 2
fmt.Println("======")
var i int
var lastOk int
for !com.Halted {
out, halt, err := com.Iterate(func() int {
r := inputs[i]
i++
if r == '\n' {
fmt.Printf("Input %d (\\n) into machine\n", r)
} else {
fmt.Printf("Input %d (%c) into machine\n", r, r)
}
return int(r)
}, true, false)
if err != nil {
log.Fatal(fmt.Errorf("failure while executing computer: %w", err))
}
if halt {
break
}
if debug {
fmt.Printf("%c", rune(out))
}
lastOk = out
}
return lastOk
}
func main() {
var debug bool
flag.BoolVar(&debug, "debug", false, "draw debug")
flag.Parse()
input, err := os.Open("input.txt")
if err != nil {
log.Fatal(fmt.Errorf("could not open input file: %w", err))
}
defer input.Close()
raw, err := ioutil.ReadAll(input)
if err != nil {
log.Fatal(fmt.Errorf("error while reading input: %w", err))
}
ops, err := func(val []byte) ([]int, error) {
vals := strings.Split(strings.TrimSpace(string(val)), ",")
result := make([]int, len(vals))
for i := range vals {
result[i], err = strconv.Atoi(vals[i])
if err != nil {
return nil, fmt.Errorf("could not convert to int: %w", err)
}
}
return result, nil
}(raw)
if err != nil {
log.Fatal(fmt.Errorf("could not read ops: %w", err))
}
alignment, grid, width, height := FindAlignment(ops, debug)
dust := NotifyRobots(ops, grid, width, height, debug)
fmt.Printf("Part 1 - Alighment: %d\n", alignment)
fmt.Printf("Part 2 - Dust: %d\n", dust)
}