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 15

Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
Knut Ahlers 2019-12-15 16:22:50 +01:00
parent a87fb7eaf6
commit 3164135e81
Signed by: luzifer
GPG key ID: DC2729FDD34BE99E
3 changed files with 322 additions and 0 deletions

300
day15.go Normal file
View file

@ -0,0 +1,300 @@
package aoc2019
import (
"context"
"fmt"
"io/ioutil"
"math"
"strings"
"github.com/pkg/errors"
)
type day15TileType int64
const (
day15TileTypeWall day15TileType = iota
day15TileTypeFloor
day15TileTypeOxygen
day15TileTypeUnknown
)
type day15Tile struct {
X, Y int64
Type day15TileType
distFromStart int64
}
func (d day15Tile) key() string { return fmt.Sprintf("%d:%d", d.X, d.Y) }
type day15Grid map[string]*day15Tile
func (d day15Grid) annotateDistance(x, y, dist int64) {
var t = d.getTile(x, y)
if t == nil {
panic("Access to non-existent tile")
}
if t.Type != day15TileTypeFloor && t.Type != day15TileTypeOxygen {
// Do not annotate distance on walls
return
}
if t.distFromStart <= dist {
// Distance already set, no need to set again
return
}
// Set distance
t.distFromStart = dist
// Annotate next fields
d.annotateDistance(x-1, y, dist+1)
d.annotateDistance(x+1, y, dist+1)
d.annotateDistance(x, y-1, dist+1)
d.annotateDistance(x, y+1, dist+1)
}
func (d day15Grid) bounds() (minX, minY, maxX, maxY int64) {
minX = math.MaxInt64
minY = math.MaxInt64
for _, t := range d {
if t.X < minX {
minX = t.X
}
if t.X > maxX {
maxX = t.X
}
if t.Y < minY {
minY = t.Y
}
if t.Y > maxY {
maxY = t.Y
}
}
return
}
func (d day15Grid) getTile(x, y int64) *day15Tile {
if v, ok := d[day15Tile{X: x, Y: y}.key()]; ok {
return v
}
return nil
}
func (d day15Grid) getTileType(x, y int64) day15TileType {
if v := d.getTile(x, y); v != nil {
return v.Type
}
return day15TileTypeUnknown
}
func (d day15Grid) print() {
minX, minY, maxX, maxY := d.bounds()
for y := minY; y <= maxY; y++ {
for x := minX; x <= maxX; x++ {
if x == 0 && y == 0 {
fmt.Printf("@")
continue
}
t := d.getTileType(x, y)
switch t {
case day15TileTypeFloor:
fmt.Printf(".")
case day15TileTypeWall:
fmt.Printf("\u2588")
case day15TileTypeOxygen:
fmt.Printf("X")
case day15TileTypeUnknown:
fmt.Printf("\u2593")
}
}
fmt.Println()
}
}
func day15ScanGrid(code []int64) (day15Grid, error) {
var (
grid = make(day15Grid)
posX, posY int64
rotation int64 = 1 // start facing north
)
recordPosition := func(success bool, tile day15TileType) bool {
var nPosX, nPosY = posX, posY
if success {
defer func() { posX, posY = nPosX, nPosY }()
}
switch rotation {
case 1:
nPosX -= 1
case 2:
nPosX += 1
case 3:
nPosY -= 1
case 4:
nPosY += 1
}
if t := grid.getTileType(nPosX, nPosY); t == tile {
return true
}
nT := day15Tile{X: nPosX, Y: nPosY, Type: tile, distFromStart: math.MaxInt64}
grid[nT.key()] = &nT
return false
}
rotate := func(forward bool) {
var (
fr = []int64{1, 4, 2, 3}
br = []int64{1, 3, 2, 4}
r []int64
)
if forward {
r = fr
} else {
r = br
}
nP := int64IndexOf(r, rotation) + 1
if nP == len(r) {
nP = 0
}
rotation = r[nP]
}
var (
in = make(chan int64)
out = make(chan int64)
)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go executeIntcodeWithParams(intcodeParams{
Code: code,
Context: ctx,
In: in,
Out: out,
})
// Start by moving
in <- rotation
for res := range out {
var alreadyKnown bool
switch day15TileType(res) {
case day15TileTypeWall:
// Ran into wall, not a successful move
alreadyKnown = recordPosition(false, day15TileType(res))
// Rotate once forward
rotate(false)
case day15TileTypeFloor, day15TileTypeOxygen:
// Moved to new tile, successful move
alreadyKnown = recordPosition(true, day15TileType(res))
// Rotate once backward
rotate(true)
default:
// Thefuck?
return grid, errors.Errorf("Invalid tile type detected: %d", res)
}
_ = alreadyKnown
if posX == 0 && posY == 0 {
// We've reached a position twice, let's quit the program but not
// yet the function as the input then will hang
cancel()
}
in <- rotation
}
return grid, nil
}
func int64IndexOf(s []int64, e int64) int {
for i, se := range s {
if se == e {
return i
}
}
return -1
}
func solveDay15Part1(inFile string) (int64, error) {
raw, err := ioutil.ReadFile(inFile)
if err != nil {
return 0, errors.Wrap(err, "Unable to read input")
}
code, err := parseIntcode(strings.TrimSpace(string(raw)))
if err != nil {
return 0, errors.Wrap(err, "Unable to parse Intcode")
}
grid, err := day15ScanGrid(code)
if err != nil {
return 0, errors.Wrap(err, "Unable to scan grid")
}
grid.annotateDistance(0, 0, 0)
var oxygen *day15Tile
for _, t := range grid {
if t.Type == day15TileTypeOxygen {
oxygen = t
break
}
}
return oxygen.distFromStart, nil
}
func solveDay15Part2(inFile string) (int64, error) {
raw, err := ioutil.ReadFile(inFile)
if err != nil {
return 0, errors.Wrap(err, "Unable to read input")
}
code, err := parseIntcode(strings.TrimSpace(string(raw)))
if err != nil {
return 0, errors.Wrap(err, "Unable to parse Intcode")
}
grid, err := day15ScanGrid(code)
if err != nil {
return 0, errors.Wrap(err, "Unable to scan grid")
}
var oxygenSystem *day15Tile
for _, t := range grid {
if t.Type == day15TileTypeOxygen {
oxygenSystem = t
break
}
}
grid.annotateDistance(oxygenSystem.X, oxygenSystem.Y, 0)
var farthestTile = oxygenSystem
for _, t := range grid {
if t.distFromStart > farthestTile.distFromStart && t.Type == day15TileTypeFloor {
farthestTile = t
}
}
return farthestTile.distFromStart, nil
}

