mirror of
https://github.com/Luzifer/aoc2019.git
synced 2024-12-21 21:41:16 +00:00
Add solution for Day 15
Signed-off-by: Knut Ahlers <knut@ahlers.me>
This commit is contained in:
parent
a87fb7eaf6
commit
3164135e81
3 changed files with 322 additions and 0 deletions
300
day15.go
Normal file
300
day15.go
Normal 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
1
day15_input.txt
Normal 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
21
day15_test.go
Normal 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)
|
||||
}
|
Loading…
Reference in a new issue