package main
import (
"flag"
"fmt"
"io/ioutil"
"log"
"os"
"strconv"
"strings"
)
type Pos struct {
x, y int
}
type OpType int
const (
OpAdd OpType = iota + 1
OpMul
OpInput
OpOutput
OpJumpTrue
OpJumpFalse
OpLessThan
OpEquals
OpRelativeBase
OpEnd = 99
)
type ModeType int
const (
ModePosition ModeType = iota
ModeImmediate
ModeRelative
)
type Computer struct {
mem []int
pc int
rb int
Halted bool
}
func NewComputer(initial []int) *Computer {
com := Computer{}
com.mem = make([]int, 65535)
copy(com.mem, initial)
return &com
}
func (com *Computer) getArgPos(pos int, mode ModeType) (int, error) {
if pos < 0 || pos >= len(com.mem) {
return -1, fmt.Errorf("invalid index, out of range: %d", pos)
}
switch mode {
case ModePosition:
if com.mem[pos] < 0 || com.mem[pos] >= len(com.mem) {
return -1, fmt.Errorf("invalid position index, out of range: %d", com.mem[pos])
}
return com.mem[pos], nil
case ModeRelative:
rel := com.mem[pos] + com.rb
if rel < 0 || rel >= len(com.mem) {
return -1, fmt.Errorf("invalid relative index, out of range: %d", rel)
}
return rel, nil
}
return pos, nil
}
func (com *Computer) Iterate(input func() int, yieldOnInput bool, debug bool) (int, bool, error) {
log := ioutil.Discard
if debug {
log = os.Stdout
}
for com.pc < len(com.mem) {
op := OpType(com.mem[com.pc])
if op == OpEnd {
fmt.Fprintf(log, "Halt!\n")
com.Halted = true
return 0, true, nil
}
aMode := ModePosition
bMode := ModePosition
cMode := ModePosition
if op > 99 {
aMode = ModeType((op / 100) % 10)
bMode = ModeType((op / 1000) % 10)
cMode = ModeType((op / 10000) % 10)
op %= 100
}
switch op {
case OpAdd:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := com.getArgPos(com.pc+2, bMode)
if err != nil {
return 0, false, err
}
o, err := com.getArgPos(com.pc+3, cMode)
if err != nil {
return 0, false, err
}
com.mem[o] = com.mem[a] + com.mem[b]
fmt.Fprintf(log, "Setting %d @%d\n", com.mem[o], o)
com.pc += 4
case OpMul:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := com.getArgPos(com.pc+2, bMode)
if err != nil {
return 0, false, err
}
o, err := com.getArgPos(com.pc+3, cMode)
if err != nil {
return 0, false, err
}
com.mem[o] = com.mem[a] * com.mem[b]
fmt.Fprintf(log, "Setting %d @%d\n", com.mem[o], o)
com.pc += 4
case OpInput:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
com.mem[a] = input()
fmt.Fprintf(log, "Inputting: %d @%d\n", com.mem[a], a)
com.pc += 2
if yieldOnInput {
return 0, false, nil
}
case OpOutput:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
fmt.Fprintf(log, "Outputting: %d @%d\n", com.mem[a], a)
com.pc += 2
return com.mem[a], false, nil
case OpJumpTrue:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := com.getArgPos(com.pc+2, bMode)
if err != nil {
return 0, false, err
}
if com.mem[a] != 0 {
com.pc = com.mem[b]
fmt.Fprintf(log, "Jump to %d\n", com.pc)
} else {
com.pc += 3
}
case OpJumpFalse:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := com.getArgPos(com.pc+2, bMode)
if err != nil {
return 0, false, err
}
if com.mem[a] == 0 {
com.pc = com.mem[b]
fmt.Fprintf(log, "Jump to %d\n", com.pc)
} else {
com.pc += 3
}
case OpLessThan:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := com.getArgPos(com.pc+2, bMode)
if err != nil {
return 0, false, err
}
o, err := com.getArgPos(com.pc+3, cMode)
if err != nil {
return 0, false, err
}
if com.mem[a] < com.mem[b] {
com.mem[o] = 1
} else {
com.mem[o] = 0
}
com.pc += 4
case OpEquals:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
b, err := com.getArgPos(com.pc+2, bMode)
if err != nil {
return 0, false, err
}
o, err := com.getArgPos(com.pc+3, cMode)
if err != nil {
return 0, false, err
}
if com.mem[a] == com.mem[b] {
com.mem[o] = 1
} else {
com.mem[o] = 0
}
com.pc += 4
case OpRelativeBase:
a, err := com.getArgPos(com.pc+1, aMode)
if err != nil {
return 0, false, err
}
com.rb += com.mem[a]
com.pc += 2
default:
return 0, false, fmt.Errorf("invalid op: %d", op)
}
}
return 0, false, fmt.Errorf("fell out of memory.. pc@%d, max mem: %d", com.pc, len(com.mem))
}
const (
TileEmpty int = iota
TileWall
TileBlock
TilePaddle
TileBall
)
func RunGame(ops []int, quarters int, haveScreen bool) (int, int) {
var tiles int
var score int
sx, sy := 40, 30
var size = sx * sy
screen := make([]int, size)
var px, bx int
com := NewComputer(ops)
com.mem[0] = quarters
for !com.Halted {
var out [3]int
for i := 0; i < 3; i++ {
var halt bool
var err error
out[i], halt, err = com.Iterate(func() int {
var joy int
if px > bx {
joy = -1
} else if px < bx {
joy = 1
}
return joy
}, false, false)
if err != nil {
log.Fatal(fmt.Errorf("failure while executing computer: %w", err))
}
if halt {
break
}
if out[0] == -1 && out[1] == 0 {
score = out[2]
} else {
if out[2] == TileBlock {
tiles++
} else if out[2] == TilePaddle {
px = out[0]
} else if out[2] == TileBall {
bx = out[0]
}
screen[(out[1]*sx)+out[0]] = out[2] + 1
}
}
if haveScreen {
fmt.Printf("\033cScore: %d\n", score)
fmt.Printf("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n")
for y := 0; y < sy; y++ {
i := y * sx
for x := 0; x < sx; x++ {
var r rune
switch screen[i+x] - 1 {
case TileEmpty:
r = '.'
case TileWall:
r = '#'
case TileBlock:
r = '-'
case TilePaddle:
r = '='
case TileBall:
r = '*'
default:
r = ' '
}
fmt.Printf("%c", r)
}
fmt.Println()
}
}
}
return tiles, score
}
func main() {
var haveScreen bool
flag.BoolVar(&haveScreen, "have-screen", false, "shows the game running")
flag.Parse()
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))
}
tiles, score := RunGame(ops, 2, haveScreen)
fmt.Printf("Part 1 - Tiles: %d\n", tiles)
fmt.Printf("Part 2 - Score: %d\n", score)
}