mirror of
https://github.com/Threnklyn/advent-of-code-go.git
synced 2026-05-18 19:13:27 +02:00
day20 part2 solution. crazy 3D dijkstras BFS traverse to find shortest path
This commit is contained in:
@@ -26,3 +26,4 @@ Day | Name | Type of Algo & Notes
|
|||||||
17 | Set and Forget | More Intcode... <br> Robot walking around a scaffolding... Fairly similar to previous algos, and a 2D grid traversal again <br> - I feel like I cheated part2 by finding the pattern by eye after printing what the 2D grid looks like. Then it was a matter of giving the intcode computer the corresponding inputs (in a weird format). <br> - Good example of __iterative approaches being better than recursive approaches__ because the recursive approach in "continuous video mode" causes a stack overflow very quickly
|
17 | Set and Forget | More Intcode... <br> Robot walking around a scaffolding... Fairly similar to previous algos, and a 2D grid traversal again <br> - I feel like I cheated part2 by finding the pattern by eye after printing what the 2D grid looks like. Then it was a matter of giving the intcode computer the corresponding inputs (in a weird format). <br> - Good example of __iterative approaches being better than recursive approaches__ because the recursive approach in "continuous video mode" causes a stack overflow very quickly
|
||||||
18 | Many-Worlds Interpretation | - Oof, this is the hardest algo so far I think... Combination of __Best Pathfinding Algo__ (going to use a BFS/Dijkstra's) and a weighted __Graph Traversal__ with "locked" edges <br> - I toyed with the idea of using some kind of bitmap to track keys that were found, but ditched it so I wouldn't have to write my own wrapper around bitwise logic... Time complexity comparisons are roughly similar for comparing two maps as comparing two sets of bits, the space complexity is probably much better for 27-bits (@ and a-z) compared to a hashmap, but OH WELL. <br> - The graph traversal required memoizing in order to run in any reasonable amount of time. <br> - My part 2 assumptions would not work for every example which is unfortunate :/ I assumed that within a particular quadrant, if the quadrant does not have the key for a door, just remove the door.
|
18 | Many-Worlds Interpretation | - Oof, this is the hardest algo so far I think... Combination of __Best Pathfinding Algo__ (going to use a BFS/Dijkstra's) and a weighted __Graph Traversal__ with "locked" edges <br> - I toyed with the idea of using some kind of bitmap to track keys that were found, but ditched it so I wouldn't have to write my own wrapper around bitwise logic... Time complexity comparisons are roughly similar for comparing two maps as comparing two sets of bits, the space complexity is probably much better for 27-bits (@ and a-z) compared to a hashmap, but OH WELL. <br> - The graph traversal required memoizing in order to run in any reasonable amount of time. <br> - My part 2 assumptions would not work for every example which is unfortunate :/ I assumed that within a particular quadrant, if the quadrant does not have the key for a door, just remove the door.
|
||||||
19 | Tractor Beam | - Another Intcode outputting to a 2D grid <br> - __THERE NEEDS TO BE A CONVENTION FOR X AND Y WHEN WORKING WITH 2D GRIDS__ This is awful, should it be mathematical? Should it represent rows and columns? Who knows?! <br> - Little geometry (drawing squares) for part2 but nothing crazy
|
19 | Tractor Beam | - Another Intcode outputting to a 2D grid <br> - __THERE NEEDS TO BE A CONVENTION FOR X AND Y WHEN WORKING WITH 2D GRIDS__ This is awful, should it be mathematical? Should it represent rows and columns? Who knows?! <br> - Little geometry (drawing squares) for part2 but nothing crazy
|
||||||
|
20 | Donut Maze | __Breadth first search__ path finding algo. Dijkstra's Algorithm to find the shortest path given a maze with "portals." <br> Part 2 is kind of wild with the maze become 3D, but the solution is effectively the same, just more complex to implement <br> - I remapped this in my head to more of a 3D cube instead of a donut... <br> - That was really cool to get working... Dare I say fun...
|
||||||
|
|||||||
+189
-109
@@ -17,31 +17,39 @@ func main() {
|
|||||||
grid[i] = strings.Split(v, "")
|
grid[i] = strings.Split(v, "")
|
||||||
|
|
||||||
// * Uncomment to print the input
|
// * Uncomment to print the input
|
||||||
fmt.Println(grid[i])
|
// fmt.Println(i-2, grid[i])
|
||||||
}
|
}
|
||||||
|
|
||||||
dijkstra := MakeDijkstraGrid(grid)
|
dijkstra := MakeDijkstraRecursive(grid)
|
||||||
fmt.Println("initial dijkstra queue", dijkstra.queue)
|
|
||||||
|
|
||||||
|
for !dijkstra.handleFrontOfQueue() {
|
||||||
|
// * Uncomment to watch the queue length grow
|
||||||
|
// fmt.Println(" QUEUE LENGTH ", len(dijkstra.queue))
|
||||||
|
}
|
||||||
|
|
||||||
|
finalLayer, finalRow, finalCol := 0, dijkstra.finishCoordinates[0], dijkstra.finishCoordinates[1]
|
||||||
|
|
||||||
|
fmt.Println("Distance to ZZ", dijkstra.layers[finalLayer].grid[finalRow][finalCol].distance)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Dijkstra struct stores the 2D grid of nodes and a queue of next points to check
|
// DijkstraRecursive struct stores the 2D grid of nodes and a queue of next points to check
|
||||||
// and a portal map to add jumps to the queue
|
// and a portal map to add jumps to the queue
|
||||||
type Dijkstra struct {
|
type DijkstraRecursive struct {
|
||||||
rawInput [][]string // stores the raw 2D grid to be passed into a Layer factory
|
sanitizedGrid [][]string // stores the raw 2D grid to be passed into a Layer factory
|
||||||
layers []*Layer
|
layers []*Layer
|
||||||
queue [][3]int
|
queue [][3]int
|
||||||
portalMap map[string][2]int // map to pass to Layer factory
|
outerPortalCoords map[string][2]int // map portal name to coordinates on outer edge
|
||||||
startCoordinates [2]int // coordinates on first layer
|
innerPortalCoords map[string][2]int // map portal name to coordinates on inner edge
|
||||||
finishCoordinates [2]int // coordinates on first layer
|
mapCoordsToPortals map[[2]int]string // arrays pass by value, so this can be used as a key
|
||||||
|
startCoordinates [2]int // coordinates on first layer
|
||||||
|
finishCoordinates [2]int // coordinates on first layer
|
||||||
}
|
}
|
||||||
|
|
||||||
// Layer is a single layer of the 3D maze
|
// Layer is a single layer of the 3D maze
|
||||||
// assuming the maze grows downwards
|
// assuming the maze grows downwards
|
||||||
type Layer struct {
|
type Layer struct {
|
||||||
grid [][]*Node
|
grid [][]*Node
|
||||||
layerIndex int // ground level == 0, one down == 1, two down == 2, etc.
|
layerIndex int // ground level == 0, one down == 1, two down == 2, etc.
|
||||||
portalMap map[string][3]int // jumpLayer, jumpRow, jumpCol
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Node data type is custom built for this algo, i.e. also stores if this is a portal cell
|
// Node data type is custom built for this algo, i.e. also stores if this is a portal cell
|
||||||
@@ -49,66 +57,47 @@ type Node struct {
|
|||||||
value string
|
value string
|
||||||
distance int
|
distance int
|
||||||
portalName string // <portalCharacters><row><col>, will be used to jump to other indexes
|
portalName string // <portalCharacters><row><col>, will be used to jump to other indexes
|
||||||
jumpCoordinates [2]int // coordinates of its paired portal (if applicable)
|
jumpCoordinates [3]int // coordinates of its paired portal if applicable
|
||||||
}
|
}
|
||||||
|
|
||||||
// MakeDijkstraGrid does just that
|
// MakeDijkstraRecursive does just that
|
||||||
func MakeDijkstraGrid(inputGrid [][]string) *Dijkstra {
|
func MakeDijkstraRecursive(inputGrid [][]string) *DijkstraRecursive {
|
||||||
dijkstra := Dijkstra{
|
dijkstra := DijkstraRecursive{
|
||||||
rawInput: inputGrid,
|
sanitizedGrid: make([][]string, len(inputGrid)-4), // preprocess the inputGrid to make future layer creation easier
|
||||||
layers: []*Layer{},
|
layers: []*Layer{},
|
||||||
queue: [][3]int{},
|
queue: make([][3]int, 1),
|
||||||
portalMap: map[string][2]int{},
|
outerPortalCoords: map[string][2]int{},
|
||||||
startCoordinates: [2]int{},
|
innerPortalCoords: map[string][2]int{},
|
||||||
finishCoordinates: [2]int{},
|
mapCoordsToPortals: map[[2]int]string{},
|
||||||
}
|
}
|
||||||
portalMapHelper := make(map[string][2]int)
|
|
||||||
|
|
||||||
grid := make([][]*Node, len(inputGrid)-4)
|
for i := range dijkstra.sanitizedGrid {
|
||||||
// iterate starting at 2,2 to skip the top and left and end len-2 to skip bottom & right
|
dijkstra.sanitizedGrid[i] = inputGrid[i+2][2 : len(inputGrid[0])-2]
|
||||||
|
}
|
||||||
|
|
||||||
|
// populate outer/innerPortalCoords, critical when generating Layers
|
||||||
|
// populating maps of jump coordinates
|
||||||
for row := 2; row < len(inputGrid)-2; row++ {
|
for row := 2; row < len(inputGrid)-2; row++ {
|
||||||
grid[row-2] = make([]*Node, len(inputGrid[0])-4)
|
|
||||||
for col := 2; col < len(inputGrid[0])-2; col++ {
|
for col := 2; col < len(inputGrid[0])-2; col++ {
|
||||||
// make a node for each cell
|
// if a hallway and portalName is not an empty string
|
||||||
switch value := inputGrid[row][col]; value {
|
portalName := getPortalName(inputGrid, row, col)
|
||||||
case "#": // wall
|
|
||||||
grid[row-2][col-2] = &Node{"#", bigSafeInt, "", [2]int{0, 0}}
|
|
||||||
// if this is a hallway node, use a helper function to determine if there this is a portal
|
|
||||||
case ".": // hallway
|
|
||||||
hallwayNode := &Node{
|
|
||||||
value: ".",
|
|
||||||
distance: bigSafeInt,
|
|
||||||
portalName: "",
|
|
||||||
jumpCoordinates: [2]int{0, 0},
|
|
||||||
}
|
|
||||||
portalName := getPortalName(inputGrid, row, col)
|
|
||||||
if len(portalName) != 0 {
|
|
||||||
// assign portal name for this node
|
|
||||||
hallwayNode.portalName = portalName
|
|
||||||
|
|
||||||
// generatine the portal maps for each node is a pain...
|
if inputGrid[row][col] == "." && portalName != "" {
|
||||||
// if this is portal's pair hasn't been found yet (i.e. equal to zero value of [2]int), add it to a map
|
// add to map of coordinates to portal name
|
||||||
if pairedPortal := portalMapHelper[portalName]; pairedPortal == [2]int{0, 0} {
|
dijkstra.mapCoordsToPortals[[2]int{row - 2, col - 2}] = portalName
|
||||||
portalMapHelper[portalName] = [2]int{row - 2, col - 2}
|
|
||||||
} else {
|
// add to outer or inner portal coords maps
|
||||||
// else it has been found, set the jumpCoordinates on this node to pair's coords
|
if onEdgeOfGrid(inputGrid, row-2, col-2) || onEdgeOfGrid(inputGrid, row+2, col+2) {
|
||||||
hallwayNode.jumpCoordinates = pairedPortal
|
dijkstra.outerPortalCoords[portalName] = [2]int{row - 2, col - 2}
|
||||||
// set its pair's jumpCoordinates to this node's coords
|
} else {
|
||||||
grid[pairedPortal[0]][pairedPortal[1]].jumpCoordinates = [2]int{row - 2, col - 2}
|
dijkstra.innerPortalCoords[portalName] = [2]int{row - 2, col - 2}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
grid[row-2][col-2] = hallwayNode
|
|
||||||
// if it is AA, update the distance of this node to zero, initialize queue
|
// Initial and final portal detection
|
||||||
if portalName == "AA" {
|
if portalName == "AA" {
|
||||||
// !! unused
|
|
||||||
dijkstra.startCoordinates = [2]int{row - 2, col - 2}
|
dijkstra.startCoordinates = [2]int{row - 2, col - 2}
|
||||||
|
dijkstra.queue[0] = [3]int{0, row - 2, col - 2}
|
||||||
hallwayNode.distance = 0
|
|
||||||
dijkstra.queue = [][2]int{
|
|
||||||
[2]int{row - 2, col - 2},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// if end portal, set finish coordinates
|
|
||||||
if portalName == "ZZ" {
|
if portalName == "ZZ" {
|
||||||
dijkstra.finishCoordinates = [2]int{row - 2, col - 2}
|
dijkstra.finishCoordinates = [2]int{row - 2, col - 2}
|
||||||
}
|
}
|
||||||
@@ -116,18 +105,144 @@ func MakeDijkstraGrid(inputGrid [][]string) *Dijkstra {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// set grid field
|
// create first layer
|
||||||
dijkstra.grid = grid
|
dijkstra.AddLayer()
|
||||||
|
|
||||||
return &dijkstra
|
return &dijkstra
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddLayer will add a new layer to the dijkstra layers slice
|
// AddLayer will add a new layer to the dijkstra layers slice
|
||||||
//
|
// will be called as out of range layers are jumped to
|
||||||
func (dijkstra *Dijkstra) AddLayer() (layerCount int) {
|
func (dijkstra *DijkstraRecursive) AddLayer() (layerCount int) {
|
||||||
|
sanitizedGrid := dijkstra.sanitizedGrid
|
||||||
|
grid := make([][]*Node, len(sanitizedGrid))
|
||||||
|
layerIndex := len(dijkstra.layers)
|
||||||
|
|
||||||
|
// make copies of the outer/innerPortalMaps
|
||||||
|
innerPortalCoords, outerPortalCoords := map[string][3]int{}, map[string][3]int{}
|
||||||
|
// For all layers copy all outer portal coordinates except for AA and ZZ
|
||||||
|
for key, val := range dijkstra.outerPortalCoords {
|
||||||
|
// if jumping TO an outer portal, that means we're going DOWN a level
|
||||||
|
// so increment the first coordinate
|
||||||
|
outerPortalCoords[key] = [3]int{
|
||||||
|
layerIndex + 1,
|
||||||
|
val[0],
|
||||||
|
val[1],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// disallow jumping to an inner (lower) layer from layer0 because all outer edges are "blocked"
|
||||||
|
if layerIndex != 0 {
|
||||||
|
for key, val := range dijkstra.innerPortalCoords {
|
||||||
|
innerPortalCoords[key] = [3]int{
|
||||||
|
layerIndex - 1,
|
||||||
|
val[0],
|
||||||
|
val[1]}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for row := 0; row < len(sanitizedGrid); row++ {
|
||||||
|
grid[row] = make([]*Node, len(sanitizedGrid[0]))
|
||||||
|
for col := 0; col < len(sanitizedGrid); col++ {
|
||||||
|
switch value := sanitizedGrid[row][col]; value {
|
||||||
|
case "#":
|
||||||
|
grid[row][col] = &Node{"#", bigSafeInt, "", [3]int{0, 0, 0}}
|
||||||
|
case ".":
|
||||||
|
grid[row][col] = &Node{
|
||||||
|
value: ".",
|
||||||
|
distance: bigSafeInt,
|
||||||
|
}
|
||||||
|
// get portal name and jump coord from maps if applicable
|
||||||
|
portalName, found := dijkstra.mapCoordsToPortals[[2]int{row, col}]
|
||||||
|
if found {
|
||||||
|
// ! this may go unused
|
||||||
|
grid[row][col].portalName = portalName
|
||||||
|
|
||||||
|
// determine if inner or outer coordinates are the ones being jumped to
|
||||||
|
if onEdgeOfGrid(sanitizedGrid, row, col) {
|
||||||
|
grid[row][col].jumpCoordinates = innerPortalCoords[portalName]
|
||||||
|
} else {
|
||||||
|
grid[row][col].jumpCoordinates = outerPortalCoords[portalName]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// set initial distance for AA cell
|
||||||
|
if portalName == "AA" && layerIndex == 0 {
|
||||||
|
grid[row][col].distance = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
dijkstra.layers = append(dijkstra.layers, &Layer{grid, layerIndex})
|
||||||
return len(dijkstra.layers)
|
return len(dijkstra.layers)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// dequeues a set of coordinates, enqueues any of its appropriate neighbors (including potential
|
||||||
|
// portals/jumps and adds layers if necessary)
|
||||||
|
// returns true if the queue is empty OR the ZZ portal on layer0 has been reached
|
||||||
|
func (dijkstra *DijkstraRecursive) handleFrontOfQueue() (done bool) {
|
||||||
|
dRow := [4]int{0, 0, -1, 1}
|
||||||
|
dCol := [4]int{-1, 1, 0, 0}
|
||||||
|
|
||||||
|
dequeued := dijkstra.queue[0]
|
||||||
|
layer, row, col := dequeued[0], dequeued[1], dequeued[2]
|
||||||
|
currentNode := dijkstra.layers[layer].grid[row][col]
|
||||||
|
|
||||||
|
// return out of the final node has been found!
|
||||||
|
if currentNode.portalName == "ZZ" && layer == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// add layers on the same layer if they are hallways to traverse into
|
||||||
|
currentLayersGrid := dijkstra.layers[layer].grid
|
||||||
|
for i := 0; i < 4; i++ {
|
||||||
|
nextRow, nextCol := row+dRow[i], col+dCol[i]
|
||||||
|
isInbounds := nextRow >= 0 && nextRow < len(currentLayersGrid) && nextCol >= 0 && nextCol < len(currentLayersGrid[0])
|
||||||
|
if isInbounds {
|
||||||
|
// if the nextNode is a hallway & has not been traveled to yet
|
||||||
|
if nextNode := currentLayersGrid[nextRow][nextCol]; nextNode != nil && nextNode.value == "." && nextNode.distance == bigSafeInt {
|
||||||
|
// update the distance of the nextNode
|
||||||
|
nextNode.distance = currentNode.distance + 1
|
||||||
|
// add its coordinates to the queue, will always be on the same layer b/c this is NOT handling jumps
|
||||||
|
dijkstra.queue = append(dijkstra.queue, [3]int{layer, nextRow, nextCol})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// check if a portal jump is possible!
|
||||||
|
// also check if the jumpCoordinates are the zero value of a [3]int
|
||||||
|
if currentNode.portalName != "" {
|
||||||
|
// find coordinates to jump to and the node itself
|
||||||
|
jumpLayer := currentNode.jumpCoordinates[0]
|
||||||
|
jumpRow := currentNode.jumpCoordinates[1]
|
||||||
|
jumpCol := currentNode.jumpCoordinates[2]
|
||||||
|
|
||||||
|
// if jump is going to be on an out of range layer, fire off an AddLayer
|
||||||
|
if jumpLayer == len(dijkstra.layers) {
|
||||||
|
dijkstra.AddLayer()
|
||||||
|
}
|
||||||
|
|
||||||
|
// update distance of node being jumped to
|
||||||
|
jumpNode := dijkstra.layers[jumpLayer].grid[jumpRow][jumpCol]
|
||||||
|
jumpNode.distance = currentNode.distance + 1
|
||||||
|
|
||||||
|
// add to queue
|
||||||
|
dijkstra.queue = append(dijkstra.queue, currentNode.jumpCoordinates)
|
||||||
|
}
|
||||||
|
|
||||||
|
// dequeue, return true if queue is now empty
|
||||||
|
dijkstra.queue = dijkstra.queue[1:]
|
||||||
|
if len(dijkstra.queue) == 0 {
|
||||||
|
fmt.Println("EMPTY QUEUE")
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
/*************************************************************************************
|
||||||
|
*** SMALL HELPER FUNCTIONS
|
||||||
|
*************************************************************************************/
|
||||||
|
|
||||||
// helper function to run in 4 directions and see if any of them are a capital letter
|
// helper function to run in 4 directions and see if any of them are a capital letter
|
||||||
// if that's true, then grab the portal name in that direction and return it (two char string)
|
// if that's true, then grab the portal name in that direction and return it (two char string)
|
||||||
func getPortalName(grid [][]string, row, col int) string {
|
func getPortalName(grid [][]string, row, col int) string {
|
||||||
@@ -162,49 +277,14 @@ func getPortalName(grid [][]string, row, col int) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns true if the queue is empty OR the ZZ portal has been reached
|
func onEdgeOfGrid(grid [][]string, row, col int) bool {
|
||||||
func (dijkstra *Dijkstra) handleFrontOfQueue() (done bool) {
|
if row == 0 || col == 0 {
|
||||||
// dRow := [4]int{0, 0, -1, 1}
|
return true
|
||||||
// dCol := [4]int{-1, 1, 0, 0}
|
}
|
||||||
|
|
||||||
// row, col := dijkstra.queue[0][0], dijkstra.queue[0][1]
|
if row == len(grid)-1 || col == len(grid[0])-1 {
|
||||||
// currentNode := dijkstra.grid[row][col]
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
// if currentNode.portalName == "ZZ" {
|
return false
|
||||||
// return true
|
|
||||||
// }
|
|
||||||
|
|
||||||
// for i := 0; i < 4; i++ {
|
|
||||||
// nextRow, nextCol := row+dRow[i], col+dCol[i]
|
|
||||||
// isInbounds := nextRow >= 0 && nextRow < len(dijkstra.grid) && nextCol >= 0 && nextCol < len(dijkstra.grid[0])
|
|
||||||
// if isInbounds {
|
|
||||||
// // if the nextNode is a hallway & has not been traveled to yet
|
|
||||||
// if nextNode := dijkstra.grid[nextRow][nextCol]; nextNode != nil && nextNode.value == "." && nextNode.distance == bigSafeInt {
|
|
||||||
// // update the distance of the nextNode
|
|
||||||
// nextNode.distance = currentNode.distance + 1
|
|
||||||
// // add its coordinates to the queue
|
|
||||||
// dijkstra.queue = append(dijkstra.queue, [2]int{nextRow, nextCol})
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // check if a portal jump is possible!
|
|
||||||
// if currentNode.portalName != "" {
|
|
||||||
// // find coordinates to jump to and the node itself
|
|
||||||
// jumpRow := currentNode.jumpCoordinates[0]
|
|
||||||
// jumpCol := currentNode.jumpCoordinates[1]
|
|
||||||
// jumpNode := dijkstra.grid[jumpRow][jumpCol]
|
|
||||||
|
|
||||||
// // update distance
|
|
||||||
// jumpNode.distance = currentNode.distance + 1
|
|
||||||
// // add to queue
|
|
||||||
// dijkstra.queue = append(dijkstra.queue, currentNode.jumpCoordinates)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// // dequeue, return true if queue is now empty
|
|
||||||
// dijkstra.queue = dijkstra.queue[1:]
|
|
||||||
// if len(dijkstra.queue) == 0 {
|
|
||||||
// return true
|
|
||||||
// }
|
|
||||||
// return false
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,3 +72,132 @@ YN......# VT..#....QG
|
|||||||
Here, AA has no direct path to ZZ, but it does connect to AS and CP. By passing through AS, QG, BU, and JO, you can reach ZZ in 58 steps.
|
Here, AA has no direct path to ZZ, but it does connect to AS and CP. By passing through AS, QG, BU, and JO, you can reach ZZ in 58 steps.
|
||||||
|
|
||||||
In your maze, how many steps does it take to get from the open tile marked AA to the open tile marked ZZ?
|
In your maze, how many steps does it take to get from the open tile marked AA to the open tile marked ZZ?
|
||||||
|
|
||||||
|
Your puzzle answer was 600.
|
||||||
|
|
||||||
|
--- Part Two ---
|
||||||
|
Strangely, the exit isn't open when you reach it. Then, you remember: the ancient Plutonians were famous for building recursive spaces.
|
||||||
|
|
||||||
|
The marked connections in the maze aren't portals: they physically connect to a larger or smaller copy of the maze. Specifically, the labeled tiles around the inside edge actually connect to a smaller copy of the same maze, and the smaller copy's inner labeled tiles connect to yet a smaller copy, and so on.
|
||||||
|
|
||||||
|
When you enter the maze, you are at the outermost level; when at the outermost level, only the outer labels AA and ZZ function (as the start and end, respectively); all other outer labeled tiles are effectively walls. At any other level, AA and ZZ count as walls, but the other outer labeled tiles bring you one level outward.
|
||||||
|
|
||||||
|
Your goal is to find a path through the maze that brings you back to ZZ at the outermost level of the maze.
|
||||||
|
|
||||||
|
In the first example above, the shortest path is now the loop around the right side. If the starting level is 0, then taking the previously-shortest path would pass through BC (to level 1), DE (to level 2), and FG (back to level 1). Because this is not the outermost level, ZZ is a wall, and the only option is to go back around to BC, which would only send you even deeper into the recursive maze.
|
||||||
|
|
||||||
|
In the second example above, there is no path that brings you to ZZ at the outermost level.
|
||||||
|
|
||||||
|
Here is a more interesting example:
|
||||||
|
|
||||||
|
Z L X W C
|
||||||
|
Z P Q B K
|
||||||
|
###########.#.#.#.#######.###############
|
||||||
|
#...#.......#.#.......#.#.......#.#.#...#
|
||||||
|
###.#.#.#.#.#.#.#.###.#.#.#######.#.#.###
|
||||||
|
#.#...#.#.#...#.#.#...#...#...#.#.......#
|
||||||
|
#.###.#######.###.###.#.###.###.#.#######
|
||||||
|
#...#.......#.#...#...#.............#...#
|
||||||
|
#.#########.#######.#.#######.#######.###
|
||||||
|
#...#.# F R I Z #.#.#.#
|
||||||
|
#.###.# D E C H #.#.#.#
|
||||||
|
#.#...# #...#.#
|
||||||
|
#.###.# #.###.#
|
||||||
|
#.#....OA WB..#.#..ZH
|
||||||
|
#.###.# #.#.#.#
|
||||||
|
CJ......# #.....#
|
||||||
|
####### #######
|
||||||
|
#.#....CK #......IC
|
||||||
|
#.###.# #.###.#
|
||||||
|
#.....# #...#.#
|
||||||
|
###.### #.#.#.#
|
||||||
|
XF....#.# RF..#.#.#
|
||||||
|
#####.# #######
|
||||||
|
#......CJ NM..#...#
|
||||||
|
###.#.# #.###.#
|
||||||
|
RE....#.# #......RF
|
||||||
|
###.### X X L #.#.#.#
|
||||||
|
#.....# F Q P #.#.#.#
|
||||||
|
###.###########.###.#######.#########.###
|
||||||
|
#.....#...#.....#.......#...#.....#.#...#
|
||||||
|
#####.#.###.#######.#######.###.###.#.#.#
|
||||||
|
#.......#.......#.#.#.#.#...#...#...#.#.#
|
||||||
|
#####.###.#####.#.#.#.#.###.###.#.###.###
|
||||||
|
#.......#.....#.#...#...............#...#
|
||||||
|
#############.#.#.###.###################
|
||||||
|
A O F N
|
||||||
|
A A D M
|
||||||
|
One shortest path through the maze is the following:
|
||||||
|
|
||||||
|
Walk from AA to XF (16 steps)
|
||||||
|
Recurse into level 1 through XF (1 step)
|
||||||
|
Walk from XF to CK (10 steps)
|
||||||
|
Recurse into level 2 through CK (1 step)
|
||||||
|
Walk from CK to ZH (14 steps)
|
||||||
|
Recurse into level 3 through ZH (1 step)
|
||||||
|
Walk from ZH to WB (10 steps)
|
||||||
|
Recurse into level 4 through WB (1 step)
|
||||||
|
Walk from WB to IC (10 steps)
|
||||||
|
Recurse into level 5 through IC (1 step)
|
||||||
|
Walk from IC to RF (10 steps)
|
||||||
|
Recurse into level 6 through RF (1 step)
|
||||||
|
Walk from RF to NM (8 steps)
|
||||||
|
Recurse into level 7 through NM (1 step)
|
||||||
|
Walk from NM to LP (12 steps)
|
||||||
|
Recurse into level 8 through LP (1 step)
|
||||||
|
Walk from LP to FD (24 steps)
|
||||||
|
Recurse into level 9 through FD (1 step)
|
||||||
|
Walk from FD to XQ (8 steps)
|
||||||
|
Recurse into level 10 through XQ (1 step)
|
||||||
|
Walk from XQ to WB (4 steps)
|
||||||
|
Return to level 9 through WB (1 step)
|
||||||
|
Walk from WB to ZH (10 steps)
|
||||||
|
Return to level 8 through ZH (1 step)
|
||||||
|
Walk from ZH to CK (14 steps)
|
||||||
|
Return to level 7 through CK (1 step)
|
||||||
|
Walk from CK to XF (10 steps)
|
||||||
|
Return to level 6 through XF (1 step)
|
||||||
|
Walk from XF to OA (14 steps)
|
||||||
|
Return to level 5 through OA (1 step)
|
||||||
|
Walk from OA to CJ (8 steps)
|
||||||
|
Return to level 4 through CJ (1 step)
|
||||||
|
Walk from CJ to RE (8 steps)
|
||||||
|
Return to level 3 through RE (1 step)
|
||||||
|
Walk from RE to IC (4 steps)
|
||||||
|
Recurse into level 4 through IC (1 step)
|
||||||
|
Walk from IC to RF (10 steps)
|
||||||
|
Recurse into level 5 through RF (1 step)
|
||||||
|
Walk from RF to NM (8 steps)
|
||||||
|
Recurse into level 6 through NM (1 step)
|
||||||
|
Walk from NM to LP (12 steps)
|
||||||
|
Recurse into level 7 through LP (1 step)
|
||||||
|
Walk from LP to FD (24 steps)
|
||||||
|
Recurse into level 8 through FD (1 step)
|
||||||
|
Walk from FD to XQ (8 steps)
|
||||||
|
Recurse into level 9 through XQ (1 step)
|
||||||
|
Walk from XQ to WB (4 steps)
|
||||||
|
Return to level 8 through WB (1 step)
|
||||||
|
Walk from WB to ZH (10 steps)
|
||||||
|
Return to level 7 through ZH (1 step)
|
||||||
|
Walk from ZH to CK (14 steps)
|
||||||
|
Return to level 6 through CK (1 step)
|
||||||
|
Walk from CK to XF (10 steps)
|
||||||
|
Return to level 5 through XF (1 step)
|
||||||
|
Walk from XF to OA (14 steps)
|
||||||
|
Return to level 4 through OA (1 step)
|
||||||
|
Walk from OA to CJ (8 steps)
|
||||||
|
Return to level 3 through CJ (1 step)
|
||||||
|
Walk from CJ to RE (8 steps)
|
||||||
|
Return to level 2 through RE (1 step)
|
||||||
|
Walk from RE to XQ (14 steps)
|
||||||
|
Return to level 1 through XQ (1 step)
|
||||||
|
Walk from XQ to FD (8 steps)
|
||||||
|
Return to level 0 through FD (1 step)
|
||||||
|
Walk from FD to ZZ (18 steps)
|
||||||
|
This path takes a total of 396 steps to move from AA at the outermost layer to ZZ at the outermost layer.
|
||||||
|
|
||||||
|
In your maze, when accounting for recursion, how many steps does it take to get from the open tile marked AA to the open tile marked ZZ, both at the outermost layer?
|
||||||
|
|
||||||
|
Your puzzle answer was 6666.
|
||||||
|
|
||||||
|
Both parts of this puzzle are complete! They provide two gold stars: **
|
||||||
|
|||||||
Reference in New Issue
Block a user