diff --git a/days/20.go b/days/20.go index 1342463..3fd2aa9 100644 --- a/days/20.go +++ b/days/20.go @@ -10,7 +10,7 @@ import ( ) type day20Cell int8 -type day20Graph map[string][]u.Pair[string, int] +type day20Graph map[day20Portal][]u.Pair[day20Portal, int] const ( day20CellWall day20Cell = iota @@ -25,21 +25,30 @@ var ( {X: 0, Y: -1}, {X: 0, Y: 1}, } + + entrancePortal = day20Portal{name: "AA"} + exitPortal = day20Portal{name: "ZZ"} ) +type day20Portal struct { + name string + inner bool + depth int +} + type Day20 struct { grid [][]day20Cell entrance u.Vec2i exit u.Vec2i - portals map[string]u.Pair[u.Vec2i, u.Vec2i] + portals map[day20Portal]u.Vec2i portalNames []string } func (d *Day20) Parse() { - d.portals = make(map[string]u.Pair[u.Vec2i, u.Vec2i]) + d.portals = make(map[day20Portal]u.Vec2i) d.portalNames = make([]string, 0) - lines := u.GetStringLines("20s1") + lines := u.GetStringLines("20p") d.grid = make([][]day20Cell, len(lines)-4) currPortal := strings.Builder{} @@ -97,25 +106,23 @@ func (d *Day20) Parse() { } if currPortal.Len() == 2 { - portal := currPortal.String() + portalName := currPortal.String() portalVec := u.Vec2i{X: portalX, Y: portalY} - if portal == "AA" { + if portalName == entrancePortal.name { d.entrance = portalVec - } else if portal == "ZZ" { + } else if portalName == exitPortal.name { d.exit = portalVec } else { - pair, exists := d.portals[portal] - if exists { - d.portals[portal] = u.Pair[u.Vec2i, u.Vec2i]{First: pair.First, Second: portalVec} - } else { - d.portals[portal] = u.Pair[u.Vec2i, u.Vec2i]{First: portalVec, Second: portalVec} + portal := day20Portal{ + name: portalName, + inner: !d.isOuterPortal(portalVec), } + d.portals[portal] = portalVec - u.AddToArray(&d.portalNames, portal) + u.AddToArray(&d.portalNames, portalName) } - // fmt.Println("full portal:", currPortal.String(), "at", portalX, ",", portalY) currPortal.Reset() } } @@ -133,8 +140,8 @@ func (d Day20) isPortal(vec u.Vec2i) (bool, int) { } for i, name := range d.portalNames { - v := d.portals[name] - if v.First == vec || v.Second == vec { + p, exists := d.portals[day20Portal{name: name, inner: !d.isOuterPortal(vec)}] + if exists && vec == p { return true, i } } @@ -175,17 +182,21 @@ func (d Day20) Draw() { } } -func (d Day20) findAdjacentCells(inPos u.Vec2i, grid [][]day20Cell) []u.Pair[string, int] { - found := make([]u.Pair[string, int], 0) +func (d Day20) isOuterPortal(pos u.Vec2i) bool { + return pos.X == 0 || pos.Y == 0 || pos.X == len(d.grid[0])-1 || pos.Y == len(d.grid)-1 +} + +func (d Day20) findAdjacentCells(inPos u.Vec2i) []u.Pair[day20Portal, int] { + found := make([]u.Pair[day20Portal, int], 0) getAdjacent := func(pos u.Vec2i) []u.Vec2i { retAdjacent := make([]u.Vec2i, 0, len(day20AdjacentOffsets)) for _, off := range day20AdjacentOffsets { offVec := u.Vec2i{X: pos.X + off.X, Y: pos.Y + off.Y} - if offVec.Y < 0 || offVec.Y >= len(grid) || offVec.X < 0 || offVec.X >= len(grid[0]) { + if offVec.Y < 0 || offVec.Y >= len(d.grid) || offVec.X < 0 || offVec.X >= len(d.grid[0]) { continue } - if grid[offVec.Y][offVec.X] != day20CellPath { + if d.grid[offVec.Y][offVec.X] != day20CellPath { continue } retAdjacent = append(retAdjacent, offVec) @@ -213,22 +224,25 @@ func (d Day20) findAdjacentCells(inPos u.Vec2i, grid [][]day20Cell) []u.Pair[str if adjacentIsPortal || next.Second == d.entrance || next.Second == d.exit { var portalName string if next.Second == d.entrance { - portalName = "AA" + portalName = entrancePortal.name } else if next.Second == d.exit { - portalName = "ZZ" + portalName = exitPortal.name } else { portalName = d.portalNames[portalIdx] } alreadyFound := false for _, p := range found { - if p.First == portalName { + if p.First.name == portalName { alreadyFound = true break } } if !alreadyFound { - found = append(found, u.Pair[string, int]{First: portalName, Second: next.First}) + found = append(found, u.Pair[day20Portal, int]{First: day20Portal{ + name: portalName, + inner: !d.isOuterPortal(next.Second), + }, Second: next.First}) continue } } @@ -246,7 +260,7 @@ func (d Day20) findAdjacentCells(inPos u.Vec2i, grid [][]day20Cell) []u.Pair[str type day20PriorityQueue struct { distance int - neighbor string + neighbor day20Portal } type day20PriorityQueueHeap []day20PriorityQueue @@ -266,8 +280,8 @@ func (h *day20PriorityQueueHeap) Pop() any { return x } -func (d Day20) dijkstra(graph day20Graph, start, end string) int { - distance := make(map[string]int) +func (d Day20) dijkstra(graph day20Graph, start, end day20Portal, neighborFunc func(inPortal day20Portal) []u.Pair[day20Portal, int]) int { + distance := make(map[day20Portal]int) ih := day20PriorityQueueHeap{ day20PriorityQueue{ @@ -277,13 +291,13 @@ func (d Day20) dijkstra(graph day20Graph, start, end string) int { } heap.Init(&ih) - visited := make(map[string]bool) + visited := make(map[day20Portal]bool) for ih.Len() > 0 { node := heap.Pop(&ih).(day20PriorityQueue) if node.neighbor == end { - return node.distance - 1 + return node.distance } if _, exists := visited[node.neighbor]; exists { @@ -292,7 +306,7 @@ func (d Day20) dijkstra(graph day20Graph, start, end string) int { visited[node.neighbor] = true - for _, p := range graph[node.neighbor] { + for _, p := range neighborFunc(node.neighbor) { if _, exists := visited[p.First]; exists { continue } @@ -301,7 +315,7 @@ func (d Day20) dijkstra(graph day20Graph, start, end string) int { if dist, exists := distance[p.First]; !exists || newDistance < dist { distance[p.First] = newDistance heap.Push(&ih, day20PriorityQueue{ - distance: newDistance + 1, + distance: newDistance, neighbor: p.First, }) } @@ -312,30 +326,73 @@ func (d Day20) dijkstra(graph day20Graph, start, end string) int { } func (d Day20) buildGraph() day20Graph { - graph := make(day20Graph) + graph := make(day20Graph, len(d.portals)+1) - adjacent := d.findAdjacentCells(d.entrance, d.grid) - graph["AA"] = adjacent + adjacent := d.findAdjacentCells(d.entrance) + graph[entrancePortal] = adjacent - for _, portalName := range d.portalNames { - portal := d.portals[portalName] - adjacent = d.findAdjacentCells(portal.First, d.grid) - graph[portalName] = adjacent - adjacent = d.findAdjacentCells(portal.Second, d.grid) - graph[portalName] = append(graph[portalName], adjacent...) + for portal, portalVec := range d.portals { + adjacent = d.findAdjacentCells(portalVec) + graph[portal] = adjacent } return graph } +func (d Day20) getDepthNeighbors(graph day20Graph, portal day20Portal) []u.Pair[day20Portal, int] { + basePortal := portal + basePortal.depth = 0 + baseNeighbors := graph[basePortal] + + neighbors := make([]u.Pair[day20Portal, int], 0) + + if portal.inner { + n := day20Portal{name: portal.name, inner: false, depth: portal.depth + 1} + neighbors = append(neighbors, u.Pair[day20Portal, int]{First: n, Second: 1}) + } + + if portal.depth == 0 { + for _, i := range baseNeighbors { + if i.First.inner || i.First.name == entrancePortal.name || i.First.name == exitPortal.name { + neighbors = append(neighbors, u.Pair[day20Portal, int]{First: i.First, Second: i.Second}) + } + } + } else { + if !portal.inner { + n := day20Portal{name: portal.name, inner: true, depth: portal.depth - 1} + neighbors = append(neighbors, u.Pair[day20Portal, int]{First: n, Second: 1}) + } + + for _, i := range baseNeighbors { + if i.First.name != entrancePortal.name && i.First.name != exitPortal.name { + n := day20Portal{name: i.First.name, inner: i.First.inner, depth: portal.depth} + neighbors = append(neighbors, u.Pair[day20Portal, int]{First: n, Second: i.Second}) + } + } + } + + return neighbors +} + func (d *Day20) Part1() string { // d.Draw() graph := d.buildGraph() - distance := d.dijkstra(graph, "AA", "ZZ") - return fmt.Sprintf("%d %s%d%s", distance, u.TextBold, len(graph), u.TextReset) + + for portal, adjacent := range graph { + if portal.name == entrancePortal.name { + continue + } + + graph[portal] = append(adjacent, u.Pair[day20Portal, int]{First: day20Portal{name: portal.name, inner: !portal.inner}, Second: 1}) + } + + distance := d.dijkstra(graph, entrancePortal, exitPortal, func(inPortal day20Portal) []u.Pair[day20Portal, int] { return graph[inPortal] }) + return fmt.Sprintf("Steps to traverse maze: %s%d%s", u.TextBold, distance, u.TextReset) } func (d *Day20) Part2() string { - return fmt.Sprintf("%s%d%s", u.TextBold, 0, u.TextReset) + graph := d.buildGraph() + distance := d.dijkstra(graph, entrancePortal, exitPortal, func(inPortal day20Portal) []u.Pair[day20Portal, int] { return d.getDepthNeighbors(graph, inPortal) }) + return fmt.Sprintf("Steps to traverse recursive maze: %s%d%s", u.TextBold, distance, u.TextReset) }