package main import ( "bufio" "fmt" "os" "strconv" "strings" "time" ) func main() { start := time.Now() partOne() duration := time.Since(start) partTwo() duration2 := time.Since(start) fmt.Printf("p1: %s, p2: %s\n", duration, duration2-duration) } 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 } }