Files
advent-of-code-go/2019/day17/part2/main.go
T
2020-11-27 02:53:18 -05:00

349 lines
10 KiB
Go

/*
Intcode struct is defined within this file
Robot struct houses an Intcode computer and a method to initialize the floor details
*/
package main
import (
"github.com/alexchao26/advent-of-code-go/util"
"fmt"
"log"
"strconv"
"strings"
"time"
)
func main() {
// read the input file, modify it to a slice of numbers
inputFile := util.ReadFile("../input.txt")
splitStrings := strings.Split(inputFile, ",")
inputNumbers := make([]int, len(splitStrings))
for i, v := range splitStrings {
inputNumbers[i], _ = strconv.Atoi(v)
}
// Modify the first address from a 1 to a 2 to start the vacuum robot
inputNumbers[0] = 2
robot := MakeRobot(inputNumbers)
// fire off function to populate the robot's floorGrid property
robot.GetFloorGrid()
// NOTE the computer accepts numbers as inputs, but these numbers correlate to ASCII
// computer will ask for main movement routine. allows: [A-C,] i.e. A B C separated by ,
// end with a newline, e.g. integer 10 in ASCII
A, B, C := int('A'), int('B'), int('C')
comma, newline := int(','), int('\n')
mainRoutineOrder := []int{A, B, A, C, B, C, B, C, A, C}
for i, routine := range mainRoutineOrder {
robot.computer.Step(routine)
if i != len(mainRoutineOrder)-1 {
robot.computer.Step(comma)
} else {
robot.computer.Step(newline)
}
}
// computer then asks for details of each movement function
// accepts [LR0-9,] ended with a newline
// NOTE I determined these paths manually after printing the floor and finding a pattern by eye...
L, R := int('L'), int('R')
numbers := make([]int, 10)
for i := range numbers {
numbers[i] = int('0') + i
}
// A is L10 R12 R12
patternA := []int{
L, comma, numbers[1], numbers[0], comma,
R, comma, numbers[1], numbers[2], comma,
R, comma, numbers[1], numbers[2],
newline,
}
// B is R6 R10 L10
patternB := []int{
R, comma, numbers[6], comma,
R, comma, numbers[1], numbers[0], comma,
L, comma, numbers[1], numbers[0],
newline,
}
// C is R10 L10 L12 R6
patternC := []int{
R, comma, numbers[1], numbers[0], comma,
L, comma, numbers[1], numbers[0], comma,
L, comma, numbers[1], numbers[2], comma,
R, comma, numbers[6],
newline,
}
// fmt.Println("pattern A: ")
for _, v := range patternA {
// fmt.Printf("%v,", v)
robot.computer.Step(v)
}
for _, v := range patternB {
robot.computer.Step(v)
}
for _, v := range patternC {
robot.computer.Step(v)
}
// finally, asks for continuous video feed or not 'y' or 'n' and a new line
robot.computer.Step(int('y'))
robot.computer.Step(newline)
fmt.Println("Dust collected", robot.computer.Outputs[len(robot.computer.Outputs)-1])
}
// Robot struct to maintain detail's on the Robot's coordinates, path
type Robot struct {
row, col int
floorGrid [][]string
computer *Intcode
}
// MakeRobot returns an instance of a Robot
func MakeRobot(intcodeInput []int) *Robot {
return &Robot{
0,
0,
make([][]string, 0),
MakeComputer(intcodeInput),
}
}
// GetFloorGrid will fire off the computer and populate the robot's floor details
func (robot *Robot) GetFloorGrid() {
robot.computer.Step(-1)
robot.floorGrid = append(robot.floorGrid, []string{})
row := 0
for _, v := range robot.computer.Outputs {
switch v {
case 10:
row++
robot.floorGrid = append(robot.floorGrid, []string{})
default:
tileType := string(v)
robot.floorGrid[row] = append(robot.floorGrid[row], tileType)
}
}
// parse off empty slices @ end
for i := len(robot.floorGrid) - 1; i >= 0; i-- {
if len(robot.floorGrid[i]) == 0 {
robot.floorGrid = robot.floorGrid[:len(robot.floorGrid)-1]
}
}
}
/*
Intcode is an OOP approach *************************************************
MakeComputer is equivalent to the constructor
Step takes in an input int and updates properties in the computer:
- InstructionIndex: where to read the next instruction from
- LastOutput, what the last opcode 4 outputted
- PuzzleIndex based if the last instruction modified the puzzle at all
****************************************************************************/
type Intcode struct {
PuzzleInput []int // file/puzzle input parsed into slice of ints
InstructionIndex int // stores the index where the next instruction is
RelativeBase int // relative base for opcode 9 and param mode 2
Outputs []int // stores all outputs
IsRunning bool // will be true until a 99 opcode is hit
}
// MakeComputer initializes a new comp
func MakeComputer(PuzzleInput []int) *Intcode {
puzzleInputCopy := make([]int, len(PuzzleInput))
copy(puzzleInputCopy, PuzzleInput)
comp := Intcode{
puzzleInputCopy,
0,
0,
make([]int, 0),
true,
}
return &comp
}
// Step will read the next 4 values in the input `sli` and make updates
// according to the opcodes
// Update to run iteratively (while the computer is running)
// it will also return out if a -1 input is asked for
// then call Step again to provide the next input, or run with -1 from the start
// to run the computer until it asks for an input OR terminates
func (comp *Intcode) Step(input int) {
for comp.IsRunning {
// read the instruction, opcode and the indexes where the params point to
opcode, paramIndexes := comp.GetOpCodeAndParamIndexes()
param1, param2, param3 := paramIndexes[0], paramIndexes[1], paramIndexes[2]
// ensure params are within the bounds of PuzzleInput, resize if necessary
switch opcode {
case 1, 2, 7, 8:
comp.ResizeMemory(param1, param2, param3)
case 5, 6:
comp.ResizeMemory(param1, param2)
case 3, 4, 9:
comp.ResizeMemory(param1)
}
switch opcode {
case 99: // 99: Terminates program
fmt.Println("Terminating...")
comp.IsRunning = false
case 1: // 1: Add next two paramIndexes, store in third
comp.PuzzleInput[param3] = comp.PuzzleInput[param1] + comp.PuzzleInput[param2]
comp.InstructionIndex += 4
case 2: // 2: Multiply next two and store in third
comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2]
comp.InstructionIndex += 4
case 3: // 3: Takes one input and saves it to position of one parameter
// check if input has already been used (i.e. input == -1)
// if it's been used, return out to prevent further Steps
// NOTE: making a big assumption that -1 will never be an input...
if input == -1 {
return
}
// else recurse with a -1 to signal the initial input has been processed
comp.PuzzleInput[param1] = input
comp.InstructionIndex += 2
// change the input value so the next time a 3 opcode is hit, will return out
input = -1
case 4: // 4: outputs its input value
output := comp.PuzzleInput[param1]
// set LastOutput of the computer & log it
comp.Outputs = append(comp.Outputs, output)
// NOTE this is specific to day17 to print the robot walking around the scaffold
// if the last two outputs are newlines (ASCII 10's), print out the output
// then just clear the output to make life easy
if output == 10 && comp.Outputs[len(comp.Outputs)-2] == 10 {
Print2DGrid(comp.Outputs)
// clear outputs slice, sleep for 100ms
comp.Outputs = []int{}
time.Sleep(time.Millisecond * 100)
}
comp.InstructionIndex += 2
// 5: jump-if-true: if first param != 0, move pointer to second param, else nothing
case 5:
if comp.PuzzleInput[param1] != 0 {
comp.InstructionIndex = comp.PuzzleInput[param2]
} else {
comp.InstructionIndex += 3
}
// 6: jump-if-false, if first param == 0 then set instruction pointer to 2nd param, else nothing
case 6:
if comp.PuzzleInput[param1] == 0 {
comp.InstructionIndex = comp.PuzzleInput[param2]
} else {
comp.InstructionIndex += 3
}
// 7: less-than, if param1 < param2 then store 1 in postion of 3rd param, else store 0
case 7:
if comp.PuzzleInput[param1] < comp.PuzzleInput[param2] {
comp.PuzzleInput[param3] = 1
} else {
comp.PuzzleInput[param3] = 0
}
comp.InstructionIndex += 4
// 8: equals, if param1 == param2 then set position of 3rd param to 1, else store 0
case 8:
if comp.PuzzleInput[param1] == comp.PuzzleInput[param2] {
comp.PuzzleInput[param3] = 1
} else {
comp.PuzzleInput[param3] = 0
}
comp.InstructionIndex += 4
// 9: adjust relative base
case 9:
comp.RelativeBase += comp.PuzzleInput[param1]
comp.InstructionIndex += 2
default:
log.Fatalf("Error: unknown opcode %v at index %v", opcode, comp.PuzzleInput[comp.InstructionIndex])
}
}
}
/*
GetOpCodeAndParamIndexes will parse the instruction at comp.PuzzleInput[comp.InstructionIndex]
- opcode will be the left two digits, mod by 100 will get that
- rest of instructions will be grabbed via mod 10
- these also have to be parsed for the
*/
func (comp *Intcode) GetOpCodeAndParamIndexes() (int, [3]int) {
instruction := comp.PuzzleInput[comp.InstructionIndex]
// opcode is the lowest two digits, so mod by 100
opcode := instruction % 100
instruction /= 100
// assign the indexes that need to be read by reading the parameter modes
var paramIndexes [3]int
for i := 1; i <= 3 && comp.InstructionIndex+i < len(comp.PuzzleInput); i++ {
// grab the mode with a mod, last digit
mode := instruction % 10
instruction /= 10
switch mode {
case 0: // position mode, index will be the value at the index
paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i]
case 1: // immediate mode, the index itself
paramIndexes[i-1] = comp.InstructionIndex + i
case 2: // relative mode, like position mode but index is added to relative base
paramIndexes[i-1] = comp.PuzzleInput[comp.InstructionIndex+i] + comp.RelativeBase
}
}
return opcode, paramIndexes
}
// ResizeMemory will take any number of integers and resize the computer's memory appropriately
func (comp *Intcode) ResizeMemory(sizes ...int) {
// get largest of input sizes
maxArg := sizes[0]
for _, v := range sizes {
if v > maxArg {
maxArg = v
}
}
// resize if PuzzleInput's length is shorter
if maxArg >= len(comp.PuzzleInput) {
// make empty slice to copy into, of the new, larger size
resizedPuzzleInput := make([]int, maxArg+1)
// copy old puzzle input values in
copy(resizedPuzzleInput, comp.PuzzleInput)
// overwrite puzzle input
comp.PuzzleInput = resizedPuzzleInput
}
}
// Print2DGrid is day17 specific. allValues are ASCII ints including 10 for newline
func Print2DGrid(allValues []int) {
var row int
floorGrid := [][]string{[]string{}}
for _, v := range allValues {
switch v {
case 10:
row++
floorGrid = append(floorGrid, []string{})
default:
tileType := string(v)
floorGrid[row] = append(floorGrid[row], tileType)
}
}
for _, v := range floorGrid {
fmt.Println(v)
}
}