package common import ( "bufio" "fmt" "math" ) type Tile byte const ( Empty Tile = iota Wall Blizzard ) type Map struct { tiles [][][]Tile steps int height int width int cache [][][]int bestDistance int } func (tileMap *Map) InitCache() { tileMap.cache = make([][][]int, (tileMap.height - 2) * (tileMap.width - 2)) for i := 0; i < len(tileMap.cache); i++ { tileMap.cache[i] = make([][]int, tileMap.height) for j := 0; j < len(tileMap.cache[i]); j++ { tileMap.cache[i][j] = make([]int, tileMap.width) for k := 0; k < len(tileMap.cache[i][j]); k++ { tileMap.cache[i][j][k] = math.MaxInt32 } } } tileMap.bestDistance = math.MaxInt32 } func (tileMap *Map) GetDistanceToEnd(currentSteps int) int { tileMap.InitCache() startSteps := currentSteps % tileMap.steps startCol := 0 for i := 0; i < tileMap.width; i++ { if tileMap.tiles[startSteps][0][i] == Empty { startCol = i break } } endCol := 0 for i := 0; i < tileMap.width; i++ { if tileMap.tiles[startSteps][len(tileMap.tiles[startSteps])-1][i] == Empty { endCol = i break } } tileMap.cache[startSteps][0][startCol] = currentSteps tileMap.PropagateDistanceFromTile(startSteps, startCol, 0, currentSteps, endCol, len(tileMap.tiles[startSteps])-1) return tileMap.bestDistance } func (tileMap *Map) GetDistanceToStart(currentSteps int) int { tileMap.InitCache() startSteps := currentSteps % tileMap.steps endCol := 0 for i := 0; i < tileMap.width; i++ { if tileMap.tiles[startSteps][len(tileMap.tiles[startSteps])-1][i] == Empty { endCol = i break } } startCol := 0 for i := 0; i < tileMap.width; i++ { if tileMap.tiles[startSteps][0][i] == Empty { startCol = i break } } tileMap.cache[startSteps][len(tileMap.tiles[startSteps])-1][endCol] = currentSteps tileMap.PropagateDistanceFromTile(startSteps, endCol, len(tileMap.tiles[startSteps])-1, currentSteps, startCol, 0) return tileMap.bestDistance } func (tileMap *Map) PropagateDistanceFromTile(step, x, y, currentDistance, targetX, targetY int) { nextStep := (step + 1) % tileMap.steps orders := []byte{0, 1, 2, 3, 4} distances := []int{(x-targetX)*(x-targetX)+(y+1-targetY)*(y+1-targetY), (x+1-targetX)*(x+1-targetX)+(y-targetY)*(y-targetY), (x-targetX)*(x-targetX)+(y-targetY)*(y-targetY), (x-1-targetX)*(x-1-targetX)+(y-targetY)*(y-targetY), (x-targetX)*(x-targetX)+(y-1-targetY)*(y-1-targetY)} if currentDistance >= tileMap.bestDistance { return } if x == targetX && y == targetY { tileMap.bestDistance = currentDistance } sorted := false for !sorted { sorted = true for i := 0; i < 4; i++ { if distances[orders[i]] > distances[orders[i+1]] { orders[i], orders[i+1] = orders[i+1], orders[i] sorted = false } } } for _, order := range orders { if order == 0 && y < tileMap.height - 1 && tileMap.tiles[nextStep][y+1][x] == Empty && currentDistance + 1 < tileMap.cache[nextStep][y+1][x] { tileMap.cache[nextStep][y+1][x] = currentDistance + 1 tileMap.PropagateDistanceFromTile(nextStep, x, y+1, currentDistance+1, targetX, targetY) } if order == 1 && x < tileMap.width - 1 && tileMap.tiles[nextStep][y][x+1] == Empty && currentDistance + 1 < tileMap.cache[nextStep][y][x+1] { tileMap.cache[nextStep][y][x+1] = currentDistance + 1 tileMap.PropagateDistanceFromTile(nextStep, x+1, y, currentDistance+1, targetX, targetY) } if order == 2 && tileMap.tiles[nextStep][y][x] == Empty && currentDistance + 1 < tileMap.cache[nextStep][y][x] { tileMap.cache[nextStep][y][x] = currentDistance + 1 tileMap.PropagateDistanceFromTile(nextStep, x, y, currentDistance+1, targetX, targetY) } if order == 3 && x > 0 && tileMap.tiles[nextStep][y][x-1] == Empty && currentDistance + 1 < tileMap.cache[nextStep][y][x-1] { tileMap.cache[nextStep][y][x-1] = currentDistance + 1 tileMap.PropagateDistanceFromTile(nextStep, x-1, y, currentDistance+1, targetX, targetY) } if order == 4 && y > 0 && tileMap.tiles[nextStep][y-1][x] == Empty && currentDistance + 1 < tileMap.cache[nextStep][y-1][x] { tileMap.cache[nextStep][y-1][x] = currentDistance + 1 tileMap.PropagateDistanceFromTile(nextStep, x, y-1, currentDistance+1, targetX, targetY) } } } func (tileMap *Map) Print(step int) { for i := 0; i < len(tileMap.tiles[step]); i++ { for j := 0; j < len(tileMap.tiles[step][i]); j++ { switch tileMap.tiles[step][i][j] { case Empty: fmt.Print(".") case Wall: fmt.Print("#") case Blizzard: fmt.Print("O") } } fmt.Println() } } func Parse(scanner bufio.Scanner) Map { tileMap := Map{} width := 0 height := 0 walls := make(map[int]map[int]struct{}) blizzards := make(map[int]map[int]map[int]struct{}) for i := 0; i < 4; i++ { blizzards[i] = make(map[int]map[int]struct{}) } for scanner.Scan() { line := scanner.Text() width = len(line) walls[height] = make(map[int]struct{}) for i := 0; i < 4; i++ { blizzards[i][height] = make(map[int]struct{}) } for i := range line { switch line[i] { case '#': walls[height][i] = struct{}{} case '>': blizzards[0][height][i] = struct{}{} case 'v': blizzards[1][height][i] = struct{}{} case '<': blizzards[2][height][i] = struct{}{} case '^': blizzards[3][height][i] = struct{}{} } } height++ } tileMap.tiles = make([][][]Tile, (height - 2) * (width - 2)) for i := 0; i < height - 2; i++ { for j := 0; j < width - 2; j++ { tileMap.tiles[i * (width - 2) + j] = make([][]Tile, height) for row := range walls { tileMap.tiles[i * (width - 2) + j][row] = make([]Tile, width) for col := range walls[row] { tileMap.tiles[i * (width - 2) + j][row][col] = Wall } } newBlizzards := make(map[int]map[int]map[int]struct{}) for k := 0; k < 4; k++ { newBlizzards[k] = make(map[int]map[int]struct{}) for l := 0; l < height; l++ { newBlizzards[k][l] = make(map[int]struct{}) } } for row := range blizzards[0] { for col := range blizzards[0][row] { tileMap.tiles[i * (width - 2) + j][row][col] = Blizzard if col == width - 2 { newBlizzards[0][row][1] = struct{}{} } else { newBlizzards[0][row][col+1] = struct{}{} } } } for row := range blizzards[1] { for col := range blizzards[1][row] { tileMap.tiles[i * (width - 2) + j][row][col] = Blizzard if row == height - 2 { newBlizzards[1][1][col] = struct{}{} } else { newBlizzards[1][row+1][col] = struct{}{} } } } for row := range blizzards[2] { for col := range blizzards[2][row] { tileMap.tiles[i * (width - 2) + j][row][col] = Blizzard if col == 1 { newBlizzards[2][row][width-2] = struct{}{} } else { newBlizzards[2][row][col-1] = struct{}{} } } } for row := range blizzards[3] { for col := range blizzards[3][row] { tileMap.tiles[i * (width - 2) + j][row][col] = Blizzard if row == 1 { newBlizzards[3][height-2][col] = struct{}{} } else { newBlizzards[3][row-1][col] = struct{}{} } } } blizzards = newBlizzards } } tileMap.height = height tileMap.width = width tileMap.steps = (height - 2) * (width - 2) return tileMap }