1
0
Fork 0
mirror of https://github.com/Luzifer/aoc2019.git synced 2024-10-18 03:04:19 +00:00

Add solution for Day 17

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2019-12-17 22:49:10 +01:00
parent 41352399f9
commit 84e8a9afa0
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
3 changed files with 435 additions and 0 deletions

413
day17.go Normal file
View file

@ -0,0 +1,413 @@
package aoc2019
import (
"fmt"
"io/ioutil"
"math"
"reflect"
"strconv"
"strings"
"sync"
"github.com/pkg/errors"
)
type day17TileType rune
const (
day17TileTypeNewline day17TileType = 10
day17TileTypeRobotDown day17TileType = 'v'
day17TileTypeRobotLeft day17TileType = '<'
day17TileTypeRobotLost day17TileType = 'X'
day17TileTypeRobotRight day17TileType = '>'
day17TileTypeRobotUp day17TileType = '^'
day17TileTypeScaffold day17TileType = 35 // #
day17TileTypeSpace day17TileType = 46 // .
)
type day17Tile struct {
X, Y int64
Type day17TileType
}
type day17Grid map[string]*day17Tile
func (d day17Grid) bounds() (minX, minY, maxX, maxY int64) {
minX, minY = math.MaxInt64, math.MaxInt64
for _, t := range d {
if t.X < minX {
minX = t.X
}
if t.Y < minY {
minY = t.Y
}
if t.X > maxX {
maxX = t.X
}
if t.Y > maxY {
maxY = t.Y
}
}
return
}
func (d day17Grid) findCurrentPosition() (int64, int64, day17TileType) {
for _, t := range d {
if t.Type == day17TileTypeRobotDown || t.Type == day17TileTypeRobotLeft || t.Type == day17TileTypeRobotRight || t.Type == day17TileTypeRobotUp {
return t.X, t.Y, t.Type
}
}
return -1, -1, day17TileTypeRobotLost
}
func (d day17Grid) findPath() []int64 {
var (
count int64
directives []int64
x, y, direction = d.findCurrentPosition()
)
move := func() {
count++
switch direction {
case day17TileTypeRobotDown:
y += 1
case day17TileTypeRobotLeft:
x -= 1
case day17TileTypeRobotRight:
x += 1
case day17TileTypeRobotUp:
y -= 1
}
}
opportunities := func() (forward, left, right bool) {
var tileFwd, tileL, tileR *day17Tile
switch direction {
case day17TileTypeRobotDown:
tileFwd, tileL, tileR = d[d.mapKey(x, y+1)], d[d.mapKey(x+1, y)], d[d.mapKey(x-1, y)]
case day17TileTypeRobotLeft:
tileFwd, tileL, tileR = d[d.mapKey(x-1, y)], d[d.mapKey(x, y+1)], d[d.mapKey(x, y-1)]
case day17TileTypeRobotRight:
tileFwd, tileL, tileR = d[d.mapKey(x+1, y)], d[d.mapKey(x, y-1)], d[d.mapKey(x, y+1)]
case day17TileTypeRobotUp:
tileFwd, tileL, tileR = d[d.mapKey(x, y-1)], d[d.mapKey(x-1, y)], d[d.mapKey(x+1, y)]
}
forward = tileFwd != nil && tileFwd.Type == day17TileTypeScaffold
left = tileL != nil && tileL.Type == day17TileTypeScaffold
right = tileR != nil && tileR.Type == day17TileTypeScaffold
return
}
turn := func(left bool) {
var (
dl = []day17TileType{day17TileTypeRobotUp, day17TileTypeRobotLeft, day17TileTypeRobotDown, day17TileTypeRobotRight}
dr = []day17TileType{day17TileTypeRobotUp, day17TileTypeRobotRight, day17TileTypeRobotDown, day17TileTypeRobotLeft}
nd []day17TileType
)
if count > 0 {
directives = append(directives, count)
count = 0
}
if left {
nd = dl
directives = append(directives, int64('L'))
} else {
nd = dr
directives = append(directives, int64('R'))
}
var cd int
for i, t := range nd {
if t == direction {
cd = i
break
}
}
if cd == len(nd)-1 {
cd = -1
}
direction = nd[cd+1]
}
// Do the movement
for {
fw, l, r := opportunities()
if fw {
move()
continue
}
if l {
turn(true)
continue
}
if r {
turn(false)
continue
}
// Nothing possible, must be the end
directives = append(directives, count)
break
}
return directives
}
func (d day17Grid) isScaffoldIntersection(x, y int64) bool {
t := d[d.mapKey(x, y)]
if t.Type == day17TileTypeSpace {
// Space cannot be a scaffold intersection
return false
}
var count int64
for _, at := range []*day17Tile{
d[d.mapKey(x-1, y)],
d[d.mapKey(x+1, y)],
d[d.mapKey(x, y-1)],
d[d.mapKey(x, y+1)],
} {
if at != nil && at.Type != day17TileTypeSpace {
count++
}
}
// One adjacent scaffold is a line
// Two adjacent scaffolds is a curve or a straight line
// Three or four adjacent scaffolds cannot be a line or curve, must be intersection
return count >= 3
}
func (d day17Grid) mapKey(x, y int64) string { return fmt.Sprintf("%d:%d", x, y) }
func (d day17Grid) print() {
var minX, minY, maxX, maxY = d.bounds()
for y := minY; y <= maxY; y++ {
for x := minX; x <= maxX; x++ {
fmt.Printf("%s", string(d[d.mapKey(x, y)].Type))
}
fmt.Println()
}
}
func day17ReadGrid(code []int64) (day17Grid, error) {
var (
grid = make(day17Grid)
out = make(chan int64)
x, y int64
)
go executeIntcode(code, nil, out)
for o := range out {
switch day17TileType(o) {
case day17TileTypeNewline:
y += 1
x = 0
case day17TileTypeRobotDown, day17TileTypeRobotLeft, day17TileTypeRobotRight, day17TileTypeRobotUp:
fallthrough // Not yet used
case day17TileTypeRobotLost:
fallthrough // Not yet used
case day17TileTypeScaffold, day17TileTypeSpace:
grid[grid.mapKey(x, y)] = &day17Tile{X: x, Y: y, Type: day17TileType(o)}
x += 1
default:
return nil, errors.Errorf("Invalid character %d", o)
}
}
return grid, nil
}
func solveDay17Part1(inFile string) (int64, error) {
rawCode, err := ioutil.ReadFile(inFile)
if err != nil {
return 0, errors.Wrap(err, "Unable to read intcode")
}
code, err := parseIntcode(strings.TrimSpace(string(rawCode)))
if err != nil {
return 0, errors.Wrap(err, "Unable to parse intcode")
}
grid, err := day17ReadGrid(code)
if err != nil {
return 0, errors.Wrap(err, "Unable to read grid")
}
var (
apSum int64
minX, minY, maxX, maxY = grid.bounds()
)
for y := minY; y <= maxY; y++ {
for x := minX; x <= maxX; x++ {
if grid.isScaffoldIntersection(x, y) {
apSum += x * y
}
}
}
return apSum, nil
}
func solveDay17Part2(inFile string) (int64, error) {
rawCode, err := ioutil.ReadFile(inFile)
if err != nil {
return 0, errors.Wrap(err, "Unable to read intcode")
}
code, err := parseIntcode(strings.TrimSpace(string(rawCode)))
if err != nil {
return 0, errors.Wrap(err, "Unable to parse intcode")
}
grid, err := day17ReadGrid(code)
if err != nil {
return 0, errors.Wrap(err, "Unable to read grid")
}
// Get the instructions to follow the whole path
path := grid.findPath()
isMovementFunction := func(n int64) bool {
return n == int64('A') || n == int64('B') || n == int64('C')
}
feedSlice := func(in chan int64, s []int64) {
for i, n := range s {
if isMovementFunction(n) || n == int64('L') || n == int64('R') || n == int64('n') {
in <- n
} else {
for _, c := range strconv.FormatInt(n, 10) {
in <- int64(c)
}
}
if i == len(s)-1 {
in <- 10 // Newline to terminate
} else {
in <- 44 // Comma to delimit chars
}
}
}
findSubmatches := func(haystack, needle []int64) []int64 {
var matches []int64
for i := 0; i < len(haystack)-len(needle)+1; i++ {
if reflect.DeepEqual(haystack[i:i+len(needle)], needle) {
matches = append(matches, int64(i))
i += len(needle) - 1
}
}
return matches
}
var (
pattern [][]int64
main = make([]int64, len(path))
)
copy(main, path)
for len(findSubmatches(main, []int64{int64('L')}))+len(findSubmatches(main, []int64{int64('R')})) > 0 {
var start, length int64 = 0, 2
for isMovementFunction(main[start]) {
start++
}
for len(findSubmatches(main, main[start:start+length])) > 1 && !isMovementFunction(main[start+length-1]) {
length += 2
}
length -= 2 // Revert last addition as it caused trouble
var patternID = int64('A') + int64(len(pattern))
pattern = append(pattern, main[start:start+length])
var (
pos int64
submatchStarts = findSubmatches(main, pattern[len(pattern)-1])
tmpMain []int64
)
for _, smStart := range submatchStarts {
tmpMain = append(tmpMain, main[pos:smStart]...)
tmpMain = append(tmpMain, patternID)
pos = smStart + length
}
tmpMain = append(tmpMain, main[pos:]...)
main = tmpMain
}
if len(pattern) != 3 {
return 0, errors.Errorf("Required more than 3 pattern: %d", len(pattern))
}
// Feed program with new data
var (
in = make(chan int64, 1000) // I could calculate the length but I don't care
out = make(chan int64)
wg = new(sync.WaitGroup)
)
// Feed main movement routine
feedSlice(in, main)
// Feed movement routines
feedSlice(in, pattern[0]) // A
feedSlice(in, pattern[1]) // B
feedSlice(in, pattern[2]) // C
// Answer "continuous video feed" question
feedSlice(in, []int64{int64('n')})
code, err = parseIntcode(strings.TrimSpace(string(rawCode)))
if err != nil {
return 0, errors.Wrap(err, "Unable to parse intcode")
}
// Force the vacuum robot to wake up by changing the value in your
// ASCII program at address 0 from 1 to 2.
code[0] = 2
// Execute the program and throw away all but last output, we know
// how the grid looks
wg.Add(1)
go executeIntcode(code, in, out)
var result int64
go func() {
for o := range out {
result = o
}
wg.Done()
}()
wg.Wait()
return result, nil
}

