AdventOfCode2020/08/main.go

166 lines
4.0 KiB
Go
Raw Normal View History

2020-12-08 23:48:21 +00:00
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
func main() {
partOne()
partTwo()
}
type opCode struct {
operation string
value int
}
type register struct {
value int
}
type registry map[string]*register
type program []opCode
func lineToOp(s string) opCode {
sp := strings.Split(s, " ")
i, _ := strconv.Atoi(sp[1])
return opCode{
operation: sp[0],
value: i,
}
}
// take registry, program, current opcode location, perform operation if possible, return next opcode location
func execute(r registry, p program, i int) (int, error) {
if i >= len(p) {
return -1, fmt.Errorf("program terminates")
}
currentOp := p[i]
switch currentOp.operation {
case "nop":
return i + 1, nil
case "acc":
r["acc"].value = r["acc"].value + currentOp.value
return i + 1, nil
case "jmp":
return i + currentOp.value, nil
default:
return -1, fmt.Errorf("unknown opcode")
}
}
// The boot code is represented as a text file with one instruction per line of text.
// Each instruction consists of an operation (acc, jmp, or nop) and an argument (a signed number like +4 or -20).
// * `acc` increases or decreases a single global value called the accumulator by the value given in the argument.
// For example, acc +7 would increase the accumulator by 7. The accumulator starts at 0.
// After an acc instruction, the instruction immediately below it is executed next.
// * `jmp` jumps to a new instruction relative to itself.
// The next instruction to execute is found using the argument as an offset from the jmp instruction;
// for example, jmp +2 would skip the next instruction, jmp +1 would continue to the instruction immediately below it,
// and jmp -20 would cause the instruction 20 lines above to be executed next.
// * `nop` stands for No OPeration - it does nothing. The instruction immediately below it is executed next.
// Run your copy of the boot code. Immediately before any instruction is executed a second time, what value is in the accumulator?
func partOne() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
prog := program{}
for scanner.Scan() {
line := scanner.Text()
prog = append(prog, lineToOp(line))
}
reg := registry{
"acc": &register{
value: 0,
},
}
currentOp := 0
executedLines := []int{}
for {
executedLines = append(executedLines, currentOp)
nextOp, err := execute(reg, prog, currentOp)
if nextOp == -1 || err != nil {
fmt.Println(err.Error())
break
}
for _, j := range executedLines {
if nextOp == j {
fmt.Println(reg["acc"].value)
return
}
}
currentOp = nextOp
}
}
// The program is supposed to terminate by attempting to execute an instruction immediately after the last instruction in the file.
// By changing exactly one jmp or nop, you can repair the boot code and make it terminate correctly.
// Fix the program so that it terminates normally by changing exactly one jmp (to nop) or nop (to jmp).
// What is the value of the accumulator after the program terminates?
func partTwo() {
f, _ := os.Open("input")
reader := bufio.NewReader(f)
scanner := bufio.NewScanner(reader)
prog := program{}
for scanner.Scan() {
line := scanner.Text()
prog = append(prog, lineToOp(line))
}
// guess we're just going to brute-force this huh
for i := range prog {
newP := make(program, len(prog))
copy(newP, prog)
if prog[i].operation == "jmp" {
newP[i].operation = "nop"
} else if prog[i].operation == "nop" {
newP[i].operation = "jmp"
}
result, err := isolatedProgramRunner(newP)
if err == nil {
fmt.Println(result)
return
}
}
}
func isolatedProgramRunner(p program) (int, error) {
reg := registry{
"acc": &register{
value: 0,
},
}
executedLines := []int{}
currentOp := 0
for {
executedLines = append(executedLines, currentOp)
nextOp, err := execute(reg, p, currentOp)
if nextOp == -1 {
return reg["acc"].value, nil
}
if err != nil {
return -1, err
}
for _, j := range executedLines {
if nextOp == j {
return -1, fmt.Errorf("program loops")
}
}
currentOp = nextOp
}
}