302 lines
6.8 KiB
Go
302 lines
6.8 KiB
Go
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.")
|
|
}
|