2020-12-08 23:48:21 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"fmt"
|
|
|
|
"os"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
2020-12-12 02:57:10 +00:00
|
|
|
"time"
|
2020-12-08 23:48:21 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
func main() {
|
2020-12-12 02:57:10 +00:00
|
|
|
start := time.Now()
|
2020-12-08 23:48:21 +00:00
|
|
|
partOne()
|
2020-12-12 02:57:10 +00:00
|
|
|
duration := time.Since(start)
|
2020-12-08 23:48:21 +00:00
|
|
|
partTwo()
|
2020-12-12 02:57:10 +00:00
|
|
|
duration2 := time.Since(start)
|
|
|
|
fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration)
|
2020-12-08 23:48:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|