day24
This commit is contained in:
313
solutions/day24/day24.go
Normal file
313
solutions/day24/day24.go
Normal file
@@ -0,0 +1,313 @@
|
||||
package day24
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
type Direction int
|
||||
|
||||
const (
|
||||
Up Direction = iota
|
||||
Down
|
||||
Left
|
||||
Right
|
||||
Stay
|
||||
)
|
||||
|
||||
type Point struct {
|
||||
x int
|
||||
y int
|
||||
}
|
||||
|
||||
type Directions []Direction
|
||||
|
||||
func listContains(dirs Directions, x Direction) bool {
|
||||
for _, v := range dirs {
|
||||
if x == v {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func sameBoard(board1 map[Point]Directions, board2 map[Point]Directions) bool {
|
||||
if len(board1) != len(board2) {
|
||||
return false
|
||||
}
|
||||
|
||||
for point, vals1 := range board1 {
|
||||
vals2 := board2[point]
|
||||
|
||||
if len(vals1) != len(vals2) {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, v := range vals1 {
|
||||
if !listContains(vals2, v) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
type Map struct {
|
||||
minute int
|
||||
width int
|
||||
height int
|
||||
elf Point
|
||||
entrance Point
|
||||
target Point
|
||||
}
|
||||
|
||||
var boardStates []map[Point]Directions
|
||||
|
||||
func printState(state Map) {
|
||||
board := boardStates[state.minute%len(boardStates)]
|
||||
|
||||
for y := 0; y < state.height; y++ {
|
||||
for x := 0; x < state.width; x++ {
|
||||
if x == state.elf.x && y == state.elf.y {
|
||||
fmt.Printf("E")
|
||||
} else if x == state.target.x && y == state.target.y {
|
||||
fmt.Printf(".")
|
||||
} else if x == state.entrance.x && y == state.entrance.y {
|
||||
fmt.Printf(".")
|
||||
} else if y == 0 {
|
||||
fmt.Printf("#")
|
||||
} else if y == state.height-1 {
|
||||
fmt.Printf("#")
|
||||
} else if x == 0 || x == state.width-1 {
|
||||
fmt.Printf("#")
|
||||
} else {
|
||||
dir, exists := board[Point{x, y}]
|
||||
if exists && len(dir) == 1 {
|
||||
switch dir[0] {
|
||||
case Left:
|
||||
fmt.Printf("<")
|
||||
case Right:
|
||||
fmt.Printf(">")
|
||||
case Up:
|
||||
fmt.Printf("^")
|
||||
case Down:
|
||||
fmt.Printf("v")
|
||||
default:
|
||||
fmt.Printf("?")
|
||||
}
|
||||
} else if exists {
|
||||
fmt.Printf("%d", len(dir))
|
||||
} else {
|
||||
fmt.Printf(".")
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
|
||||
func okMove(state Map) bool {
|
||||
if state.elf.x == state.target.x && state.elf.y == state.target.y {
|
||||
return true
|
||||
}
|
||||
|
||||
if state.elf.x <= 0 || state.elf.y <= 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
if state.elf.x == state.width-1 || state.elf.y == state.height-1 {
|
||||
return false
|
||||
}
|
||||
|
||||
board := boardStates[state.minute%len(boardStates)]
|
||||
_, exists := board[Point{state.elf.x, state.elf.y}]
|
||||
return !exists
|
||||
}
|
||||
|
||||
func nextBoard(width int, height int, board map[Point]Directions) map[Point]Directions {
|
||||
newBoard := map[Point]Directions{}
|
||||
|
||||
// start with the snow move
|
||||
for point, dirs := range board {
|
||||
for _, dir := range dirs {
|
||||
switch dir {
|
||||
case Left:
|
||||
newX := point.x - 1
|
||||
if newX == 0 {
|
||||
newX = width - 2
|
||||
}
|
||||
newPoint := Point{newX, point.y}
|
||||
newBoard[newPoint] = append(newBoard[newPoint], Left)
|
||||
|
||||
case Right:
|
||||
newX := point.x + 1
|
||||
if newX == width-1 {
|
||||
newX = 1
|
||||
}
|
||||
newPoint := Point{newX, point.y}
|
||||
newBoard[newPoint] = append(newBoard[newPoint], Right)
|
||||
|
||||
case Up:
|
||||
newY := point.y - 1
|
||||
if newY == 0 {
|
||||
newY = height - 2
|
||||
}
|
||||
newPoint := Point{point.x, newY}
|
||||
newBoard[newPoint] = append(newBoard[newPoint], Up)
|
||||
|
||||
case Down:
|
||||
newY := point.y + 1
|
||||
if newY == height-1 {
|
||||
newY = 1
|
||||
}
|
||||
newPoint := Point{point.x, newY}
|
||||
newBoard[newPoint] = append(newBoard[newPoint], Down)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return newBoard
|
||||
}
|
||||
|
||||
func nextStates(state Map) []Map {
|
||||
retval := []Map{}
|
||||
newMinute := state.minute + 1
|
||||
|
||||
stay := Map{newMinute, state.width, state.height, state.elf, state.entrance, state.target}
|
||||
if okMove(stay) {
|
||||
retval = append(retval, stay)
|
||||
}
|
||||
|
||||
left := Map{newMinute, state.width, state.height, Point{state.elf.x - 1, state.elf.y}, state.entrance, state.target}
|
||||
if okMove(left) {
|
||||
retval = append(retval, left)
|
||||
}
|
||||
|
||||
right := Map{newMinute, state.width, state.height, Point{state.elf.x + 1, state.elf.y}, state.entrance, state.target}
|
||||
if okMove(right) {
|
||||
retval = append(retval, right)
|
||||
}
|
||||
|
||||
up := Map{newMinute, state.width, state.height, Point{state.elf.x, state.elf.y - 1}, state.entrance, state.target}
|
||||
if okMove(up) {
|
||||
retval = append(retval, up)
|
||||
}
|
||||
|
||||
down := Map{newMinute, state.width, state.height, Point{state.elf.x, state.elf.y + 1}, state.entrance, state.target}
|
||||
if okMove(down) {
|
||||
retval = append(retval, down)
|
||||
}
|
||||
|
||||
return retval
|
||||
}
|
||||
|
||||
func search(initialState Map) int {
|
||||
queue := []Map{initialState}
|
||||
minute := -1
|
||||
|
||||
for len(queue) > 0 {
|
||||
workState := queue[0]
|
||||
|
||||
if workState.minute > minute {
|
||||
minute = workState.minute
|
||||
filteredStates := []Map{}
|
||||
pointsFound := map[Point]bool{}
|
||||
|
||||
for _, state := range queue {
|
||||
if state.minute != minute {
|
||||
fmt.Println("PANIC: WEIRD")
|
||||
}
|
||||
_, exists := pointsFound[state.elf]
|
||||
if !exists {
|
||||
filteredStates = append(filteredStates, state)
|
||||
pointsFound[state.elf] = true
|
||||
}
|
||||
}
|
||||
|
||||
queue = filteredStates
|
||||
continue
|
||||
}
|
||||
|
||||
if workState.elf.x == workState.target.x && workState.elf.y == workState.target.y {
|
||||
return workState.minute
|
||||
}
|
||||
|
||||
queue = append(queue[1:], nextStates(workState)...)
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func Run(filename string) {
|
||||
file, err := os.Open(filename)
|
||||
if err != nil {
|
||||
fmt.Println("Error opening file:", err)
|
||||
return
|
||||
}
|
||||
|
||||
scanner := bufio.NewScanner(file)
|
||||
scanner.Split(bufio.ScanLines)
|
||||
var entrance, exit Point
|
||||
board := map[Point]Directions{}
|
||||
width := 0
|
||||
y := 0
|
||||
|
||||
for scanner.Scan() {
|
||||
line := []rune(scanner.Text())
|
||||
|
||||
if line[3] == '#' {
|
||||
for x, r := range line {
|
||||
if r == '.' {
|
||||
if y == 0 {
|
||||
entrance = Point{x, 0}
|
||||
} else {
|
||||
exit = Point{x, y}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for x, r := range line {
|
||||
switch r {
|
||||
case '>':
|
||||
board[Point{x, y}] = Directions{Right}
|
||||
case '<':
|
||||
board[Point{x, y}] = Directions{Left}
|
||||
case '^':
|
||||
board[Point{x, y}] = Directions{Up}
|
||||
case 'v':
|
||||
board[Point{x, y}] = Directions{Down}
|
||||
case '.', '#':
|
||||
continue
|
||||
default:
|
||||
fmt.Println("PANIC: Unexpected character", string(r))
|
||||
}
|
||||
}
|
||||
}
|
||||
if width == 0 {
|
||||
width = len(line)
|
||||
}
|
||||
y += 1
|
||||
}
|
||||
file.Close()
|
||||
|
||||
boardStates = []map[Point]Directions{board}
|
||||
for {
|
||||
nextBoard := nextBoard(width, y, boardStates[len(boardStates)-1])
|
||||
if sameBoard(nextBoard, boardStates[0]) {
|
||||
break
|
||||
}
|
||||
boardStates = append(boardStates, nextBoard)
|
||||
}
|
||||
fmt.Println("Pre-computed", len(boardStates), "boards.")
|
||||
|
||||
initialState := Map{0, width, y, entrance, entrance, exit}
|
||||
printState(initialState)
|
||||
foundExit := search(initialState)
|
||||
fmt.Println("Found exit after", foundExit, "minutes")
|
||||
backToEntrance := search(Map{foundExit, initialState.width, initialState.height, initialState.target, initialState.entrance, initialState.entrance})
|
||||
fmt.Println("Made it back to the exit at", backToEntrance, "minutes")
|
||||
backToExit := search(Map{backToEntrance, initialState.width, initialState.height, initialState.elf, initialState.entrance, initialState.target})
|
||||
fmt.Println("And then back to the exit at", backToExit, "minutes")
|
||||
}
|
||||
Reference in New Issue
Block a user