2024-12-16 21:10:13 +00:00
|
|
|
package main
|
|
|
|
|
|
|
|
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
|
|
|
}
|