Files
advent2022/solutions/day22/day22.go
2022-12-23 19:45:56 -08:00

636 lines
12 KiB
Go

package day22
import (
"bufio"
"fmt"
"os"
)
type Command int
type Tile int
type Direction int
type Face int
type Board [][]Tile
type FaceMap map[Face]Board
const (
TurnRight Command = -1
TurnLeft = -2
)
const (
Void Tile = iota
Empty
Wall
)
const (
Right Direction = 0
Down = 1
Left = 2
Up = 3
)
const (
TopFace Face = iota
BottomFace
LeftFace
RightFace
FrontFace
BackFace
)
func printBoard(board Board) {
for _, row := range board {
for _, tile := range row {
switch tile {
case Void:
fmt.Printf(" ")
case Empty:
fmt.Printf(".")
case Wall:
fmt.Printf("#")
}
}
fmt.Println()
}
}
func parseCommands(runes []rune) []Command {
result := []Command{}
accum := 0
for _, r := range runes {
if r >= '0' && r <= '9' {
accum = (accum * 10) + int(r-'0')
} else {
result = append(result, Command(accum))
accum = 0
if r == 'R' {
result = append(result, TurnRight)
} else {
result = append(result, TurnLeft)
}
}
}
if accum > 0 {
result = append(result, Command(accum))
}
return result
}
func parseBoardLine(runes []rune) []Tile {
result := []Tile{}
for _, r := range runes {
switch r {
case ' ':
result = append(result, Void)
case '.':
result = append(result, Empty)
case '#':
result = append(result, Wall)
default:
fmt.Println("PANIC: Illegal character in board map", string(r))
}
}
return result
}
func initialPosition(board Board) (x int, y int) {
for idx, tile := range board[0] {
if tile == Empty {
x = idx
y = 0
return
}
}
fmt.Println("PANIC: Couldn't figure out initial position")
x = 0
y = 0
return
}
func turn(direction Direction, turn Command) Direction {
switch direction {
case Up:
switch turn {
case TurnLeft:
return Left
case TurnRight:
return Right
}
case Right:
switch turn {
case TurnLeft:
return Up
case TurnRight:
return Down
}
case Down:
switch turn {
case TurnLeft:
return Right
case TurnRight:
return Left
}
case Left:
switch turn {
case TurnLeft:
return Down
case TurnRight:
return Up
}
}
fmt.Println("PANIC: turn default WTF")
return Down
}
type State2D struct {
x int
y int
direction Direction
board Board
}
func step2d(count int, state *State2D) {
if count == 0 {
return
}
keepGoing := true
switch state.direction {
case Right:
currentRow := state.board[state.y]
tester := (state.x + 1) % len(currentRow)
for {
if currentRow[tester] == Void {
tester = (tester + 1) % len(currentRow)
continue
} else if currentRow[tester] == Wall {
keepGoing = false
break
} else {
(*state).x = tester
break
}
}
case Left:
currentRow := state.board[state.y]
tester := state.x - 1
for {
if tester < 0 {
tester = len(currentRow) - 1
}
if currentRow[tester] == Void {
tester -= 1
continue
} else if currentRow[tester] == Wall {
keepGoing = false
break
} else {
(*state).x = tester
break
}
}
case Up:
tester := state.y - 1
for {
if tester < 0 {
tester = len(state.board) - 1
}
if state.board[tester][state.x] == Void {
tester -= 1
continue
} else if state.board[tester][state.x] == Wall {
keepGoing = false
break
} else {
(*state).y = tester
break
}
}
case Down:
fmt.Println("Moving down count is", count, "y value", state.y)
tester := (state.y + 1) % len(state.board)
for {
if state.board[tester][state.x] == Void {
tester = (tester + 1) % len(state.board)
continue
} else if state.board[tester][state.x] == Wall {
fmt.Println("Hit wall at tester", tester)
keepGoing = false
break
} else {
(*state).y = tester
break
}
}
default:
fmt.Println("BAD")
}
if keepGoing {
step2d(count-1, state)
}
}
func copyFace(startX int, startY int, faceSize int, board Board) Board {
retval := Board{}
for y := 0; y < faceSize; y++ {
newRow := []Tile{}
for x := 0; x < faceSize; x++ {
newRow = append(newRow, board[y+startY][x+startX])
}
retval = append(retval, newRow)
}
return retval
}
func validPoint(x int, y int, board Board) bool {
return (x >= 0) && (y >= 0) && (x < len(board[0])) && (y < len(board))
}
var faceMoves map[Face]map[Direction]Face = map[Face]map[Direction]Face{
TopFace: map[Direction]Face{
Up: BackFace,
Left: LeftFace,
Right: RightFace,
Down: FrontFace,
},
BottomFace: map[Direction]Face{
Up: FrontFace,
Left: LeftFace,
Right: RightFace,
Down: BackFace,
},
LeftFace: map[Direction]Face{
Up: TopFace,
Left: BackFace,
Right: FrontFace,
Down: BottomFace,
},
RightFace: map[Direction]Face{
Up: TopFace,
Left: FrontFace,
Right: BackFace,
Down: BottomFace,
},
FrontFace: map[Direction]Face{
Up: TopFace,
Left: LeftFace,
Right: RightFace,
Down: BottomFace,
},
BackFace: map[Direction]Face{
Up: BackFace,
Left: RightFace,
Right: FrontFace,
Down: FrontFace,
},
}
var faceName map[Face]string = map[Face]string{
TopFace: "top",
BottomFace: "bottom",
LeftFace: "left",
RightFace: "right",
FrontFace: "front",
BackFace: "back",
}
var dirName map[Direction]string = map[Direction]string{
Up: "up",
Down: "down",
Left: "left",
Right: "right",
}
var tileName map[Tile]string = map[Tile]string{
Void: "void",
Empty: "empty",
Wall: "wall",
}
type State3D struct {
x int
y int
face Face
direction Direction
board FaceMap
}
func translate(x int, y int, faceSize int, face Face, dir Direction) (resx int, resy int, resdir Direction) {
switch face {
case TopFace:
resdir = Down
switch dir {
case Right, Left:
resy = 0
resx = y
case Up, Down:
resy = 0
resx = x
}
case FrontFace:
resdir = dir
switch dir {
case Up:
resy = faceSize - 1
resx = x
case Down:
resy = 0
resx = x
case Left:
resy = y
resx = faceSize - 1
case Right:
resy = y
resx = 0
}
case LeftFace:
switch dir {
case Up:
resx = 0
resy = x
resdir = Right
case Down:
resx = 0
resy = x
resdir = Right
case Left:
resy = y
resx = faceSize - 1
resdir = Left
case Right:
resy = y
resx = 0
resdir = Right
}
case RightFace:
switch dir {
case Up:
resx = faceSize - 1
resy = x
resdir = Left
case Down:
resx = faceSize - 1
resy = x
resdir = Left
case Left:
resx = faceSize - 1
resy = y
resdir = Left
case Right:
resy = y
resx = 0
resdir = Right
}
case BackFace:
switch dir {
case Up:
resx = x
resy = 0
resdir = Down
case Down:
resx = x
resy = faceSize - 1
resdir = Up
case Left:
resx = faceSize - 1
resy = y
resdir = Right
case Right:
resy = y
resx = 0
resdir = Left
}
case BottomFace:
switch dir {
case Up:
resx = x
resy = faceSize - 1
resdir = Up
case Down:
resx = x
resy = faceSize - 1
resdir = Up
case Left:
resx = y
resy = faceSize - 1
resdir = Up
case Right:
resy = faceSize - 1
resx = y
resdir = Up
}
}
return
}
func step3d(count int, state *State3D) {
if count == 0 {
return
}
for i := 0; i < count; i++ {
newX := state.x
newY := state.y
newFace := state.face
newDirection := state.direction
switch state.direction {
case Up:
newY -= 1
case Down:
newY += 1
case Left:
newX -= 1
case Right:
newX += 1
}
if !validPoint(newX, newY, state.board[state.face]) {
newFace = faceMoves[state.face][state.direction]
newX, newY, newDirection = translate(newX, newY, len(state.board[state.face]), state.face, state.direction)
}
fmt.Printf("(%d, %d) going %s on %s ---> (%d, %d) going %s on %s [%s]\n", state.x, state.y, dirName[state.direction], faceName[state.face], newX, newY, dirName[newDirection], faceName[newFace], tileName[state.board[newFace][newY][state.x]])
if state.board[newFace][newY][newX] == Wall {
return
}
(*state).x = newX
(*state).y = newY
(*state).face = newFace
(*state).direction = newDirection
}
}
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 commands []Command
initialBoard := Board{}
for scanner.Scan() {
line := scanner.Text()
runes := []rune(line)
if len(runes) > 0 {
if runes[0] >= '0' && runes[0] <= '9' {
commands = parseCommands(runes)
} else {
initialBoard = append(initialBoard, parseBoardLine(runes))
}
}
}
file.Close()
maximumRowLength := 0
for _, row := range initialBoard {
if len(row) > maximumRowLength {
maximumRowLength = len(row)
}
}
board := Board{}
for _, row := range initialBoard {
for len(row) < maximumRowLength {
row = append(row, Void)
}
board = append(board, row)
}
printBoard(board)
startX, startY := initialPosition(board)
x, y := startX, startY
fmt.Println("initial position is", x, y)
state := State2D{x, y, Right, board}
for _, command := range commands {
fmt.Println("x", state.x, "y", state.y, "direction", dirName[state.direction])
fmt.Println("Running", command)
switch command {
case TurnRight:
state.direction = turn(state.direction, TurnRight)
case TurnLeft:
state.direction = turn(state.direction, TurnLeft)
default:
step2d(int(command), &state)
}
}
finalRow := state.y + 1
finalColumn := state.x + 1
finalPassword := 1000*finalRow + 4*finalColumn + int(state.direction)
fmt.Println("Final row is", finalRow, "final column is", finalColumn, "final password is", finalPassword)
narrowestSection := len(board[0])
for _, row := range board {
startIdx := -1
endIdx := -1
for idx, tile := range row {
if tile != Void && startIdx == -1 {
startIdx = idx
}
if tile == Void && (startIdx != -1) && (endIdx == -1) {
endIdx = idx
}
}
if endIdx == -1 {
endIdx = len(row) - 1
}
if (endIdx - startIdx) < narrowestSection {
narrowestSection = endIdx - startIdx
}
}
cubeFaceSize := narrowestSection
fmt.Println("Cube face size is", cubeFaceSize)
faceMap := FaceMap{}
currentFace := TopFace
currentSpineX := startX
currentSpineY := startY
for validPoint(currentSpineX, currentSpineY, board) {
faceMap[currentFace] = copyFace(currentSpineX, currentSpineY, cubeFaceSize, board)
fmt.Printf("Found %s face at (%d,%d)\n", faceName[currentFace], currentSpineX, currentSpineY)
leftwardsFace := faceMoves[currentFace][Left]
for leftX := currentSpineX - cubeFaceSize; validPoint(leftX, currentSpineY, board) && board[currentSpineY][leftX] != Void; leftX -= cubeFaceSize {
faceMap[leftwardsFace] = copyFace(leftX, currentSpineY, cubeFaceSize, board)
fmt.Printf("Found %s face at (%d,%d)\n", faceName[leftwardsFace], leftX, currentSpineY)
fmt.Println("Moving face", faceName[leftwardsFace], "left to", faceName[faceMoves[leftwardsFace][Left]])
leftwardsFace = faceMoves[leftwardsFace][Left]
}
rightwardsFace := faceMoves[currentFace][Right]
for rightX := currentSpineX + cubeFaceSize; validPoint(rightX, currentSpineY, board) && board[currentSpineY][rightX] != Void; rightX += cubeFaceSize {
faceMap[rightwardsFace] = copyFace(rightX, currentSpineY, cubeFaceSize, board)
fmt.Printf("Found %s face at (%d,%d)\n", faceName[rightwardsFace], rightX, currentSpineY)
fmt.Println("Moving face", faceName[rightwardsFace], "right to", faceName[faceMoves[rightwardsFace][Left]])
rightwardsFace = faceMoves[rightwardsFace][Right]
}
currentSpineY += cubeFaceSize
fmt.Println("Moving face", faceName[currentFace], "down to", faceName[faceMoves[currentFace][Down]])
currentFace = faceMoves[currentFace][Down]
}
if len(faceMap) != 6 {
fmt.Println("COULD NOT FIND ALL SIX FACES")
return
}
for face, board := range faceMap {
fmt.Println("Board", faceName[face])
printBoard(board)
}
fmt.Println("initial position is", x, y)
state3 := State3D{0, 0, TopFace, Right, faceMap}
for _, command := range commands {
fmt.Println("x", state3.x, "y", state3.y, "face", faceName[state3.face], "direction", dirName[state3.direction])
fmt.Println("Running", command)
switch command {
case TurnRight:
state3.direction = turn(state3.direction, TurnRight)
case TurnLeft:
state3.direction = turn(state3.direction, TurnLeft)
default:
step3d(int(command), &state3)
}
}
finalRow = state3.y + 1
finalColumn = state3.x + 1
finalPassword = 1000*finalRow + 4*finalColumn + int(state.direction)
fmt.Println("Final row is", finalRow, "final column is", finalColumn, "final password is", finalPassword)
}