package main import ( "fmt" "io" "io/ioutil" "log" "os" "strconv" "strings" ) type OpType int const ( OpAdd OpType = iota + 1 OpMul OpInput OpOutput OpJumpTrue OpJumpFalse OpLessThan OpEquals OpEnd = 99 ) type ModeType int const ( ModePosition ModeType = 0 ModeImmediate = 1 ) func getArgPos(ops []int, pos int, mode ModeType) (int, error) { if pos >= len(ops) { return -1, fmt.Errorf("invalid index, out of range: %d", pos) } if mode == ModePosition { if pos >= len(ops) { return -1, fmt.Errorf("invalid immediate index, out of range: %d", pos) } return ops[pos], nil } return pos, nil } func ExecuteProgram(ops []int, input func() int, output io.Writer, debug bool) ([]int, error) { log := ioutil.Discard if debug { log = os.Stdout } var pc int for pc < len(ops) { op := OpType(ops[pc]) if op == OpEnd { break } aMode := ModePosition bMode := ModePosition if op > 99 { aMode = ModeType((op / 100) % 10) bMode = ModeType((op / 1000) % 10) op %= 100 } switch op { case OpAdd: a, err := getArgPos(ops, pc+1, aMode) if err != nil { return nil, err } b, err := getArgPos(ops, pc+2, bMode) if err != nil { return nil, err } o, err := getArgPos(ops, pc+3, ModePosition) if err != nil { return nil, err } ops[o] = ops[a] + ops[b] fmt.Fprintf(log, "Setting %d @%d\n", ops[o], o) pc += 4 case OpMul: a, err := getArgPos(ops, pc+1, aMode) if err != nil { return nil, err } b, err := getArgPos(ops, pc+2, bMode) if err != nil { return nil, err } o, err := getArgPos(ops, pc+3, ModePosition) if err != nil { return nil, err } ops[o] = ops[a] * ops[b] fmt.Fprintf(log, "Setting %d @%d\n", ops[o], o) pc += 4 case OpInput: a, err := getArgPos(ops, pc+1, ModePosition) if err != nil { return nil, err } ops[a] = input() fmt.Fprintf(log, "Inputting: %d @%d\n", ops[a], a) pc += 2 case OpOutput: a, err := getArgPos(ops, pc+1, aMode) if err != nil { return nil, err } if output != nil { fmt.Fprintf(output, "%d", ops[a]) } pc += 2 case OpJumpTrue: a, err := getArgPos(ops, pc+1, aMode) if err != nil { return nil, err } b, err := getArgPos(ops, pc+2, bMode) if err != nil { return nil, err } if ops[a] != 0 { pc = ops[b] fmt.Fprintf(log, "Jump to %d\n", pc) } else { pc += 3 } case OpJumpFalse: a, err := getArgPos(ops, pc+1, aMode) if err != nil { return nil, err } b, err := getArgPos(ops, pc+2, bMode) if err != nil { return nil, err } if ops[a] == 0 { pc = ops[b] fmt.Fprintf(log, "Jump to %d\n", pc) } else { pc += 3 } case OpLessThan: a, err := getArgPos(ops, pc+1, aMode) if err != nil { return nil, err } b, err := getArgPos(ops, pc+2, bMode) if err != nil { return nil, err } o, err := getArgPos(ops, pc+3, ModePosition) if err != nil { return nil, err } if ops[a] < ops[b] { ops[o] = 1 } else { ops[o] = 0 } pc += 4 case OpEquals: a, err := getArgPos(ops, pc+1, aMode) if err != nil { return nil, err } b, err := getArgPos(ops, pc+2, bMode) if err != nil { return nil, err } o, err := getArgPos(ops, pc+3, ModePosition) if err != nil { return nil, err } if ops[a] == ops[b] { ops[o] = 1 } else { ops[o] = 0 } pc += 4 default: return nil, fmt.Errorf("invalid op: %d", op) } } return ops, nil } func main() { 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)) } prog := make([]int, len(ops)) copy(prog, ops) // Part 1 fmt.Println("Part 1...") _, err = ExecuteProgram(prog, func() int { return 1 }, os.Stdout, false) if err != nil { log.Fatal(fmt.Errorf("could not run part 1: %w", err)) } fmt.Println() copy(prog, ops) fmt.Println("\nPart 2...") _, err = ExecuteProgram(prog, func() int { return 5 }, os.Stdout, false) if err != nil { log.Fatal(fmt.Errorf("could not run part 2: %w", err)) } fmt.Println() }