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") }