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) }