Day 15 part 1
I wanted to use something like a right-hand wall solver, but the fact that you don't know the maze ahead of time and you can't see what something is without trying to move into it made that difficult. This semi-brute-force approach works well enough. I originally stopped as soon as I found the oxygen system and figured out the shortest path, but once I submitted that answer and saw that part 2 wanted the full map explored, I figured I might as well just do it all at once. Part 2 might be able to use the right-hand exploration rule since it has the full map, maybe...possibly a pathfinding/A* type solution, but the problem is finding the "goal" location (furthest point from the oxygen system) itself, so I'm not sure if those will work. My current plan is to either try right-hand wall walking or some sort of breadth-first tree system to plot all distances from the oxygen system, then take the furthest one as the answer. I think I would have been stuck on part 1 longer if my input set didn't happen to find the goal system fairly easily (or maybe my debug drawing helped me work through it with that input set specifically, I'm not sure) since a different input set required some tweaking to the max-visited threshold in order to find things that my first input set found with a lower setting. Regardless, I'm pretty excited that I came to Trémaux's algorithm, more or less, on my own. I went to Wikipedia to see if I was on the right track and lo and behold, I came to a version of it myself.
This commit is contained in:
302
days/15.go
Normal file
302
days/15.go
Normal file
@ -0,0 +1,302 @@
|
||||
package days
|
||||
|
||||
//go:generate stringer -type=cellStatus,responseType,dirType -output=15_types_string.go
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
|
||||
u "parnic.com/aoc2019/utilities"
|
||||
)
|
||||
|
||||
type point u.Pair[int, int]
|
||||
type cellStatus int
|
||||
type responseType int
|
||||
type dirType int
|
||||
|
||||
const (
|
||||
cellStatusUnknown cellStatus = iota
|
||||
cellStatusWall
|
||||
cellStatusOpen
|
||||
cellStatusGoal
|
||||
)
|
||||
|
||||
const (
|
||||
responseWall responseType = iota
|
||||
responseSuccess
|
||||
responseFoundGoal
|
||||
)
|
||||
|
||||
const maxVisited = 3
|
||||
|
||||
const (
|
||||
dirNorth dirType = iota + 1
|
||||
dirSouth
|
||||
dirWest
|
||||
dirEast
|
||||
|
||||
dirFirst = dirNorth
|
||||
dirLast = dirEast
|
||||
)
|
||||
|
||||
var dirOrders = [][]dirType{
|
||||
{dirNorth, dirSouth, dirWest, dirEast},
|
||||
{dirSouth, dirWest, dirEast, dirNorth},
|
||||
{dirWest, dirEast, dirNorth, dirSouth},
|
||||
{dirEast, dirNorth, dirSouth, dirWest},
|
||||
}
|
||||
|
||||
// turned out to be unnecessary on multiple datasets i tried. increases the iterations 6x
|
||||
// var dirOrders = u.GetPermutations(dirNorth, dirSouth, dirWest, dirEast)
|
||||
|
||||
type visitedStatus struct {
|
||||
timesVisited int
|
||||
distanceFromStart int
|
||||
}
|
||||
|
||||
type Day15 struct {
|
||||
program u.IntcodeProgram
|
||||
grid map[point]cellStatus
|
||||
visited map[point]*visitedStatus
|
||||
shortestPath []point
|
||||
pos point
|
||||
goalPos point
|
||||
}
|
||||
|
||||
func (d *Day15) Parse() {
|
||||
d.program = u.LoadIntcodeProgram("15p")
|
||||
d.grid = map[point]cellStatus{
|
||||
{First: 0, Second: 0}: cellStatusOpen,
|
||||
}
|
||||
d.visited = map[point]*visitedStatus{
|
||||
{First: 0, Second: 0}: {timesVisited: 1},
|
||||
}
|
||||
d.shortestPath = []point{{}}
|
||||
}
|
||||
|
||||
func (d Day15) Num() int {
|
||||
return 15
|
||||
}
|
||||
|
||||
func (d Day15) getPointInDirection(pos point, dir dirType) point {
|
||||
target := pos
|
||||
switch dir {
|
||||
case dirNorth:
|
||||
target.First--
|
||||
case dirSouth:
|
||||
target.First++
|
||||
case dirWest:
|
||||
target.Second--
|
||||
case dirEast:
|
||||
target.Second++
|
||||
}
|
||||
|
||||
return target
|
||||
}
|
||||
|
||||
func (d Day15) getCellTypeInDirection(pos point, dir dirType) (cellStatus, point) {
|
||||
target := d.getPointInDirection(pos, dir)
|
||||
return d.grid[target], target
|
||||
}
|
||||
|
||||
func (d Day15) getDirToNextCellType(pos point, t cellStatus, maxNumVisited int, dirs []dirType) (dirType, point, error) {
|
||||
for _, dir := range dirs {
|
||||
cellInDirection, targetCell := d.getCellTypeInDirection(pos, dir)
|
||||
if cellInDirection == t {
|
||||
_, visitedTargetExists := d.visited[targetCell]
|
||||
foundUnknown := t == cellStatusUnknown && !visitedTargetExists
|
||||
foundOther := t != cellStatusUnknown && visitedTargetExists && d.visited[targetCell].timesVisited <= maxNumVisited
|
||||
if foundUnknown || foundOther {
|
||||
return dir, targetCell, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return dirFirst, point{}, fmt.Errorf("no %v tiles around %v", t, pos)
|
||||
}
|
||||
|
||||
func (d *Day15) Draw() {
|
||||
min := point{First: math.MaxInt, Second: math.MaxInt}
|
||||
max := point{First: math.MinInt, Second: math.MinInt}
|
||||
for p := range d.grid {
|
||||
if p.First < min.First {
|
||||
min.First = p.First
|
||||
}
|
||||
if p.First > max.First {
|
||||
max.First = p.First
|
||||
}
|
||||
if p.Second < min.Second {
|
||||
min.Second = p.Second
|
||||
}
|
||||
if p.Second > max.Second {
|
||||
max.Second = p.Second
|
||||
}
|
||||
}
|
||||
|
||||
for x := min.First; x <= max.First; x++ {
|
||||
for y := min.Second; y <= max.Second; y++ {
|
||||
p := point{First: x, Second: y}
|
||||
switch d.grid[p] {
|
||||
case cellStatusGoal:
|
||||
fmt.Printf("%s@%s", u.ColorBrightGreen, u.TextReset)
|
||||
case cellStatusOpen:
|
||||
if p == d.pos {
|
||||
fmt.Print(u.BackgroundBrightRed)
|
||||
} else if x == 0 && y == 0 {
|
||||
fmt.Print(u.BackgroundYellow)
|
||||
} else if u.ArrayContains(d.shortestPath, p) {
|
||||
fmt.Print(u.BackgroundGreen)
|
||||
} else if d.visited[p] != nil && d.visited[p].timesVisited > maxVisited {
|
||||
fmt.Print(u.ColorYellow)
|
||||
fmt.Print(u.BackgroundBlack)
|
||||
} else if d.visited[p] != nil && d.visited[p].timesVisited > 1 {
|
||||
fmt.Print(u.BackgroundMagenta)
|
||||
} else {
|
||||
fmt.Print(u.BackgroundBlue)
|
||||
}
|
||||
fmt.Printf(".%s", u.TextReset)
|
||||
case cellStatusWall:
|
||||
fmt.Print("█")
|
||||
case cellStatusUnknown:
|
||||
fmt.Print(" ")
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Day15) markShortestPath() {
|
||||
pos := d.goalPos
|
||||
checkOffsets := []point{
|
||||
{First: -1, Second: 0},
|
||||
{First: 1, Second: 0},
|
||||
{First: 0, Second: -1},
|
||||
{First: 0, Second: 1},
|
||||
}
|
||||
|
||||
checkPt := func(pt point) (bool, int) {
|
||||
if v, exists := d.visited[pt]; exists && d.grid[pt] == cellStatusOpen {
|
||||
return true, v.distanceFromStart
|
||||
}
|
||||
return false, math.MaxInt
|
||||
}
|
||||
|
||||
d.shortestPath = []point{d.goalPos}
|
||||
|
||||
for pos.First != 0 || pos.Second != 0 {
|
||||
lowestDist := math.MaxInt
|
||||
lowestPoint := point{}
|
||||
for _, pt := range checkOffsets {
|
||||
newPt := point{First: pos.First + pt.First, Second: pos.Second + pt.Second}
|
||||
if found, dist := checkPt(newPt); found && dist < lowestDist {
|
||||
lowestDist = dist
|
||||
lowestPoint = newPt
|
||||
}
|
||||
}
|
||||
|
||||
d.shortestPath = append(d.shortestPath, lowestPoint)
|
||||
pos = lowestPoint
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Day15) exploreFullMap() map[point]*visitedStatus {
|
||||
grids := make([]map[point]cellStatus, 0, len(dirOrders))
|
||||
goalVisited := d.visited
|
||||
|
||||
for _, dirOrder := range dirOrders {
|
||||
d.program.Reset()
|
||||
|
||||
targetPos := point{}
|
||||
nextDir := dirFirst
|
||||
distFromStart := 0
|
||||
|
||||
d.pos = point{}
|
||||
d.visited = map[point]*visitedStatus{
|
||||
{First: 0, Second: 0}: {timesVisited: 1},
|
||||
}
|
||||
d.grid = map[point]cellStatus{
|
||||
{First: 0, Second: 0}: cellStatusOpen,
|
||||
}
|
||||
d.program.RunIn(func(inputStep int) int64 {
|
||||
var err error
|
||||
nextDir, targetPos, err = d.getDirToNextCellType(d.pos, cellStatusUnknown, 0, dirOrder)
|
||||
if err != nil {
|
||||
// ensure we never try to go back into the trapped spot
|
||||
d.visited[d.pos].timesVisited = maxVisited + 1
|
||||
for x := 1; x <= maxVisited && err != nil; x++ {
|
||||
nextDir, targetPos, err = d.getDirToNextCellType(d.pos, cellStatusOpen, x, dirOrder)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
// d.Draw()
|
||||
// panic(err)
|
||||
d.program.Stop()
|
||||
}
|
||||
|
||||
return int64(nextDir)
|
||||
}, func(val int64, state u.IntcodeProgramState) {
|
||||
rVal := responseType(val)
|
||||
|
||||
p := d.getPointInDirection(d.pos, nextDir)
|
||||
shouldMove := true
|
||||
switch rVal {
|
||||
case responseWall:
|
||||
d.grid[p] = cellStatusWall
|
||||
shouldMove = false
|
||||
case responseSuccess:
|
||||
d.grid[p] = cellStatusOpen
|
||||
case responseFoundGoal:
|
||||
d.grid[p] = cellStatusGoal
|
||||
}
|
||||
|
||||
if shouldMove {
|
||||
d.pos = targetPos
|
||||
if d.visited[d.pos] == nil {
|
||||
d.visited[d.pos] = &visitedStatus{}
|
||||
distFromStart++
|
||||
} else {
|
||||
distFromStart--
|
||||
}
|
||||
d.visited[d.pos].timesVisited++
|
||||
d.visited[d.pos].distanceFromStart = distFromStart
|
||||
}
|
||||
|
||||
if rVal == responseFoundGoal {
|
||||
// d.Draw()
|
||||
d.goalPos = targetPos
|
||||
goalVisited = d.visited
|
||||
}
|
||||
})
|
||||
|
||||
grids = append(grids, d.grid)
|
||||
}
|
||||
|
||||
d.grid = map[point]cellStatus{
|
||||
{First: 0, Second: 0}: cellStatusOpen,
|
||||
}
|
||||
for _, grid := range grids {
|
||||
keys := u.MapKeys(grid)
|
||||
for _, key := range keys {
|
||||
d.grid[key] = grid[key]
|
||||
}
|
||||
}
|
||||
|
||||
return goalVisited
|
||||
}
|
||||
|
||||
func (d *Day15) Part1() string {
|
||||
d.visited = d.exploreFullMap()
|
||||
d.markShortestPath()
|
||||
|
||||
for _, visited := range d.visited {
|
||||
visited.timesVisited = 1
|
||||
}
|
||||
d.pos = point{}
|
||||
d.Draw()
|
||||
|
||||
return fmt.Sprintf("Moves required to reach target: %s%d%s", u.TextBold, d.visited[d.goalPos].distanceFromStart, u.TextReset)
|
||||
}
|
||||
|
||||
func (d *Day15) Part2() string {
|
||||
return fmt.Sprintf("%s%d%s", u.TextBold, 0, u.TextReset)
|
||||
}
|
66
days/15_types_string.go
Normal file
66
days/15_types_string.go
Normal file
@ -0,0 +1,66 @@
|
||||
// Code generated by "stringer -type=cellStatus,responseType,dirType -output=15_types_string.go"; DO NOT EDIT.
|
||||
|
||||
package days
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[cellStatusUnknown-0]
|
||||
_ = x[cellStatusWall-1]
|
||||
_ = x[cellStatusOpen-2]
|
||||
_ = x[cellStatusGoal-3]
|
||||
}
|
||||
|
||||
const _cellStatus_name = "cellStatusUnknowncellStatusWallcellStatusOpencellStatusGoal"
|
||||
|
||||
var _cellStatus_index = [...]uint8{0, 17, 31, 45, 59}
|
||||
|
||||
func (i cellStatus) String() string {
|
||||
if i < 0 || i >= cellStatus(len(_cellStatus_index)-1) {
|
||||
return "cellStatus(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _cellStatus_name[_cellStatus_index[i]:_cellStatus_index[i+1]]
|
||||
}
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[responseWall-0]
|
||||
_ = x[responseSuccess-1]
|
||||
_ = x[responseFoundGoal-2]
|
||||
}
|
||||
|
||||
const _responseType_name = "responseWallresponseSuccessresponseFoundGoal"
|
||||
|
||||
var _responseType_index = [...]uint8{0, 12, 27, 44}
|
||||
|
||||
func (i responseType) String() string {
|
||||
if i < 0 || i >= responseType(len(_responseType_index)-1) {
|
||||
return "responseType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _responseType_name[_responseType_index[i]:_responseType_index[i+1]]
|
||||
}
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[dirNorth-1]
|
||||
_ = x[dirSouth-2]
|
||||
_ = x[dirWest-3]
|
||||
_ = x[dirEast-4]
|
||||
}
|
||||
|
||||
const _dirType_name = "dirNorthdirSouthdirWestdirEast"
|
||||
|
||||
var _dirType_index = [...]uint8{0, 8, 16, 23, 30}
|
||||
|
||||
func (i dirType) String() string {
|
||||
i -= 1
|
||||
if i < 0 || i >= dirType(len(_dirType_index)-1) {
|
||||
return "dirType(" + strconv.FormatInt(int64(i+1), 10) + ")"
|
||||
}
|
||||
return _dirType_name[_dirType_index[i]:_dirType_index[i+1]]
|
||||
}
|
1
inputs/15p.txt
Normal file
1
inputs/15p.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,102,1,1034,1039,1001,1036,0,1041,1001,1035,1,1040,1008,1038,0,1043,1,1037,1038,1042,1106,0,124,1001,1034,-1,1039,1008,1036,0,1041,1002,1035,1,1040,1002,1038,1,1043,101,0,1037,1042,1106,0,124,1001,1034,1,1039,1008,1036,0,1041,101,0,1035,1040,1002,1038,1,1043,101,0,1037,1042,1006,1039,217,1006,1040,217,1008,1039,40,1032,1005,1032,217,1008,1040,40,1032,1005,1032,217,1008,1039,37,1032,1006,1032,165,1008,1040,9,1032,1006,1032,165,1101,2,0,1044,1105,1,224,2,1041,1043,1032,1006,1032,179,1101,1,0,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,50,1044,1106,0,224,1102,0,1,1044,1105,1,224,1006,1044,247,1001,1039,0,1034,102,1,1040,1035,102,1,1041,1036,101,0,1043,1038,102,1,1042,1037,4,1044,1106,0,0,37,22,74,27,37,99,30,8,72,31,49,29,51,32,85,21,39,72,2,2,43,94,31,11,76,43,95,21,38,8,90,13,39,97,54,47,14,6,20,49,5,30,97,9,99,64,71,24,36,87,52,94,36,18,52,42,83,38,98,53,26,87,69,32,18,94,2,93,97,15,65,65,21,40,99,19,91,13,4,89,38,70,65,41,73,49,62,54,37,46,14,49,88,86,13,89,23,89,10,3,48,57,92,43,65,4,35,97,48,10,19,64,3,79,38,87,6,13,71,49,74,43,92,8,4,71,6,35,85,98,94,6,38,59,80,65,46,62,63,62,49,61,68,6,7,64,66,40,56,82,59,30,85,45,57,36,86,70,25,83,31,96,65,19,16,67,55,36,49,54,29,75,69,3,3,37,75,49,23,65,22,6,52,75,31,7,87,85,19,48,97,65,51,78,10,35,40,59,54,14,85,6,30,94,68,42,87,46,75,26,82,36,21,65,90,16,59,14,76,55,37,41,99,80,9,79,12,59,17,75,2,40,52,45,76,45,16,82,13,55,61,14,11,49,97,81,99,38,35,20,98,51,64,13,24,85,94,38,25,87,1,42,89,18,32,54,55,17,15,84,98,25,31,21,55,44,57,59,11,78,49,72,87,20,7,33,91,80,75,18,33,37,52,7,26,87,65,36,52,92,6,8,95,89,37,38,57,25,23,71,75,47,20,87,90,37,54,38,77,32,39,67,16,69,62,15,96,47,91,95,18,96,24,45,21,64,9,72,2,54,65,39,36,54,23,71,74,18,26,97,35,44,29,87,54,48,31,55,33,85,74,13,99,82,39,35,97,43,20,62,58,86,98,41,47,92,79,74,10,85,28,66,86,18,35,5,84,67,13,91,47,44,1,84,56,32,96,7,77,21,88,92,38,31,65,82,87,45,55,4,60,58,64,49,53,3,63,32,52,43,10,66,75,96,53,11,95,44,36,16,65,91,47,32,9,3,73,29,25,93,29,18,88,45,41,46,12,94,13,89,5,36,94,88,33,10,10,2,52,90,19,63,26,84,12,76,16,42,75,63,39,32,72,72,84,70,2,63,33,74,43,68,38,84,72,44,89,18,24,78,69,4,80,41,54,75,72,4,16,91,5,48,30,64,38,4,52,38,30,95,99,32,38,52,35,58,71,38,89,86,25,84,88,41,39,32,56,79,12,52,19,80,46,66,38,32,69,67,6,87,88,36,59,51,5,33,46,45,82,15,57,80,91,12,86,29,34,15,61,19,73,46,82,60,73,13,52,36,67,3,49,87,39,12,98,58,87,32,82,47,65,6,87,71,13,17,65,69,14,34,42,82,42,1,77,63,10,63,28,90,24,13,99,19,38,68,62,44,2,65,81,95,7,54,24,58,16,58,48,95,9,80,9,51,73,23,96,49,64,58,1,6,72,69,39,2,10,63,36,9,85,59,90,41,2,72,77,23,23,80,75,33,6,20,18,59,39,36,89,35,89,42,42,22,37,24,30,51,53,43,78,48,27,76,84,22,81,72,25,95,28,15,51,58,48,7,1,90,72,19,37,52,60,39,81,20,70,6,39,82,26,77,14,96,52,30,84,33,66,80,5,52,15,72,46,55,2,21,8,97,79,43,8,91,27,67,5,18,74,71,34,51,6,83,25,52,92,5,15,85,11,72,33,85,30,59,6,84,29,51,77,99,43,95,44,83,95,89,27,54,16,85,90,82,34,98,59,87,12,73,25,74,29,95,82,51,5,81,46,51,0,0,21,21,1,10,1,0,0,0,0,0,0
|
Reference in New Issue
Block a user