1
day15_input.txt Normal file
View file

@ -0,0 +1 @@
3,1033,1008,1033,1,1032,1005,1032,31,1008,1033,2,1032,1005,1032,58,1008,1033,3,1032,1005,1032,81,1008,1033,4,1032,1005,1032,104,99,1002,1034,1,1039,1001,1036,0,1041,1001,1035,-1,1040,1008,1038,0,1043,102,-1,1043,1032,1,1037,1032,1042,1105,1,124,1002,1034,1,1039,1002,1036,1,1041,1001,1035,1,1040,1008,1038,0,1043,1,1037,1038,1042,1105,1,124,1001,1034,-1,1039,1008,1036,0,1041,1002,1035,1,1040,101,0,1038,1043,102,1,1037,1042,1105,1,124,1001,1034,1,1039,1008,1036,0,1041,101,0,1035,1040,1001,1038,0,1043,1002,1037,1,1042,1006,1039,217,1006,1040,217,1008,1039,40,1032,1005,1032,217,1008,1040,40,1032,1005,1032,217,1008,1039,1,1032,1006,1032,165,1008,1040,7,1032,1006,1032,165,1102,2,1,1044,1106,0,224,2,1041,1043,1032,1006,1032,179,1102,1,1,1044,1106,0,224,1,1041,1043,1032,1006,1032,217,1,1042,1043,1032,1001,1032,-1,1032,1002,1032,39,1032,1,1032,1039,1032,101,-1,1032,1032,101,252,1032,211,1007,0,45,1044,1106,0,224,1101,0,0,1044,1105,1,224,1006,1044,247,1002,1039,1,1034,1001,1040,0,1035,1002,1041,1,1036,101,0,1043,1038,1001,1042,0,1037,4,1044,1106,0,0,40,37,39,25,93,75,34,5,82,9,65,6,30,72,37,18,22,87,38,34,43,70,28,24,83,38,94,29,9,33,54,44,6,74,32,32,15,2,35,36,74,83,3,47,32,73,98,2,40,70,80,3,89,6,58,83,15,50,92,82,40,66,2,80,30,66,99,1,63,37,4,81,65,49,51,13,97,43,50,41,33,69,61,44,28,9,85,71,38,38,87,90,59,7,90,17,63,7,42,90,13,34,50,28,67,43,98,67,63,43,71,24,55,16,77,81,17,2,98,94,33,74,2,34,69,31,29,81,84,42,31,7,46,10,17,65,40,84,90,68,42,15,87,27,62,3,19,52,9,77,22,44,24,62,62,38,25,58,35,44,48,1,46,51,43,23,11,95,16,87,81,32,50,28,10,28,89,32,66,71,38,48,12,81,7,73,38,34,38,72,22,70,23,44,67,36,31,54,57,29,10,44,63,66,67,94,31,81,93,34,5,39,89,83,93,35,27,99,3,98,98,28,99,37,55,29,50,24,92,8,75,40,80,12,58,98,41,42,52,95,80,8,71,42,96,4,80,18,53,50,79,35,37,87,39,89,28,9,66,21,74,19,77,79,23,92,36,47,11,67,35,76,28,42,33,90,88,18,7,55,5,75,10,60,17,89,31,80,38,77,41,65,41,98,49,39,77,14,82,24,34,53,27,73,91,86,22,87,83,5,81,36,90,12,30,85,49,83,44,39,58,42,53,5,73,15,67,17,98,35,30,72,81,33,78,7,99,83,18,76,15,36,49,40,66,36,65,9,53,95,21,30,22,85,91,3,28,97,84,31,32,52,14,64,15,4,69,12,56,71,1,11,47,22,29,32,71,30,78,53,23,81,30,44,92,41,42,56,11,16,6,80,29,18,72,66,68,4,36,94,36,20,10,75,79,35,17,62,6,80,46,76,34,96,31,74,11,56,3,18,66,30,73,65,18,99,14,61,7,26,51,11,92,55,29,3,9,89,96,24,67,21,85,7,23,75,71,26,19,43,81,2,89,36,82,23,81,18,60,67,25,56,43,27,77,42,44,86,2,90,23,81,1,41,93,81,40,99,6,66,77,17,95,47,4,44,48,51,78,16,78,51,34,82,3,67,67,27,55,14,36,84,5,79,47,12,31,86,23,54,92,27,71,12,40,58,50,42,78,25,27,89,41,55,59,40,30,55,6,70,9,95,86,51,27,91,15,32,47,79,20,47,90,14,10,49,35,2,96,16,20,68,43,6,2,52,11,71,26,79,88,28,57,31,47,12,26,2,59,30,68,16,34,3,84,43,82,29,61,25,9,55,74,6,9,12,46,16,40,46,90,33,63,57,2,90,68,92,29,55,44,36,25,3,47,29,57,44,12,5,99,95,78,4,9,28,48,5,27,77,39,97,79,39,49,99,40,47,91,13,77,28,51,36,62,25,68,18,6,65,79,65,3,47,53,81,32,95,59,33,84,40,73,59,10,46,57,50,36,44,62,42,48,24,36,63,59,1,31,58,24,29,76,2,40,31,72,47,27,72,42,41,60,4,14,58,99,34,94,44,41,97,35,6,51,10,23,53,80,5,39,16,18,12,91,36,95,51,38,1,49,86,35,71,6,82,15,30,15,92,65,76,81,19,71,32,12,89,40,91,89,2,89,62,67,5,17,54,73,70,16,78,10,55,43,97,78,59,29,95,39,54,80,76,37,95,92,79,16,50,21,80,11,55,13,73,57,60,3,84,4,61,19,63,12,82,22,53,31,63,0,0,21,21,1,10,1,0,0,0,0,0,0

21
day15_test.go Normal file
View file

@ -0,0 +1,21 @@
package aoc2019
import "testing"
func TestCalculateDay15_Part1(t *testing.T) {
count, err := solveDay15Part1("day15_input.txt")
if err != nil {
t.Fatalf("Day 15 solver failed: %s", err)
}
t.Logf("Solution Day 15 Part 1: %d", count)
}
func TestCalculateDay15_Part2(t *testing.T) {
res, err := solveDay15Part2("day15_input.txt")
if err != nil {
t.Fatalf("Day 15 solver failed: %s", err)
}
t.Logf("Solution Day 15 Part 2: %d", res)
}