196 lines
4.6 KiB
Go
196 lines
4.6 KiB
Go
|
package main
|
||
|
|
||
|
import (
|
||
|
"errors"
|
||
|
"fmt"
|
||
|
"io"
|
||
|
"os"
|
||
|
"slices"
|
||
|
"strings"
|
||
|
)
|
||
|
|
||
|
type Tile rune
|
||
|
|
||
|
const (
|
||
|
TileEmpty Tile = '.'
|
||
|
TileWall Tile = '#'
|
||
|
TileRight Tile = '>'
|
||
|
TileLeft Tile = '<'
|
||
|
TileUp Tile = '^'
|
||
|
TileDown Tile = 'v'
|
||
|
)
|
||
|
|
||
|
type Coordinate struct {
|
||
|
Row int
|
||
|
Col int
|
||
|
}
|
||
|
|
||
|
func main() {
|
||
|
if len(os.Args) != 2 {
|
||
|
fmt.Fprintf(os.Stderr, "Usage: %s inputfile\n", os.Args[0])
|
||
|
os.Exit(1)
|
||
|
}
|
||
|
|
||
|
inputFilename := os.Args[1]
|
||
|
inputFile, err := os.Open(inputFilename)
|
||
|
if err != nil {
|
||
|
panic(fmt.Sprintf("could not open input file: %s", err))
|
||
|
}
|
||
|
|
||
|
defer inputFile.Close()
|
||
|
|
||
|
inputBytes, err := io.ReadAll(inputFile)
|
||
|
if err != nil {
|
||
|
panic(fmt.Sprintf("could not read input file: %s", err))
|
||
|
}
|
||
|
|
||
|
input := strings.TrimSpace(string(inputBytes))
|
||
|
inputLines := strings.Split(input, "\n")
|
||
|
grid, err := parseGrid(inputLines)
|
||
|
if err != nil {
|
||
|
panic(fmt.Sprintf("could not parse input: %s", err))
|
||
|
}
|
||
|
|
||
|
for _, line := range grid {
|
||
|
for _, char := range line {
|
||
|
fmt.Printf("%c", char)
|
||
|
}
|
||
|
fmt.Println()
|
||
|
}
|
||
|
|
||
|
fmt.Printf("Part 1: %d\n", part1(grid))
|
||
|
}
|
||
|
|
||
|
func part1(grid [][]Tile) int {
|
||
|
startCol, err := findStartingTile(grid[0])
|
||
|
if err != nil {
|
||
|
panic(fmt.Sprintf("could not find starting tile: %s", err))
|
||
|
}
|
||
|
|
||
|
path := findLongestPath(
|
||
|
Coordinate{Row: 0, Col: startCol},
|
||
|
grid,
|
||
|
)
|
||
|
|
||
|
return len(path) - 1
|
||
|
}
|
||
|
|
||
|
func findStartingTile(firstRow []Tile) (int, error) {
|
||
|
candidate := (*int)(nil)
|
||
|
for col, item := range firstRow {
|
||
|
if item == TileEmpty && candidate == nil {
|
||
|
savedCol := col
|
||
|
candidate = &savedCol
|
||
|
} else if item == TileEmpty && candidate != nil {
|
||
|
return 0, errors.New("more than one open position")
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if candidate == nil {
|
||
|
return 0, errors.New("no open positions")
|
||
|
}
|
||
|
|
||
|
return *candidate, nil
|
||
|
}
|
||
|
|
||
|
func findLongestPath(start Coordinate, grid [][]Tile) []Coordinate {
|
||
|
// position := start
|
||
|
inBounds := func(pos Coordinate) bool {
|
||
|
return pos.Row >= 0 && pos.Row < len(grid) && pos.Col >= 0 && pos.Col < len(grid[0])
|
||
|
}
|
||
|
|
||
|
var dfs func(Coordinate, []Coordinate) []Coordinate
|
||
|
// visited := map[Coordinate]struct{}{}
|
||
|
dfs = func(cursor Coordinate, path []Coordinate) []Coordinate {
|
||
|
// fmt.Println(path)
|
||
|
// visited[cursor] = struct{}{}
|
||
|
|
||
|
upNeighbor := Coordinate{Row: cursor.Row - 1, Col: cursor.Col}
|
||
|
downNeighbor := Coordinate{Row: cursor.Row + 1, Col: cursor.Col}
|
||
|
leftNeighbor := Coordinate{Row: cursor.Row, Col: cursor.Col - 1}
|
||
|
rightNeighbor := Coordinate{Row: cursor.Row, Col: cursor.Col + 1}
|
||
|
switch grid[cursor.Row][cursor.Col] {
|
||
|
case TileUp:
|
||
|
// _, ok := visited[upNeighbor]
|
||
|
ok := slices.Contains(path, upNeighbor)
|
||
|
if inBounds(upNeighbor) && !ok {
|
||
|
nextPath := slices.Clone(path)
|
||
|
return dfs(upNeighbor, append(nextPath, upNeighbor))
|
||
|
}
|
||
|
case TileDown:
|
||
|
// _, ok := visited[downNeighbor]
|
||
|
ok := slices.Contains(path, downNeighbor)
|
||
|
if inBounds(downNeighbor) && !ok {
|
||
|
nextPath := slices.Clone(path)
|
||
|
return dfs(downNeighbor, append(nextPath, downNeighbor))
|
||
|
}
|
||
|
case TileLeft:
|
||
|
ok := slices.Contains(path, leftNeighbor)
|
||
|
// _, ok := visited[leftNeighbor]
|
||
|
if inBounds(leftNeighbor) && !ok {
|
||
|
nextPath := slices.Clone(path)
|
||
|
return dfs(leftNeighbor, append(nextPath, leftNeighbor))
|
||
|
}
|
||
|
case TileRight:
|
||
|
ok := slices.Contains(path, rightNeighbor)
|
||
|
// _, ok := visited[rightNeighbor]
|
||
|
if inBounds(rightNeighbor) && !ok {
|
||
|
nextPath := slices.Clone(path)
|
||
|
return dfs(rightNeighbor, append(nextPath, rightNeighbor))
|
||
|
}
|
||
|
case TileEmpty:
|
||
|
neighbors := []Coordinate{upNeighbor, downNeighbor, leftNeighbor, rightNeighbor}
|
||
|
longestPath := path
|
||
|
for _, neighbor := range neighbors {
|
||
|
if !inBounds(neighbor) {
|
||
|
continue
|
||
|
} else if slices.Contains(path, neighbor) {
|
||
|
continue
|
||
|
}
|
||
|
//else if _, ok := visited[neighbor]; ok {
|
||
|
// continue
|
||
|
//}
|
||
|
|
||
|
nextPath := slices.Clone(path)
|
||
|
fullPath := dfs(neighbor, append(nextPath, neighbor))
|
||
|
if len(fullPath) > len(longestPath) {
|
||
|
longestPath = fullPath
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return longestPath
|
||
|
case TileWall:
|
||
|
return path
|
||
|
}
|
||
|
|
||
|
return path
|
||
|
}
|
||
|
|
||
|
return dfs(start, []Coordinate{})
|
||
|
}
|
||
|
|
||
|
func parseGrid(inputLines []string) ([][]Tile, error) {
|
||
|
if len(inputLines) == 0 {
|
||
|
return nil, errors.New("cannot parse grid of no length")
|
||
|
}
|
||
|
|
||
|
height := len(inputLines)
|
||
|
width := len(inputLines[0])
|
||
|
grid := make([][]Tile, height)
|
||
|
for i, line := range inputLines {
|
||
|
if len(line) != width {
|
||
|
return nil, errors.New("grid's rows are not of consistent length")
|
||
|
}
|
||
|
|
||
|
for _, char := range line {
|
||
|
if Tile(char) != TileWall && Tile(char) != TileEmpty && Tile(char) != TileUp && Tile(char) != TileDown && Tile(char) != TileLeft && Tile(char) != TileRight {
|
||
|
return nil, fmt.Errorf("invalid tile char %c", char)
|
||
|
}
|
||
|
|
||
|
grid[i] = append(grid[i], Tile(char))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return grid, nil
|
||
|
}
|