aoc24/day16/main.go

215 lines
8.5 KiB
Go
Raw Permalink Normal View History

2024-12-16 21:10:13 +00:00
package main
2024-12-17 15:50:48 +00:00
// Look at https://topaz.github.io/paste/#XQAAAQB1CAAAAAAAAAA4GEiZzRd1JAgz+whYRQxSFI7XvmlfhtGDinecZE7XVglOhhPLih7tF+1f6vGBXHx2f7N/fIfjMG8zy2b5K1bcEP2Fo6yufIOrK2Xv9CFqxAiFcqXF+B4dWDdaic6Mhmi96bCxTu3B1MuJ8XPiKzAsC1j5U5qhN1K9v/wrCnRnSs483gy4bYiKZacHfPN6AsZrOcqsgagmb5XZWYgYt6yR6lJ7yx0goiNLILnmpT8E9yBh5igU8VCxKT8lpj05Ce5ffUlcz4mCgQC5jenYOl86fCFPgFC05AvKYVOmGpjW8syIdhCLxhQJnhNsljM5wHIUezS9qwRsjXZTfItq30EnkIM9mdSPZY2/eGBlasPQi5LR+l5JFw9w3W3fesXYEB2u9yc/umujdgTI4RikSMdOfdYOrgCLuPFA72fHU3tGk2oyL5k58ZAR1SKFm+7XExv3gFPh6t+mrFjJ9QZ7qEb19gn0kMt2MaSiNLzyB8cJ9Dr3yxTGqfBOPsJPSI2jM2H1xgg1fu7gBZAYu5+KQ1CyctZ9mFzm25QxvrNyLFoNTPKn9ikZw7kUmqL6mIwu4bdJPwHivcPJTg0gNw03vByP90Yd/JGfGc+sFDd3V3R+LWLj3r3vIqs4QiSSgF/FOsQQjPPCt51Px1gtSR0opaqNeKHOAZirql6UaFuE/UPgUVH87T/q/Qi+jeLtc+pE3KySx42V4YJvPFEZKYCmGhrt1inGQXpS4K53V+VuCMBmo0jFayu8azXUyXaf2TgAtRNMmzCr2UmW+XnhCuMrdiEzj/vEH6WMrnJsfQlB8LrkwLYjdp16kMGavefwDk84W4C0lIcaDLRrxd+z5tN8+6jBpamqrsJy7BiKbDq7swhTXGoss0Xl2ZychI1h95+ZkFRQPm2ykeIwRcMwUIIA3WE8mXvo79noiPpQgasyUEqfGBEulP9/AaIidnEmK20OPFW4iwxCy/DinEok9tpMRocHYYQyyjFiqgd4D4blrPIDPjy0PE+zGhhrpyOOPEL7lEOoLCOHm8E54KuYX0eX1jnnjOSBlhO2//OOAmyWb2adhy5ir5ZWsqnXQaLo0FPwefYre1c6QuppDgiDgww0DhyroJ4YLvEn70ELBqlTIlcHI2XLDrD2wJF8lKa0TzrS3W+o3w86g1WSRLOWmcaCinUYEFSgaGsJzkNjkHS0IILRn2pWSxHqDxE5o2vR6ji41ijmNUDfq2gM9Gi+gTs+WgPKG+a4RX9qWY3aNqRleT89ZdORfsfz5/H1B0R7NDXcvoUY8fZ76k0ylqS1uc4Dc2ZTynjcufAHl4PbX9Sd/X1rXgvWac3RO5F8ztK8ESr1LqYZDqrQYNiwY+7F6P/W/BYU
// and https://topaz.github.io/paste/#XQAAAQDkCQAAAAAAAAA4GEiZzRd1JAgz+whYRQxSFI7XvmlfhtGDinecZE7XVglOhhPLih7tF+1f6vGBXHx2f7N/fIfjMG8zy2b5K1bcEP2Fo6yufIOrK2Xv9CFqxAiFcqXF+B4dWDZXMbthHyfiUnWQNzkV0hcT5wVw9evKcKh6zuVp40d6Delxu4q84FVJ2dhY/eNJLsgsqMaSnCN8+e4FFMobzvc4ZXLFZQrIjVJ0UJwQ7ZKOvJDlemAVoOBoABHsgOgAJCFm4JV2lELwBITIKT9LzmRsO7giPuslQ7Zsz3Hs9czf3v/ly4cWYb2QQMsQPahsGW2aIYGEflk2nhYJPAosdN9zZ285PGXNNrW5e9NHP5AnsS9lkvf3Kedlg2bQd+ByV/EhMGpwOQjNYG0JpZBF5rJzWVdsuYwvetCboF9ZqInecZftwTsJtyTV2P70EYjs5ipojuXXieZ6QG/XxI/aDOh4WXuWORVA0TxxMtLzuiUElWvTdAjA17uwb1/1i12tbP6s6EFNB6IMgaZnQv0o2zAcIH7em3/HdDXdRytvqC96G34ml/kFYUvvQu1tbK0f9lZIfUtPt30fKSM2zg4NJ60S2DNHt8zU6JvrGqyk/2CPSoeineqtMlQLoTm+a+jRr0FcUEpSMNWYm5bOpcllImxoVUXA7HV0FTDYM0yo8q/pMptPAp/971Z4HCMjsOyJTKuQeAXK8iIYqWaXXpcyhwaaDZ99+p8rlz5K1xAT308o5A4ZdcIALJ3ZEtNlD/9Y5aZ7+Ynngr4O72arMxGuS6wAUj001ocWXbSOEGlmGeCWVUdIEuRF2RQUlcBzFluDR9FeUkVoGqnjGLQ1MWSyRCfKm2btiWELjge8ge4TZEd7KVG5Nrm8cKrZDAYjZ0xkO8p8nnSTlIGJ1Vft1y7SEsxexvx0O0Flz4rQg1Z6SR1JNqOuTkGjhirnSdBN+r6Mvs2Fqb/Kq/FJlHe+kXSF1bitp2skfxdiFYqEEdL+unScYL7SAczwANBr/IiznSJyyL9zfBYvXQcVerkWkfAlm5ape09UaxgGv9LKREALhYAzN6AJDDB9c6tRbN+plzVIRN9CBmDVLReSIo7R8E0etA8L3yhbe2BBLfN4lL2gAXtzzJjYvkUp9raqCz61WMvHreUD2PhTazl+kwjM8IUzmo2DFrXU1ZzOF/T7XWXrjlqgOzBmEE/rldgY0ddx8e0vSj7gKD8EUIPAlR6trZcdCA0L2ENYbOisKQnTn/cfnHJl8NeXH0AOxNKw+V+mRh3W7S747+nBiRmseGkDM17g3MR6hoVdW3V3thtUuwgtP7VBfwdvjlumN1m2L3a6of+hqL7djDL3IzQQBnlhEdszcToONrRBmVRJOMf0ECPY5A9brSO1jR6sVTjQF1nKabW6tEFLuVHcr+BiKUP+pZKKz8amUuffmQixFGfeWUKS0RJTMJt8TVzczSpqNvaJ02r+5qgkN5PddKwMufcpMeTEQy4IRFTCwM6spWm9FVB/8uR3Htfv1KgA0Z6cSkH/+Y3ogw==
// Maybe also https://github.com/AhmedYassineMaalej/AoC-2024/blob/master/src/problems/day16.rs
// And more https://github.com/ayoubzulfiqar/advent-of-code/blob/main/2024/Go/Day16/part_1.go
2024-12-16 21:10:13 +00:00
import (
2024-12-17 15:34:02 +00:00
"fmt"
2024-12-16 21:10:13 +00:00
"math"
2024-12-17 15:34:02 +00:00
"slices"
"strings"
2024-12-16 21:10:13 +00:00
"git.mstar.dev/mstar/goutils/sliceutils"
"git.mstar.dev/mstar/aoc24/util"
)
// A crossing is a path location with 3 or 4 other paths next to it
type Crossing struct {
At util.Vec2
LowestScore int
2024-12-17 15:34:02 +00:00
LowestFrom util.Vec2 // Index into list of crossings
Neighbours []util.Vec2 // Directions where a path is
}
type Area map[int64]map[int64]*Crossing
type Wrapper struct {
A Area
2024-12-16 21:10:13 +00:00
}
var (
DirUp = util.Vec2{X: 0, Y: -1}
DirRight = util.Vec2{X: 1, Y: 0}
DirDown = util.Vec2{X: 0, Y: 1}
DirLeft = util.Vec2{X: -1, Y: 0}
)
var startDir = DirRight
// Get the size of the bord
func getSize(lines []string) util.Vec2 {
return util.Vec2{X: int64(len(lines[0])), Y: int64(len(lines))}
}
// Count the paths next to a given positon
2024-12-17 15:34:02 +00:00
func countPathsAt(lines [][]rune, at util.Vec2) []util.Vec2 {
2024-12-16 21:10:13 +00:00
center := lines[at.Y][at.X]
2024-12-17 15:34:02 +00:00
paths := []util.Vec2{}
2024-12-16 21:10:13 +00:00
if center == '#' {
2024-12-17 15:34:02 +00:00
return paths
2024-12-16 21:10:13 +00:00
}
if util.SafeGet(lines, at.Up()) != '#' {
2024-12-17 15:34:02 +00:00
paths = append(paths, at.Up())
2024-12-16 21:10:13 +00:00
}
if util.SafeGet(lines, at.Down()) != '#' {
2024-12-17 15:34:02 +00:00
paths = append(paths, at.Down())
2024-12-16 21:10:13 +00:00
}
if util.SafeGet(lines, at.Left()) != '#' {
2024-12-17 15:34:02 +00:00
paths = append(paths, at.Left())
2024-12-16 21:10:13 +00:00
}
if util.SafeGet(lines, at.Right()) != '#' {
2024-12-17 15:34:02 +00:00
paths = append(paths, at.Right())
2024-12-16 21:10:13 +00:00
}
2024-12-17 15:34:02 +00:00
return paths
2024-12-16 21:10:13 +00:00
}
2024-12-17 15:34:02 +00:00
func linesToMap(lines [][]rune, size util.Vec2) Area {
area := Area{}
// Ensure area has full maps
for i := range size.Y {
area[i] = map[int64]*Crossing{}
}
2024-12-16 21:10:13 +00:00
for iy, line := range lines {
2024-12-17 15:34:02 +00:00
for ix, char := range line {
pos := util.Vec2{X: int64(ix), Y: int64(iy)}
switch char {
case '.', 'E', 'S':
dirs := countPathsAt(lines, pos)
area[int64(ix)][int64(iy)] = &Crossing{
At: pos,
LowestScore: math.MaxInt64,
LowestFrom: util.Vec2{X: -1, Y: -1},
Neighbours: dirs,
}
default:
2024-12-16 21:10:13 +00:00
}
}
}
2024-12-17 15:34:02 +00:00
return area
2024-12-16 21:10:13 +00:00
}
func findStart(lines [][]rune) util.Vec2 {
for iy, line := range lines {
for ix, char := range line {
if char == 'S' {
return util.Vec2{X: int64(ix), Y: int64(iy)}
}
}
}
// Return invalid positon if none found
return util.Vec2{X: -1, Y: -1}
}
func findEnd(lines [][]rune) util.Vec2 {
for iy, line := range lines {
for ix, char := range line {
if char == 'E' {
return util.Vec2{X: int64(ix), Y: int64(iy)}
}
}
}
// Return invalid positon if none found
return util.Vec2{X: -1, Y: -1}
}
2024-12-17 15:34:02 +00:00
func walk(
area *Wrapper,
from util.Vec2,
dir util.Vec2,
score int,
) {
2024-12-16 21:10:13 +00:00
// Idea: walk forward from start until crossing or wall
// At crossing, defer a walker for both sides (if not walled off) but prioritise forwards
// At crossing, check if own score is lower than current score
// If it is, update score and set own origin
2024-12-17 15:34:02 +00:00
// Else if crossing has a lower score than self, abort
2024-12-16 21:10:13 +00:00
// Special case:
// If at end, update end "crossing" following the earlier rules
2024-12-17 15:34:02 +00:00
// Walk forward in dir until no node found
type WalkTarget struct {
Pos, Dir util.Vec2
Score int
}
// targets := []WalkTarget{}
defer fmt.Println("Deffered done", from)
fmt.Println("Starting", from)
prev := from
for pos := from.Add(dir); area.A[pos.X][pos.Y] != nil; pos = pos.Add(dir) {
score++
node := area.A[pos.X][pos.Y]
if node.LowestScore <= score {
return
}
node.LowestScore = score
node.LowestFrom = prev
// node.LowestFrom = pos.Add(dir.Mult(-1))
fmt.Println("Setting node", pos, score, node.LowestFrom)
// Get all directions that don't match current walking direction (and reverse)
filtered := sliceutils.Filter(node.Neighbours, func(t util.Vec2) bool {
return !t.Add(pos.Mult(-1)).Eq(dir) && !t.Add(pos.Mult(-1)).Eq(dir.Mult(-1))
})
// fmt.Println("Filtered neighbours of node", filtered)
for _, neighbour := range filtered {
fmt.Println("Adding target", neighbour)
// targets = append(targets, WalkTarget{
// neighbour, neighbour.Add(pos.Mult(-1)), score + 1001,
// })
defer walk(area, neighbour, neighbour.Add(pos.Mult(-1)), score+1001)
}
fmt.Println("Stepping", dir)
prev = pos
}
// fmt.Println("Hitting stored targets", from)
// for _, target := range targets {
// walk(area, target.Pos, target.Dir, target.Score)
// }
fmt.Println("Done, walking deferred", from)
}
func visualisePath(area Area, endPos, size util.Vec2, inputLineRunes [][]rune) string {
lineCopy := [][]rune{}
for _, line := range inputLineRunes {
lineCopy = append(lineCopy, slices.Clone(line))
}
for pos := endPos; pos.IsInBounds(size); pos = area[pos.X][pos.Y].LowestFrom {
fmt.Println("Path elem", pos)
lineCopy[pos.Y][pos.X] = 'x'
}
builder := strings.Builder{}
for _, line := range lineCopy {
for _, char := range line {
builder.WriteRune(char)
}
builder.WriteRune('\n')
}
return builder.String()
2024-12-16 21:10:13 +00:00
}
func main() {
inputLines := util.FileContentToNonEmptyLines(util.LoadFileFromArgs())
2024-12-17 15:34:02 +00:00
size := getSize(inputLines)
inputLineChars := sliceutils.Map(inputLines, func(t string) []rune { return []rune(t) })
area := linesToMap(inputLineChars, size)
start := findStart(inputLineChars)
end := findEnd(inputLineChars)
// Fill entire map
fmt.Println("Filling right")
wrapped := Wrapper{area}
walk(&wrapped, start, DirRight, 0)
fmt.Println("Filling up")
walk(&wrapped, start, DirUp, 1000)
fmt.Println("Filling down")
walk(&wrapped, start, DirDown, 1000)
fmt.Println("Filling left")
walk(&wrapped, start, DirLeft, 2000)
fmt.Printf("%#v\n", area[start.X][start.Y])
fmt.Printf("Task 1: %d\n", area[end.X][end.Y].LowestScore)
fmt.Println(visualisePath(area, end, size, inputLineChars))
2024-12-16 21:10:13 +00:00
}