1
day17_input.txt Normal file
View file

@ -0,0 +1 @@
1,330,331,332,109,4278,1101,0,1182,16,1102,1485,1,24,102,1,0,570,1006,570,36,102,1,571,0,1001,570,-1,570,1001,24,1,24,1105,1,18,1008,571,0,571,1001,16,1,16,1008,16,1485,570,1006,570,14,21102,1,58,0,1105,1,786,1006,332,62,99,21101,0,333,1,21101,0,73,0,1106,0,579,1101,0,0,572,1102,1,0,573,3,574,101,1,573,573,1007,574,65,570,1005,570,151,107,67,574,570,1005,570,151,1001,574,-64,574,1002,574,-1,574,1001,572,1,572,1007,572,11,570,1006,570,165,101,1182,572,127,1002,574,1,0,3,574,101,1,573,573,1008,574,10,570,1005,570,189,1008,574,44,570,1006,570,158,1105,1,81,21102,340,1,1,1105,1,177,21102,477,1,1,1106,0,177,21102,514,1,1,21101,0,176,0,1105,1,579,99,21102,184,1,0,1105,1,579,4,574,104,10,99,1007,573,22,570,1006,570,165,1002,572,1,1182,21101,0,375,1,21102,1,211,0,1105,1,579,21101,1182,11,1,21101,222,0,0,1106,0,979,21102,1,388,1,21101,0,233,0,1105,1,579,21101,1182,22,1,21101,244,0,0,1106,0,979,21101,401,0,1,21102,1,255,0,1105,1,579,21101,1182,33,1,21101,0,266,0,1106,0,979,21101,414,0,1,21101,277,0,0,1106,0,579,3,575,1008,575,89,570,1008,575,121,575,1,575,570,575,3,574,1008,574,10,570,1006,570,291,104,10,21101,0,1182,1,21102,1,313,0,1105,1,622,1005,575,327,1101,0,1,575,21102,1,327,0,1106,0,786,4,438,99,0,1,1,6,77,97,105,110,58,10,33,10,69,120,112,101,99,116,101,100,32,102,117,110,99,116,105,111,110,32,110,97,109,101,32,98,117,116,32,103,111,116,58,32,0,12,70,117,110,99,116,105,111,110,32,65,58,10,12,70,117,110,99,116,105,111,110,32,66,58,10,12,70,117,110,99,116,105,111,110,32,67,58,10,23,67,111,110,116,105,110,117,111,117,115,32,118,105,100,101,111,32,102,101,101,100,63,10,0,37,10,69,120,112,101,99,116,101,100,32,82,44,32,76,44,32,111,114,32,100,105,115,116,97,110,99,101,32,98,117,116,32,103,111,116,58,32,36,10,69,120,112,101,99,116,101,100,32,99,111,109,109,97,32,111,114,32,110,101,119,108,105,110,101,32,98,117,116,32,103,111,116,58,32,43,10,68,101,102,105,110,105,116,105,111,110,115,32,109,97,121,32,98,101,32,97,116,32,109,111,115,116,32,50,48,32,99,104,97,114,97,99,116,101,114,115,33,10,94,62,118,60,0,1,0,-1,-1,0,1,0,0,0,0,0,0,1,24,26,0,109,4,1201,-3,0,587,20102,1,0,-1,22101,1,-3,-3,21102,1,0,-2,2208,-2,-1,570,1005,570,617,2201,-3,-2,609,4,0,21201,-2,1,-2,1105,1,597,109,-4,2105,1,0,109,5,2102,1,-4,629,21001,0,0,-2,22101,1,-4,-4,21101,0,0,-3,2208,-3,-2,570,1005,570,781,2201,-4,-3,653,20102,1,0,-1,1208,-1,-4,570,1005,570,709,1208,-1,-5,570,1005,570,734,1207,-1,0,570,1005,570,759,1206,-1,774,1001,578,562,684,1,0,576,576,1001,578,566,692,1,0,577,577,21102,1,702,0,1106,0,786,21201,-1,-1,-1,1105,1,676,1001,578,1,578,1008,578,4,570,1006,570,724,1001,578,-4,578,21102,731,1,0,1106,0,786,1105,1,774,1001,578,-1,578,1008,578,-1,570,1006,570,749,1001,578,4,578,21101,0,756,0,1106,0,786,1105,1,774,21202,-1,-11,1,22101,1182,1,1,21101,774,0,0,1106,0,622,21201,-3,1,-3,1105,1,640,109,-5,2106,0,0,109,7,1005,575,802,21002,576,1,-6,21001,577,0,-5,1106,0,814,21101,0,0,-1,21102,0,1,-5,21102,1,0,-6,20208,-6,576,-2,208,-5,577,570,22002,570,-2,-2,21202,-5,57,-3,22201,-6,-3,-3,22101,1485,-3,-3,1202,-3,1,843,1005,0,863,21202,-2,42,-4,22101,46,-4,-4,1206,-2,924,21102,1,1,-1,1105,1,924,1205,-2,873,21101,35,0,-4,1105,1,924,2101,0,-3,878,1008,0,1,570,1006,570,916,1001,374,1,374,1202,-3,1,895,1101,0,2,0,2101,0,-3,902,1001,438,0,438,2202,-6,-5,570,1,570,374,570,1,570,438,438,1001,578,558,922,20101,0,0,-4,1006,575,959,204,-4,22101,1,-6,-6,1208,-6,57,570,1006,570,814,104,10,22101,1,-5,-5,1208,-5,49,570,1006,570,810,104,10,1206,-1,974,99,1206,-1,974,1101,1,0,575,21101,973,0,0,1106,0,786,99,109,-7,2105,1,0,109,6,21102,1,0,-4,21102,0,1,-3,203,-2,22101,1,-3,-3,21208,-2,82,-1,1205,-1,1030,21208,-2,76,-1,1205,-1,1037,21207,-2,48,-1,1205,-1,1124,22107,57,-2,-1,1205,-1,1124,21201,-2,-48,-2,1105,1,1041,21102,-4,1,-2,1106,0,1041,21102,1,-5,-2,21201,-4,1,-4,21207,-4,11,-1,1206,-1,1138,2201,-5,-4,1059,1202,-2,1,0,203,-2,22101,1,-3,-3,21207,-2,48,-1,1205,-1,1107,22107,57,-2,-1,1205,-1,1107,21201,-2,-48,-2,2201,-5,-4,1090,20102,10,0,-1,22201,-2,-1,-2,2201,-5,-4,1103,1202,-2,1,0,1106,0,1060,21208,-2,10,-1,1205,-1,1162,21208,-2,44,-1,1206,-1,1131,1105,1,989,21102,1,439,1,1106,0,1150,21102,1,477,1,1105,1,1150,21102,1,514,1,21102,1,1149,0,1106,0,579,99,21101,0,1157,0,1106,0,579,204,-2,104,10,99,21207,-3,22,-1,1206,-1,1138,1202,-5,1,1176,2101,0,-4,0,109,-6,2106,0,0,14,11,46,1,9,1,46,1,9,1,46,1,9,1,46,1,9,1,46,1,9,1,46,1,9,1,46,1,9,1,44,13,44,1,1,1,54,1,1,1,54,1,1,1,54,1,1,9,46,1,9,1,46,1,9,1,46,1,9,1,46,1,9,1,11,13,22,1,9,1,11,1,11,1,22,1,9,1,11,1,9,11,14,1,9,1,11,1,9,1,1,1,7,1,14,9,1,1,11,1,9,1,1,1,7,1,22,1,1,1,11,1,9,1,1,1,7,1,22,1,1,1,11,1,9,1,1,1,7,1,22,1,1,1,11,1,9,1,1,1,7,1,22,1,1,13,9,1,1,1,7,1,22,1,23,1,1,1,7,1,14,13,19,13,12,1,7,1,25,1,7,1,1,1,12,1,7,1,25,9,1,1,12,1,7,1,35,1,12,1,7,1,35,1,12,1,7,1,35,1,8,13,35,1,8,1,3,1,43,14,43,2,7,1,47,2,7,1,37,9,1,2,7,1,37,1,7,1,1,2,7,1,35,14,7,1,35,1,1,1,7,1,2,1,7,1,35,1,1,1,7,1,2,1,7,1,35,1,1,1,7,1,2,1,7,1,35,1,1,1,7,1,2,1,7,1,35,1,1,1,7,1,2,9,35,1,1,1,7,1,46,1,1,1,7,1,46,11,48,1,44,13,10

21
day17_test.go Normal file
View file

@ -0,0 +1,21 @@
package aoc2019
import "testing"
func TestCalculateDay17_Part1(t *testing.T) {
res, err := solveDay17Part1("day17_input.txt")
if err != nil {
t.Fatalf("Day 17 solver failed: %s", err)
}
t.Logf("Solution Day 17 Part 1: %d", res)
}
func TestCalculateDay17_Part2(t *testing.T) {
res, err := solveDay17Part2("day17_input.txt")
if err != nil {
t.Fatalf("Day 17 solver failed: %s", err)
}
t.Logf("Solution Day 17 Part 2: %d", res)
}