Files
advent-of-code-2024/day16/maze.go

177 lines
4.1 KiB
Go
Raw Normal View History

2024-12-17 09:03:26 +01:00
package main
import ( "os"; "fmt"; "strings")
// ----------------------------------------
type Direction uint8
const (
Up Direction = iota
Down Direction = iota
Left Direction = iota
Right Direction = iota
)
// ----------------------------------------
type Path struct {
position [2]uint
direction Direction
}
func (p Path) StepUnbound(d Direction) Path {
switch {
case d == Down: p.position[1]++
case d == Up: p.position[1]--
case d == Left: p.position[0]--
case d == Right: p.position[0]++
}
p.direction = d
return p
}
// ----------------------------------------
type Maze struct {
start [2]uint
end [2]uint
field [][]byte
}
func NewMaze(str string) *Maze {
var maze Maze
for y, line := range strings.Split(str, "\n") {
line_arr := make([]byte, len(line))
for x, char := range []byte(line) {
switch {
case char == 'S':
line_arr[x] = '.'
maze.start = [...]uint{uint(x), uint(y)}
case char == 'E':
line_arr[x] = '.'
maze.end = [...]uint{uint(x), uint(y)}
default: line_arr[x] = char
}
}
maze.field = append(maze.field, line_arr)
}
return &maze
}
func (m *Maze) Step(path Path) []Path {
out := make([]Path, 0, 4)
if m.field[path.position[1] + 1][path.position[0]] == '.' && path.direction != Up {
out = append(out, path.StepUnbound(Down))
}
if m.field[path.position[1] - 1][path.position[0]] == '.' && path.direction != Down {
out = append(out, path.StepUnbound(Up))
}
if m.field[path.position[1]][path.position[0] + 1] == '.' && path.direction != Left {
out = append(out, path.StepUnbound(Right))
}
if m.field[path.position[1]][path.position[0] - 1] == '.' && path.direction != Right {
out = append(out, path.StepUnbound(Left))
}
return out
}
func (m *Maze) Start() Path {
return Path{m.start, Right}
}
func (m *Maze) Finished(path Path) bool {
return m.end == path.position
}
func (m *Maze) Print() {
for _, line := range m.field {
fmt.Println(string(line))
}
}
// ----------------------------------------
type MazeGraph struct {
cost uint
children []*MazeGraph
}
var new_maze_graph_visited map[Path]uint
func NewMazeGraph(m *Maze, start Path, cost uint) *MazeGraph {
children := make([]*MazeGraph, 0, 3)
// finish reached?
if m.Finished(start) { return &MazeGraph{ cost, children } }
// check for loop
value, exists := new_maze_graph_visited[start]
if exists {
if value <= cost { return nil }
}
new_maze_graph_visited[start] = cost
// build subgraphs/ children
for _, path := range m.Step(start) {
var child *MazeGraph
if start.direction != path.direction {
child = NewMazeGraph(m, path, cost + 1001)
} else {
child = NewMazeGraph(m, path, cost + 1)
}
if child != nil { children = append(children, child) }
}
// found dead end
if len(children) == 0 { return nil }
// collapse if there is only one possible way
if len(children) == 0 { return children[0] }
return &MazeGraph{ cost: cost, children: children }
}
func (graph *MazeGraph) Leaves() []*MazeGraph {
out := make([]*MazeGraph, 0)
if len(graph.children) == 0 {
out = append(out, graph)
return out
}
for _, g := range graph.children {
out = append(out, g.Leaves()...)
}
return out
}
// ----------------------------------------
func check(err error) { if err != nil { panic(err) } }
func main() {
dat, err := os.ReadFile("data.txt")
check(err)
input := strings.TrimSpace(string(dat))
maze := NewMaze(input)
var best *MazeGraph
new_maze_graph_visited = make(map[Path]uint)
for _, leave := range NewMazeGraph(maze, maze.Start(), 0).Leaves() {
if best == nil || best.cost > leave.cost { best = leave }
}
fmt.Println(best.cost)
}