636 lines
12 KiB
Go
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)
|
|
|
|
}
|