package main
import (
"fmt"
"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
}
type Amplifier struct {
ops []int
pc int
Halted bool
}
func (amp *Amplifier) Iterate(input func() int, yieldOnInput bool, debug bool) (int, bool, error) {
log := ioutil.Discard
if debug {
log = os.Stdout
}
for amp.pc < len(amp.ops) {
op := OpType(amp.ops[amp.pc])
if op == OpEnd {
fmt.Fprintf(log, "Halt!\n")
amp.Halted = true
return 0, true, nil
}
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(amp.ops, amp.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := getArgPos(amp.ops, amp.pc+2, bMode)
if err != nil {
return 0, false, err
}
o, err := getArgPos(amp.ops, amp.pc+3, ModePosition)
if err != nil {
return 0, false, err
}
amp.ops[o] = amp.ops[a] + amp.ops[b]
fmt.Fprintf(log, "Setting %d @%d\n", amp.ops[o], o)
amp.pc += 4
case OpMul:
a, err := getArgPos(amp.ops, amp.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := getArgPos(amp.ops, amp.pc+2, bMode)
if err != nil {
return 0, false, err
}
o, err := getArgPos(amp.ops, amp.pc+3, ModePosition)
if err != nil {
return 0, false, err
}
amp.ops[o] = amp.ops[a] * amp.ops[b]
fmt.Fprintf(log, "Setting %d @%d\n", amp.ops[o], o)
amp.pc += 4
case OpInput:
a, err := getArgPos(amp.ops, amp.pc+1, ModePosition)
if err != nil {
return 0, false, err
}
amp.ops[a] = input()
fmt.Fprintf(log, "Inputting: %d @%d\n", amp.ops[a], a)
amp.pc += 2
if yieldOnInput {
return 0, false, nil
}
case OpOutput:
a, err := getArgPos(amp.ops, amp.pc+1, aMode)
if err != nil {
return 0, false, err
}
fmt.Fprintf(log, "Outputting: %d @%d\n", amp.ops[a], a)
amp.pc += 2
return amp.ops[a], false, nil
case OpJumpTrue:
a, err := getArgPos(amp.ops, amp.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := getArgPos(amp.ops, amp.pc+2, bMode)
if err != nil {
return 0, false, err
}
if amp.ops[a] != 0 {
amp.pc = amp.ops[b]
fmt.Fprintf(log, "Jump to %d\n", amp.pc)
} else {
amp.pc += 3
}
case OpJumpFalse:
a, err := getArgPos(amp.ops, amp.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := getArgPos(amp.ops, amp.pc+2, bMode)
if err != nil {
return 0, false, err
}
if amp.ops[a] == 0 {
amp.pc = amp.ops[b]
fmt.Fprintf(log, "Jump to %d\n", amp.pc)
} else {
amp.pc += 3
}
case OpLessThan:
a, err := getArgPos(amp.ops, amp.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := getArgPos(amp.ops, amp.pc+2, bMode)
if err != nil {
return 0, false, err
}
o, err := getArgPos(amp.ops, amp.pc+3, ModePosition)
if err != nil {
return 0, false, err
}
if amp.ops[a] < amp.ops[b] {
amp.ops[o] = 1
} else {
amp.ops[o] = 0
}
amp.pc += 4
case OpEquals:
a, err := getArgPos(amp.ops, amp.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := getArgPos(amp.ops, amp.pc+2, bMode)
if err != nil {
return 0, false, err
}
o, err := getArgPos(amp.ops, amp.pc+3, ModePosition)
if err != nil {
return 0, false, err
}
if amp.ops[a] == amp.ops[b] {
amp.ops[o] = 1
} else {
amp.ops[o] = 0
}
amp.pc += 4
default:
return 0, false, fmt.Errorf("invalid op: %d", op)
}
}
return 0, false, nil
}
func RunCircuit(ops []int, inputs []int, debug bool) (int, error) {
amps := make([]Amplifier, len(inputs))
for i := range amps {
amps[i].ops = make([]int, len(ops))
copy(amps[i].ops, ops)
}
// Set phase values
for i := range amps {
_, _, err := amps[i].Iterate(func() int {
return inputs[i]
}, true, debug)
if err != nil {
return 0, fmt.Errorf("error while executing: %w", err)
}
}
// Run output
var output int
var halt bool
var count int
for !halt {
for i := range amps {
var o int
var err error
o, halt, err = amps[i].Iterate(func() int {
return output
}, false, debug)
if err != nil {
return 0, fmt.Errorf("error while executing: %w", err)
}
if halt {
break
}
output = o
}
if debug {
count++
fmt.Printf("Iteration: %d -> %d\n", count, output)
}
}
return output, nil
}
func verifyInput(input []int) bool {
seen := make(map[int]bool)
for _, i := range input {
if _, ok := seen[i]; ok {
return false
}
seen[i] = true
}
return true
}
func FindPhaseSettings(ops []int, lo, hi int) (int, error) {
result := 0
for i := lo; i <= hi; i++ {
for j := lo; j <= hi; j++ {
for k := lo; k <= hi; k++ {
for l := lo; l <= hi; l++ {
for m := lo; m <= hi; m++ {
input := []int{i, j, k, l, m}
if !verifyInput(input) {
continue
}
amp, err := RunCircuit(ops, input, false)
if err != nil {
return 0, fmt.Errorf("could not run phase check: %w", err)
}
if amp > result {
result = amp
}
}
}
}
}
}
return result, 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))
}
fmt.Println(ops)
// Part 1
result, err := FindPhaseSettings(ops, 0, 4)
if err != nil {
log.Fatal(fmt.Errorf("could not run part 1: %w", err))
}
fmt.Printf("Part 1: %d\n", result)
// Part 2
result, err = FindPhaseSettings(ops, 5, 9)
if err != nil {
log.Fatal(fmt.Errorf("could not run part 2: %w", err))
}
fmt.Printf("Part 2: %d\n", result)
}