package day17 import ( "bufio" "fmt" "os" "strconv" "strings" ) type Piece []string var horizontalBar Piece = Piece{"@@@@"} var plus Piece = Piece{".@.", "@@@", ".@."} var backwardsL Piece = Piece{"..@", "..@", "@@@"} var verticalBar Piece = Piece{"@", "@", "@", "@"} var block Piece = Piece{"@@", "@@"} var pieces []Piece = []Piece{horizontalBar, plus, backwardsL, verticalBar, block} type Board [][]rune func newBoard() Board { return Board{} } func pieceToRows(piece Piece) Board { retval := Board{} for i := range piece { newRow := append([]rune(".."), []rune(piece[i])...) for len(newRow) < 7 { newRow = append(newRow, '.') } retval = append(retval, newRow) } return retval } func rowEmpty(row int, board Board) bool { if row >= len(board) { return false } for i := 0; i < 7; i++ { if board[row][i] != '.' { return false } } return true } func addPiece(piece Piece, board *Board) { rows := pieceToRows(piece) for !(rowEmpty(0, *board) && rowEmpty(1, *board) && rowEmpty(2, *board)) { *board = append(Board{[]rune(".......")}, *board...) } for len(*board) >= 3 && rowEmpty(3, *board) { *board = (*board)[1:] } *board = append(rows, *board...) } func printBoard(board *Board) { for _, row := range *board { fmt.Printf("|") for _, r := range row { fmt.Printf("%c", r) } fmt.Printf("|\n") } fmt.Printf("+-------+\n") } func copyBoard(clearPiece bool, currentBoard *Board, newBoard *Board) { for len(*newBoard) < len(*currentBoard) { *newBoard = append(Board{[]rune(".......")}, *newBoard...) } for len(*newBoard) > len(*currentBoard) { *newBoard = (*newBoard)[1:] } for y, row := range *currentBoard { for x, value := range row { if clearPiece && value == '@' { (*newBoard)[y][x] = '.' } else { (*newBoard)[y][x] = (*currentBoard)[y][x] } } } } func movePiece(direction rune, currentBoard *Board, newBoard *Board) bool { /// match the values copyBoard(true, currentBoard, newBoard) for y, row := range *currentBoard { for x, value := range row { if value == '@' { newX := x newY := y switch direction { case '<': newX = x - 1 case '>': newX = x + 1 case 'v': newY = y + 1 default: fmt.Printf("PANIC: Invalid direction %c\n", direction) return false } if newX < 0 || newX >= 7 || newY >= len(*currentBoard) { return false } if (*currentBoard)[newY][newX] == '#' { return false } (*newBoard)[newY][newX] = '@' } } } return true } func freezeBoard(board *Board) int { for _, row := range *board { for x, value := range row { if value == '@' { row[x] = '#' } } } for y := 0; y < len(*board)-3; y++ { allCovered := true for x := 0; x < 7; x++ { allCovered = allCovered && ((*board)[y][x] == '#' || (*board)[y+1][x] == '#' || (*board)[y+2][x] == '#' || (*board)[y+3][x] == '#') } if allCovered { originalLength := len(*board) //fmt.Println("--- original board ---") //printBoard(board) *board = (*board)[0 : y+4] //fmt.Println("--- cleaned board ---") //printBoard(board) //fmt.Println("---", originalLength-len(*board), "rows removed ---") removedRows := originalLength - len(*board) //if removedRows > 0 { // fmt.Println("Removed", removedRows, "rows.") //} return removedRows } } return 0 } func sameBoard(board1 Board, board2 Board) bool { if len(board1) != len(board2) { return false } for y, row := range board1 { for x := range row { if board1[y][x] != board2[y][x] { return false } } } return true } func duplicateBoard(board Board) Board { newBoard := Board{} for _, row := range board { newRow := []rune(".......") copy(newRow, row) newBoard = append(newBoard, newRow) } return newBoard } type PreviousState struct { moveIndex int pieceIndex int } type PreviousLocation struct { dropNo int removedRows int board Board } func Run(filename string, targetStr string) { targetPieces, err := strconv.Atoi(targetStr) if err != nil { fmt.Println("PANIC: Don't understand target number") return } file, err := os.Open(filename) if err != nil { fmt.Println("Error opening file:", err) return } scanner := bufio.NewScanner(file) scanner.Split(bufio.ScanLines) instructions := []rune{} for scanner.Scan() { line := scanner.Text() line = strings.TrimFunc(line, func(r rune) bool { return r != '>' && r != '<' }) instructions = append(instructions, []rune(line)...) } file.Close() board1 := newBoard() board2 := newBoard() moveIndex := 0 pieceIndex := 0 previousStates := map[PreviousState][]PreviousLocation{} removedRows := 0 skippedAhead := false for dropNo := 1; dropNo <= targetPieces; dropNo++ { //fmt.Println("Dropping piece", dropNo, "with piece", pieceIndex, "and move", moveIndex) if !skippedAhead { state := PreviousState{moveIndex, pieceIndex} previousLocations, previousStateExisted := previousStates[state] if previousStateExisted { for _, location := range previousLocations { if sameBoard(board1, location.board) { dropDifference := dropNo - location.dropNo removedRowsDifference := removedRows - location.removedRows fmt.Println("Got it, current drop", dropNo, "previous version", location.dropNo, "removed row diff", removedRowsDifference) repeats := (targetPieces - dropNo) / dropDifference fmt.Println("Drop difference of", dropDifference, "meaning we can do this gap", repeats, "more times") fmt.Println("removed rows", removedRows, "+", repeats*removedRowsDifference, "=", removedRows+(repeats*removedRowsDifference)) removedRows += repeats * removedRowsDifference fmt.Println("Drop number", dropNo, "+", repeats*dropDifference, "=", dropNo+(repeats*dropDifference)) dropNo += repeats*dropDifference - 1 skippedAhead = true break } } if skippedAhead { continue } else { previousStates[state] = append(previousStates[state], PreviousLocation{dropNo, removedRows, duplicateBoard(board1)}) } } else { previousStates[state] = []PreviousLocation{{dropNo, removedRows, duplicateBoard(board1)}} } } addPiece(pieces[pieceIndex], &board1) for { //fmt.Println("Move", moveIndex, "is", string(instructions[moveIndex])) if !movePiece(instructions[moveIndex], &board1, &board2) { copyBoard(false, &board1, &board2) } moveIndex = (moveIndex + 1) % len(instructions) if !movePiece('v', &board2, &board1) { copyBoard(false, &board2, &board1) break } } removedRows += freezeBoard(&board1) pieceIndex = (pieceIndex + 1) % len(pieces) } //printBoard(&board1) fmt.Println("Removed", removedRows, "rows.") rowsWithRocks := removedRows for i := 0; i < len(board1); i++ { if !rowEmpty(i, board1) { rowsWithRocks += 1 } } fmt.Println("Rocks are", rowsWithRocks, "rows tall.") }