package main import ( "fmt" "time" "github.com/gdamore/tcell" ) // NewBoard creates a new clear board func NewBoard() { board = &Board{} board.Clear() } // ChangeBoardSize changes board size func ChangeBoardSize(width int, height int) { if board.width == width && board.height == height { return } newBoard := &Board{width: width, height: height, boardsIndex: board.boardsIndex} newBoard.colors = make([][]tcell.Color, width) for i := 0; i < width; i++ { newBoard.colors[i] = make([]tcell.Color, height) for j := 0; j < height; j++ { if i < board.width && j < board.height { newBoard.colors[i][j] = board.colors[i][j] } else { newBoard.colors[i][j] = colorBlank } } } newBoard.rotation = make([][]int, width) for i := 0; i < width; i++ { newBoard.rotation[i] = make([]int, height) for j := 0; j < height; j++ { if i < board.width && j < board.height { newBoard.rotation[i][j] = board.rotation[i][j] } else { break } } } board = newBoard board.fullLinesY = make([]bool, board.height) board.previewMino = NewMino() board.currentMino = NewMino() } // Clear clears the board to original state func (board *Board) Clear() { board.width = len(boards[board.boardsIndex].colors) board.height = len(boards[board.boardsIndex].colors[0]) board.colors = make([][]tcell.Color, board.width) for i := 0; i < board.width; i++ { board.colors[i] = make([]tcell.Color, board.height) copy(board.colors[i], boards[board.boardsIndex].colors[i]) } board.rotation = make([][]int, board.width) for i := 0; i < board.width; i++ { board.rotation[i] = make([]int, board.height) copy(board.rotation[i], boards[board.boardsIndex].rotation[i]) } board.fullLinesY = make([]bool, board.height) board.previewMino = NewMino() board.currentMino = NewMino() } // EmptyBoard removes all blocks/colors from the board func (board *Board) EmptyBoard() { for i := 0; i < board.width; i++ { for j := 0; j < board.height; j++ { board.colors[i][j] = colorBlank } } for i := 0; i < board.width; i++ { for j := 0; j < board.height; j++ { board.rotation[i][j] = 0 } } } // PreviousBoard switches to previous board func (board *Board) PreviousBoard() { board.boardsIndex-- if board.boardsIndex < 0 { board.boardsIndex = len(boards) - 1 } engine.PreviewBoard() board.Clear() } // NextBoard switches to next board func (board *Board) NextBoard() { board.boardsIndex++ if board.boardsIndex == len(boards) { board.boardsIndex = 0 } engine.PreviewBoard() board.Clear() } // MinoMoveLeft moves mino left func (board *Board) MinoMoveLeft() { board.dropDistance = 0 mino := board.currentMino.CloneMoveLeft() if mino.ValidLocation(false) { board.currentMino = mino board.StartLockDelayIfBottom() } } // MinoMoveRight moves mino right func (board *Board) MinoMoveRight() { board.dropDistance = 0 mino := board.currentMino.CloneMoveRight() if mino.ValidLocation(false) { board.currentMino = mino board.StartLockDelayIfBottom() } } // MinoRotateRight rotates mino right func (board *Board) MinoRotateRight() { board.dropDistance = 0 mino := board.currentMino.CloneRotateRight() if mino.ValidLocation(false) { board.currentMino = mino board.StartLockDelayIfBottom() return } mino.MoveLeft() if mino.ValidLocation(false) { board.currentMino = mino board.StartLockDelayIfBottom() return } mino.MoveRight() mino.MoveRight() if mino.ValidLocation(false) { board.currentMino = mino board.StartLockDelayIfBottom() return } } // MinoRotateLeft rotates mino right func (board *Board) MinoRotateLeft() { board.dropDistance = 0 mino := board.currentMino.CloneRotateLeft() if mino.ValidLocation(false) { board.currentMino = mino board.StartLockDelayIfBottom() return } mino.MoveLeft() if mino.ValidLocation(false) { board.currentMino = mino board.StartLockDelayIfBottom() return } mino.MoveRight() mino.MoveRight() if mino.ValidLocation(false) { board.currentMino = mino board.StartLockDelayIfBottom() return } } // MinoMoveDown moves mino down func (board *Board) MinoMoveDown() { mino := board.currentMino.CloneMoveDown() if mino.ValidLocation(false) { board.dropDistance = 0 board.currentMino = mino if !board.StartLockDelayIfBottom() { engine.ResetTimer(0) } return } if !board.currentMino.ValidLocation(true) { engine.GameOver() return } board.nextMino() } // MinoDrop dropps mino func (board *Board) MinoDrop() { board.dropDistance = 0 mino := board.currentMino.CloneMoveDown() for mino.ValidLocation(false) { board.dropDistance++ mino.MoveDown() } for i := 0; i < board.dropDistance; i++ { board.currentMino.MoveDown() } if !board.currentMino.ValidLocation(true) { engine.GameOver() return } if board.dropDistance < 1 { return } if !board.StartLockDelayIfBottom() { engine.ResetTimer(0) } } // StartLockDelayIfBottom if at bottom, starts lock delay func (board *Board) StartLockDelayIfBottom() bool { mino := board.currentMino.CloneMoveDown() if mino.ValidLocation(false) { return false } engine.ResetTimer(300 * time.Millisecond) return true } // nextMino gets next mino func (board *Board) nextMino() { engine.AddScore(board.dropDistance) board.currentMino.SetOnBoard() board.deleteCheck() if !board.previewMino.ValidLocation(false) { board.previewMino.MoveUp() if !board.previewMino.ValidLocation(false) { engine.GameOver() return } } board.currentMino = board.previewMino board.previewMino = NewMino() engine.AiGetBestQueue() engine.ResetTimer(0) } // deleteCheck checks if there are any lines on the board that can be deleted func (board *Board) deleteCheck() { lines := board.fullLines() if len(lines) < 1 { return } view.ShowDeleteAnimation(lines) for _, line := range lines { board.deleteLine(line) } engine.AddDeleteLines(len(lines)) } // fullLines returns the line numbers that have full lines func (board *Board) fullLines() []int { fullLines := make([]int, 0, 1) for j := 0; j < board.height; j++ { if board.isFullLine(j) { fullLines = append(fullLines, j) } } return fullLines } // isFullLine checks if line is full func (board *Board) isFullLine(j int) bool { for i := 0; i < board.width; i++ { if board.colors[i][j] == colorBlank { return false } } return true } // deleteLine deletes the line func (board *Board) deleteLine(line int) { for i := 0; i < board.width; i++ { board.colors[i][line] = colorBlank } for j := line; j > 0; j-- { for i := 0; i < board.width; i++ { board.colors[i][j] = board.colors[i][j-1] board.rotation[i][j] = board.rotation[i][j-1] } } for i := 0; i < board.width; i++ { board.colors[i][0] = colorBlank } } // SetColor sets the color and rotation of board location func (board *Board) SetColor(x int, y int, color tcell.Color, rotation int) { board.colors[x][y] = color if rotation < 0 { return } board.rotation[x][y] = rotation } // RotateLeft rotates cell left func (board *Board) RotateLeft(x int, y int) { if board.rotation[x][y] == 0 { board.rotation[x][y] = 3 return } board.rotation[x][y]-- } // RotateRight rotates cell right func (board *Board) RotateRight(x int, y int) { if board.rotation[x][y] == 3 { board.rotation[x][y] = 0 return } board.rotation[x][y]++ } // ValidBlockLocation checks if block location is vaild func (board *Board) ValidBlockLocation(x int, y int, mustBeOnBoard bool) bool { if x < 0 || x >= board.width || y >= board.height { return false } if mustBeOnBoard { if y < 0 { return false } } else { if y < -2 { return false } } if y > -1 { if board.colors[x][y] != colorBlank { return false } } return true } // ValidDisplayLocation checks if vaild display location func ValidDisplayLocation(x int, y int) bool { return x >= 0 && x < board.width && y >= 0 && y < board.height } // DrawBoard draws the board with help from view func (board *Board) DrawBoard() { for i := 0; i < board.width; i++ { for j := 0; j < board.height; j++ { if board.colors[i][j] != colorBlank { view.DrawBlock(i, j, board.colors[i][j], board.rotation[i][j]) } } } } // DrawPreviewMino draws the preview mino func (board *Board) DrawPreviewMino() { board.previewMino.DrawMino(MinoPreview) } // DrawCurrentMino draws the current mino func (board *Board) DrawCurrentMino() { board.currentMino.DrawMino(MinoCurrent) } // DrawDropMino draws the drop mino func (board *Board) DrawDropMino() { mino := board.currentMino.CloneMoveDown() if !mino.ValidLocation(false) { return } for mino.ValidLocation(false) { mino.MoveDown() } mino.MoveUp() mino.DrawMino(MinoDrop) } // DrawCursor draws the edit cursor func (board *Board) DrawCursor(x int, y int) { view.DrawCursor(x, y, board.colors[x][y]) } // printDebugBoard is for printing board in text for debuging func (board *Board) printDebugBoard() { for j := 0; j < board.height; j++ { for i := 0; i < board.width; i++ { switch board.colors[i][j] { case colorBlank: fmt.Print(".") case colorBlue: fmt.Print("B") case colorCyan: fmt.Print("C") case colorGreen: fmt.Print("G") case colorMagenta: fmt.Print("M") case colorRed: fmt.Print("R") case colorWhite: fmt.Print("W") case colorYellow: fmt.Print("Y") default: fmt.Print("U") } } fmt.Println("") } } // getDebugBoard returns board as string for debuging and testing func (board *Board) getDebugBoard() []string { lines := make([]string, board.height) for j := 0; j < board.height; j++ { for i := 0; i < board.width; i++ { switch board.colors[i][j] { case colorBlank: lines[j] += "." case colorBlue: lines[j] += "B" case colorCyan: lines[j] += "C" case colorGreen: lines[j] += "G" case colorMagenta: lines[j] += "M" case colorRed: lines[j] += "R" case colorWhite: lines[j] += "W" case colorYellow: lines[j] += "Y" default: lines[j] += "U" } } } return lines } // getDebugBoardWithMino returns board with mino placed on it func (board *Board) getDebugBoardWithMino(mino *Mino) []string { lines := make([]string, board.height) for j := 0; j < board.height; j++ { for i := 0; i < board.width; i++ { switch mino.getMinoColorAtLocation(i, j) { case colorBlank: switch board.colors[i][j] { case colorBlank: lines[j] += "." case colorBlue: lines[j] += "B" case colorCyan: lines[j] += "C" case colorGreen: lines[j] += "G" case colorMagenta: lines[j] += "M" case colorRed: lines[j] += "R" case colorWhite: lines[j] += "W" case colorYellow: lines[j] += "Y" default: lines[j] += "U" } case colorBlue: lines[j] += "b" case colorCyan: lines[j] += "c" case colorGreen: lines[j] += "g" case colorMagenta: lines[j] += "m" case colorRed: lines[j] += "r" case colorWhite: lines[j] += "w" case colorYellow: lines[j] += "y" default: lines[j] += "u" } } } return lines }