advent-of-code-2023/day23/main.go

196 lines
4.6 KiB
Go
Raw Normal View History

2023-12-23 21:00:51 +00:00
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
}