166 lines
4.0 KiB
Go
166 lines
4.0 KiB
Go
|
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": ®ister{
|
||
|
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": ®ister{
|
||
|
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
|
||
|
}
|
||
|
}
|