added day13 solution, ai is ezpz

This commit is contained in:
Alex Chao
2020-08-04 14:40:34 -04:00
parent 6f12ff1ca5
commit 9399e87fc5
4 changed files with 596 additions and 0 deletions
+1
View File
@@ -18,3 +18,4 @@ Day | Name | Type of Algo & Notes
10 | Monitoring Station | - This (part2) is my favorite algo... Yes I have a favorite algo <br> Fundamentally it's a geometry problem, angles and trig <br> - Part 1: Calculated via slopes <br> - Part 2: Using Arctangent to find angles an asteroid makes against a vertical line from the home base asteroid. Then those angles can be used to determine if Asteroids are covering each other, AND iterating through all of them can find the next angle Asteroid to vaporize
11 | Space Police | - More Intcode stuff... <br> - __2D Array/Slice manipulation__ and a bit of maths/graphing <br> - Implemented a __RotateGrid__ algo
12 | The N-Body Problem | I like to call this a _(harmonic) frequency_ algo. Finding the harmonic frequency of multiple bodies/items and then finding the Least Common Multiple of those frequencies will tell you when all the bodies have returned to their initial state. <br> - I've used this approach for a leetcode problem about prisoners in jail cells too
13 | Care Package | Intcode again! It's basically every other day... <br> - part1: 2D array manipulation again <br> - part2: holy algo, some logic to basically play Bricks game. <br> - This is more of a design question for how you manage state
+1
View File
File diff suppressed because one or more lines are too long
+268
View File
@@ -0,0 +1,268 @@
/*
Intcode struct is defined within this file
Robot struct contains coordinates and robot's directions
- methods can Move the robot based on its brain's (intcode comp.) output
Draw function generates a string to display in terminal
- helper functions remove some whitespace and rotate the grid/matrix
*/
package main
import (
"adventofcode/util"
"fmt"
"log"
"strconv"
"strings"
)
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)
}
// initialize a computer
comp := MakeComputer(inputNumbers)
// no input asked for in this computer, so just fire it off once (it will terminate & not ask for an input)
comp.Step(0)
// initialize game
game := MakeGame(comp.Outputs)
// iterate through screen and find number of block tiles (i.e. a 2)
var blocks int
for _, row := range game.screen {
for _, tileID := range row {
if tileID == 2 {
blocks++
}
}
}
fmt.Println("Number of blocks", blocks)
}
// Game stores the screen appearance of the game
type Game struct {
screen [][]int
}
// MakeGame returns an instance of a Game struct
func MakeGame(setupInstructions []int) Game {
// determine the size of the screen, i.e. iterate through all instructions 3 at a time and find
// the max x (2nd of triplet) and max y (3rd of triplet)
// note: assuming all x and y values are positive, i.e. drawing down & right from top left corner
var maxX, maxY int
for i := 0; i+2 < len(setupInstructions); i += 3 {
x := setupInstructions[i]
y := setupInstructions[i+1]
if maxX < x {
maxX = x
}
if maxY < y {
maxY = y
}
}
// make screen, x is distance from left, y is distance from top, so Y is the number of rows
// X is number of columns
screen := make([][]int, maxY+1)
for i := range screen {
screen[i] = make([]int, maxX+1)
}
// fill screen
for i := 0; i+2 < len(setupInstructions); i += 3 {
fromLeft, fromTop, tileID := setupInstructions[i], setupInstructions[i+1], setupInstructions[i+2]
screen[fromTop][fromLeft] = tileID
}
return Game{screen}
}
/*
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
func (comp *Intcode) Step(input int) {
// 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
// Note: need to optimize this to not resize if the params are not being used
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
comp.Step(input)
case 2: // 2: Multiply next two and store in third
comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2]
comp.InstructionIndex += 4
comp.Step(input)
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
comp.Step(-1)
case 4: // 4: outputs its input value
// set LastOutput of the computer & log it
comp.Outputs = append(comp.Outputs, comp.PuzzleInput[param1])
// fmt.Printf("Opcode 4 output: %v\n", comp.LastOutput)
comp.InstructionIndex += 2
// continue running until terminates or asks for another input
comp.Step(input)
// 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
}
comp.Step(input)
// 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
}
comp.Step(input)
// 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
comp.Step(input)
// 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
comp.Step(input)
// 9: adjust relative base
case 9:
comp.RelativeBase += comp.PuzzleInput[param1]
comp.InstructionIndex += 2
comp.Step(input)
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
}
}
+326
View File
@@ -0,0 +1,326 @@
/*
Intcode struct is defined within this file
Game struct maintains information about the ball and paddle coordinates & state of the screen
NOTE: the "computer" and "game" are decoupled in this solution which is probably not _ideal_,
NOTE but reusing the old code, it was simpler like this
NOTE: The "hold for an input" value was changed from -1 to -2 in the Intcode computer
*/
package main
import (
"adventofcode/util"
"fmt"
"log"
"strconv"
"strings"
)
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)
}
// set first puzzle input value to 2 to "play the game for free"
inputNumbers[0] = 2
// initialize a computer
comp := MakeComputer(inputNumbers)
// Get the setup Outputs for the game screen
// NOTE inputting -2 because this is the input that "holds" the computer when it's asking for an input
comp.Step(-2)
// initialize game
game := MakeGame(comp.Outputs)
// loop while the intcode computer is asking for an input
for comp.IsRunning {
// clear previous outputs of computer
comp.Outputs = []int{}
// Find joystick input based on ball vs paddle x coord: 0 (zero value), -1 (left) or 1 (right)
var joystickInput int
if game.ballX < game.paddleX {
joystickInput = -1
} else if game.ballX > game.paddleX {
joystickInput = 1
}
// move joystick by giving computer input
comp.Step(joystickInput)
// handle all trios of outputs from the computer
for i := 0; i < len(comp.Outputs); i += 3 {
game.HandleOutput(comp.Outputs[i], comp.Outputs[i+1], comp.Outputs[i+2])
}
// // Uncomment to print board at every "step"
// for _, level := range game.screen {
// // pretty print game screen
// var strLevel string
// for _, tileID := range level {
// if tileID == 0 {
// strLevel += " "
// } else {
// strLevel += strconv.Itoa(tileID)
// }
// }
// fmt.Println(strLevel)
// }
// fmt.Println("") // print an extra line to separate out the new game screens
// time.Sleep(time.Millisecond * 10)
}
fmt.Println("Final score", game.score)
}
// Game stores the screen appearance of the game
type Game struct {
screen [][]int
score int
ballX, ballY int
paddleX, paddleY int
}
// MakeGame returns an instance of a Game struct
func MakeGame(setupInstructions []int) Game {
// determine the size of the screen, i.e. iterate through all instructions 3 at a time and find
// the max x (2nd of triplet) and max y (3rd of triplet)
// note: assuming all x and y values are positive, i.e. drawing down & right from top left corner
var maxX, maxY int
for i := 0; i+2 < len(setupInstructions); i += 3 {
x := setupInstructions[i]
y := setupInstructions[i+1]
if x < 0 || y < 0 {
continue
}
if maxX < x {
maxX = x
}
if maxY < y {
maxY = y
}
}
// make screen, x is distance from left, y is distance from top, so Y is the number of rows
// X is number of columns. +1 to convert max index to length of slice
screen := make([][]int, maxY+1)
for i := range screen {
screen[i] = make([]int, maxX+1)
}
// initialize Game
game := Game{screen, 0, 0, 0, 0, 0}
// fill screen with initial outputs
for i := 0; i+2 < len(setupInstructions); i += 3 {
game.HandleOutput(setupInstructions[i], setupInstructions[i+1], setupInstructions[i+2])
}
return game
}
// HandleOutput handles one trio of outputs from intcode
func (game *Game) HandleOutput(fromLeft, fromTop, tileID int) {
if fromLeft == -1 && fromTop == 0 {
game.score = tileID
} else {
game.screen[fromTop][fromLeft] = tileID
if tileID == 3 {
game.paddleX = fromLeft
game.paddleY = fromTop
}
if tileID == 4 {
game.ballX = fromLeft
game.ballY = fromTop
}
}
}
/*
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
func (comp *Intcode) Step(input int) {
// 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
// Note: need to optimize this to not resize if the params are not being used
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
comp.Step(input)
case 2: // 2: Multiply next two and store in third
comp.PuzzleInput[param3] = comp.PuzzleInput[param1] * comp.PuzzleInput[param2]
comp.InstructionIndex += 4
comp.Step(input)
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 == -2 {
return
}
// else recurse with a -1 to signal the initial input has been processed
comp.PuzzleInput[param1] = input
comp.InstructionIndex += 2
comp.Step(-2)
case 4: // 4: outputs its input value
// set LastOutput of the computer & log it
comp.Outputs = append(comp.Outputs, comp.PuzzleInput[param1])
// fmt.Printf("Opcode 4 output: %v\n", comp.LastOutput)
comp.InstructionIndex += 2
// continue running until terminates or asks for another input
comp.Step(input)
// 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
}
comp.Step(input)
// 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
}
comp.Step(input)
// 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
comp.Step(input)
// 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
comp.Step(input)
// 9: adjust relative base
case 9:
comp.RelativeBase += comp.PuzzleInput[param1]
comp.InstructionIndex += 2
comp.Step(input)
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
}
}