Files
advent-of-code-2022/day24/common/blizzard.go
2022-12-24 16:38:43 +01:00

268 lines
7.7 KiB
Go

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
}