diff --git a/inputs/example.24 b/inputs/example.24 new file mode 100644 index 0000000..6b9b892 --- /dev/null +++ b/inputs/example.24 @@ -0,0 +1,6 @@ +#.###### +#>>.<^<# +#.<..<<# +#>v.><># +#<^v^^># +######.# \ No newline at end of file diff --git a/inputs/input.24 b/inputs/input.24 new file mode 100644 index 0000000..c336f13 --- /dev/null +++ b/inputs/input.24 @@ -0,0 +1,27 @@ +#.######################################################################################################################## +#<^>^<^^^<^v>vv^v<^>^v<>v<<<.^<>^>v^>>>>.^>>>v<.^^^^^>.v.v<^.v>v<<<<<.<<# +#<>^^^v<.v<v<>.>vv>^>vv>^^><<>><>.v.v>><><<>>vv>^^v<^<.>^^^>^>^.<^.^<.>^v<<<# +#><<>.>^^^><<>^><>>>^v<>><>^>>v^v>.^v>.v<..>^^^>><^vv><>>>vvv^<<^v^>v.^v^<# +#<.^vv<^<.>>>>v>>^v<<.v<><>>^.<^<^v.v>>vv<>>vv^.^.>vv.v.># +#<><^>v.^vvv>>^v<>^>^v<>><^<^<^>v^^^><^<>v^.<>>.><^<^><>vv^vv>><<><.<# +#.^^^^^^v^v.<<><>vv<>.>^v^>.<>v.^^<><>^<>^v^^.>v<^<^>.^<^vvv<>^<^v^^.>.^.><<>v>v^>^^>vv^v>v.^vv>^^v><# +#<<^<^>>v><^.^<.<>^.>vv<<.>v<^^v>vv>>>^<<>^<<<<^>v^>vv>^vv^v<.>v^^vv^<>>^v^.<^v<# +#<<>.v^^<>>v><<><<<<>^<.<<>><^>^<<^.v<<.>>>>^><^v<>><^>>v>>>.^>>v^<^.vv..v^^vvv^vv<>^.># +#><.v.^>>>>><.>.vv^<<>v><^<<>.v<<.>>v>>v<^.^^^<^^^^<<>>><^>><^>^<.>^^^^<.^<>v># +#<<>v<^<.<<.>vv<.><>.vvvv<^.<<>><>^^>v>^v>v<^^v.<^v<>vv^<><<>vv^v<>.>>vv^.>.>v>v>^v>^.><^># +#>.>>vv>><<>^v^^v>^<<.^^>>>>><<^^^<^vv<>v.<^<^<>v<^><>v.v^.>>v^^^^>.v.>v^.^>v<>v.^^.^^v^v>.^><<.^<># +#<>v.^>>>>^^^>v<^<>v>^>v>vv^^>v.v<>>.>^><<>v^v.^..v.>vv.^>^^vvvvvv>>^<^v.<>><<.v^>vv>>>vv><^.><.<^^^v^>^v<>v# +#<^>.^<^^v^v.v^>.<.<<^><<^^v^v^vv^<>v^>v^vv<>v.^v.<>^>vv>.^<^.v.>v>v>>v>.<<>^^vvv>>v^^>^<^>.<<.v^>vv>>vv>>.>>.^<<^v>.vvv><<<>^.^v>vvv^^^^v<<<><^^<<^><<>><>v^>v^>^.<<^<>>v.vv>>>^^v^.vv^>><<<<.<<>^>^v>><>.>><>^>^>^^>>v>^>v.>^<<^><^v^<>v<>^^>>v.# +#..^>^v.>.<^>^>.v>>.<<..<<>v^vvv<v><.<^^>^<^^<^>><>^<.vv<<<>^^v^><><^<^vv<<.<^>v.v<^^<>>>.>^^v<^v># +#<.>vv<.<>^<<.>.v<<>><<.>.vv^^<>vv^v..^<>>.^<>^>^^<^^^>>><><>^^.vv<<>v>^.v.^^<<^v<>.^>>^^>v>v^^^>v^>^>>><# +#<>v>vv<<<><<v>>^.^<<^>>^^>^v.v^<>^^<>>^v.>.<.>>>.<<^><.<^^>^>>.><^>.^>v<^>^>v>.^^v>.v^>>>>>v>># +#<.<<^>><<^>.v>v^^.v^>v<^>>^.>>>^>^v<^v.>^^<<<>v.^.v^v.^>.<>^><<<<>v^^>..>v.><^^.<<<^.>^<>>>vv><># +#>^v.>vvv^^.^<>^>^>v^<>.^.v.^v<^v><^v<>^>>v.^<^><<>^>>>><^^<<>^^><<<^vvv>><.^>>v^^<^><>.>..^^v>v># +#.<^^><<<^^^v<>^.>v>v^>v^^.v^.^<<<^^v.v^<^>.<>><<><^v^v.^^<>^v^vvvv><<^^><<>vvv^># +#.<^..v^^^>>^v^^.><>v^.>v^^<<><^^<.<>>v^>.<>>.^>vv>v>^v.<^^^<<^.^>^.<<<.^^>.^^^.v<<>v<.vv^^<>v>>>><>^>^^.<<^^<>><<# +#<^<^^>>^.^^^>v><^><.v>>v^>vv>v.>^>^<^^^^v<<<^>.>v^v^..<<<<>^<<<>^^vv^v>.<^v<><>^<<>^<>>v>><<>^>vvvv<>># +#>>^.^^>^^<<>^^vv>>>^>vv.><><^^v<<<.v<^^>><^v>^<>^v<^.<<>^v<.^<.v.><.<^>^vvvvvv^v^^<^.<># +#<><<^<.<^>^^^.^v>>vv.vv.^vv^.^^vvvv..<.v^>^>>>vv<>^>.>>^^^<<^v<^.vv>>v.v<<<^^^v^vvv..^># +########################################################################################################################.# diff --git a/main.go b/main.go index 43bf46b..9054cd1 100644 --- a/main.go +++ b/main.go @@ -20,6 +20,7 @@ import ( "github.com/acw/advent2022/solutions/day21" "github.com/acw/advent2022/solutions/day22" "github.com/acw/advent2022/solutions/day23" + "github.com/acw/advent2022/solutions/day24" "github.com/acw/advent2022/solutions/day3" "github.com/acw/advent2022/solutions/day4" "github.com/acw/advent2022/solutions/day5" @@ -82,6 +83,8 @@ func main() { day22.Run(os.Args[2]) case "day23": day23.Run(os.Args[2]) + case "day24": + day24.Run(os.Args[2]) default: fmt.Println("PANIC! Unknown day.") } diff --git a/solutions/day24/day24.go b/solutions/day24/day24.go new file mode 100644 index 0000000..3e73468 --- /dev/null +++ b/solutions/day24/day24.go @@ -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") +}