go-tetris/board.go

498 lines
11 KiB
Go
Raw Normal View History

2017-03-27 15:07:29 -05:00
package main
import (
"fmt"
"time"
"github.com/nsf/termbox-go"
2017-03-27 15:07:29 -05:00
)
2018-10-02 20:21:08 -05:00
// NewBoard creates a new clear board
func NewBoard() {
board = &Board{}
board.Clear()
2017-03-27 15:07:29 -05:00
}
2019-01-09 20:30:45 -06:00
// 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([][]termbox.Attribute, width)
for i := 0; i < width; i++ {
newBoard.colors[i] = make([]termbox.Attribute, 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] = blankColor
}
}
}
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
2019-01-11 20:39:41 -06:00
board.fullLinesY = make([]bool, board.height)
2019-01-09 20:30:45 -06:00
board.previewMino = NewMino()
board.currentMino = NewMino()
}
// Clear clears the board to orginal state
func (board *Board) Clear() {
board.width = len(boards[board.boardsIndex].colors)
board.height = len(boards[board.boardsIndex].colors[0])
2019-01-09 20:30:45 -06:00
board.colors = make([][]termbox.Attribute, board.width)
for i := 0; i < board.width; i++ {
board.colors[i] = make([]termbox.Attribute, board.height)
copy(board.colors[i], boards[board.boardsIndex].colors[i])
}
2019-01-09 20:30:45 -06:00
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])
2017-03-27 15:07:29 -05:00
}
2019-01-11 20:39:41 -06:00
board.fullLinesY = make([]bool, board.height)
2017-03-27 15:07:29 -05:00
board.previewMino = NewMino()
board.currentMino = NewMino()
}
2019-01-09 20:30:45 -06:00
// 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] = blankColor
}
}
for i := 0; i < board.width; i++ {
for j := 0; j < board.height; j++ {
board.rotation[i][j] = 0
}
}
}
2018-10-15 14:53:57 -05:00
// PreviousBoard switches to previous board
func (board *Board) PreviousBoard() {
board.boardsIndex--
if board.boardsIndex < 0 {
board.boardsIndex = len(boards) - 1
}
engine.PreviewBoard()
board.Clear()
}
2018-10-15 14:53:57 -05:00
// NextBoard switches to next board
func (board *Board) NextBoard() {
board.boardsIndex++
if board.boardsIndex == len(boards) {
board.boardsIndex = 0
}
engine.PreviewBoard()
board.Clear()
2017-03-27 15:07:29 -05:00
}
2018-10-02 20:21:08 -05:00
// MinoMoveLeft moves mino left
2017-03-27 15:07:29 -05:00
func (board *Board) MinoMoveLeft() {
board.dropDistance = 0
mino := board.currentMino.CloneMoveLeft()
if mino.ValidLocation(false) {
board.currentMino = mino
board.StartLockDelayIfBottom()
}
}
2018-10-02 20:21:08 -05:00
// MinoMoveRight moves mino right
2017-03-27 15:07:29 -05:00
func (board *Board) MinoMoveRight() {
board.dropDistance = 0
mino := board.currentMino.CloneMoveRight()
if mino.ValidLocation(false) {
board.currentMino = mino
board.StartLockDelayIfBottom()
}
}
2018-10-02 20:21:08 -05:00
// MinoRotateRight rotates mino right
2017-03-27 15:07:29 -05:00
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
}
}
2018-10-02 20:21:08 -05:00
// MinoRotateLeft rotates mino right
2017-03-27 15:07:29 -05:00
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
}
}
2018-10-02 20:21:08 -05:00
// MinoMoveDown moves mino down
2017-03-27 15:07:29 -05:00
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()
}
2018-10-02 20:21:08 -05:00
// MinoDrop dropps mino
2017-03-27 15:07:29 -05:00
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)
}
}
2018-10-02 20:21:08 -05:00
// StartLockDelayIfBottom if at bottom, starts lock delay
2017-03-27 15:07:29 -05:00
func (board *Board) StartLockDelayIfBottom() bool {
mino := board.currentMino.CloneMoveDown()
if mino.ValidLocation(false) {
return false
}
engine.ResetTimer(300 * time.Millisecond)
return true
}
2018-10-02 20:21:08 -05:00
// nextMino gets next mino
2017-03-27 15:07:29 -05:00
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()
2017-03-27 15:07:29 -05:00
engine.ResetTimer(0)
}
2018-10-02 20:21:08 -05:00
// deleteCheck checks if there are any lines on the board that can be deleted
2017-03-27 15:07:29 -05:00
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))
}
2018-10-02 20:21:08 -05:00
// fullLines returns the line numbers that have full lines
2017-03-27 15:07:29 -05:00
func (board *Board) fullLines() []int {
fullLines := make([]int, 0, 1)
for j := 0; j < board.height; j++ {
2017-03-27 15:07:29 -05:00
if board.isFullLine(j) {
fullLines = append(fullLines, j)
}
}
return fullLines
}
2018-10-02 20:21:08 -05:00
// isFullLine checks if line is full
2017-03-27 15:07:29 -05:00
func (board *Board) isFullLine(j int) bool {
for i := 0; i < board.width; i++ {
2017-03-27 15:07:29 -05:00
if board.colors[i][j] == blankColor {
return false
}
}
return true
}
2018-10-02 20:21:08 -05:00
// deleteLine deletes the line
2017-03-27 15:07:29 -05:00
func (board *Board) deleteLine(line int) {
for i := 0; i < board.width; i++ {
2017-03-27 15:07:29 -05:00
board.colors[i][line] = blankColor
}
for j := line; j > 0; j-- {
for i := 0; i < board.width; i++ {
2017-03-27 15:07:29 -05:00
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++ {
2017-03-27 15:07:29 -05:00
board.colors[i][0] = blankColor
}
}
2018-10-02 20:21:08 -05:00
// SetColor sets the color and rotation of board location
2017-03-27 15:07:29 -05:00
func (board *Board) SetColor(x int, y int, color termbox.Attribute, rotation int) {
board.colors[x][y] = color
2019-01-09 20:30:45 -06:00
if rotation < 0 {
return
}
2017-03-27 15:07:29 -05:00
board.rotation[x][y] = rotation
}
2019-01-09 20:30:45 -06:00
// 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]++
}
2018-10-02 20:21:08 -05:00
// ValidBlockLocation checks if block location is vaild
2018-05-25 21:02:00 -05:00
func (board *Board) ValidBlockLocation(x int, y int, mustBeOnBoard bool) bool {
if x < 0 || x >= board.width || y >= board.height {
2017-03-27 15:07:29 -05:00
return false
}
if mustBeOnBoard {
if y < 0 {
return false
}
} else {
if y < -2 {
return false
}
}
if y > -1 {
if board.colors[x][y] != blankColor {
return false
}
}
return true
}
2018-10-02 20:21:08 -05:00
// ValidDisplayLocation checks if vaild display location
2017-03-27 15:07:29 -05:00
func ValidDisplayLocation(x int, y int) bool {
return x >= 0 && x < board.width && y >= 0 && y < board.height
2017-03-27 15:07:29 -05:00
}
2018-10-02 20:21:08 -05:00
// DrawBoard draws the board with help from view
2017-03-27 15:07:29 -05:00
func (board *Board) DrawBoard() {
for i := 0; i < board.width; i++ {
for j := 0; j < board.height; j++ {
2017-03-27 15:07:29 -05:00
if board.colors[i][j] != blankColor {
view.DrawBlock(i, j, board.colors[i][j], board.rotation[i][j])
}
}
}
}
2018-10-02 20:21:08 -05:00
// DrawPreviewMino draws the preview mino
2017-03-27 15:07:29 -05:00
func (board *Board) DrawPreviewMino() {
board.previewMino.DrawMino(MinoPreview)
}
2018-10-02 20:21:08 -05:00
// DrawCurrentMino draws the current mino
2017-03-27 15:07:29 -05:00
func (board *Board) DrawCurrentMino() {
board.currentMino.DrawMino(MinoCurrent)
}
2018-10-02 20:21:08 -05:00
// DrawDropMino draws the drop mino
2017-03-27 15:07:29 -05:00
func (board *Board) DrawDropMino() {
mino := board.currentMino.CloneMoveDown()
if !mino.ValidLocation(false) {
return
}
for mino.ValidLocation(false) {
mino.MoveDown()
}
mino.MoveUp()
mino.DrawMino(MinoDrop)
}
2019-01-09 20:30:45 -06:00
// DrawCursor draws the edit cursor
func (board *Board) DrawCursor(x int, y int) {
view.DrawCursor(x, y, board.colors[x][y])
}
2018-10-02 20:21:08 -05:00
// 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++ {
2017-03-27 15:07:29 -05:00
switch board.colors[i][j] {
case blankColor:
fmt.Print(".")
case termbox.ColorBlue:
fmt.Print("B")
case termbox.ColorCyan:
fmt.Print("C")
case termbox.ColorGreen:
fmt.Print("G")
case termbox.ColorMagenta:
fmt.Print("M")
case termbox.ColorRed:
fmt.Print("R")
case termbox.ColorWhite:
fmt.Print("W")
case termbox.ColorYellow:
fmt.Print("Y")
default:
fmt.Print("U")
2017-03-27 15:07:29 -05:00
}
}
fmt.Println("")
}
}
2018-10-02 20:21:08 -05:00
// 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 blankColor:
lines[j] += "."
case termbox.ColorBlue:
lines[j] += "B"
case termbox.ColorCyan:
lines[j] += "C"
case termbox.ColorGreen:
lines[j] += "G"
case termbox.ColorMagenta:
lines[j] += "M"
case termbox.ColorRed:
lines[j] += "R"
case termbox.ColorWhite:
lines[j] += "W"
case termbox.ColorYellow:
lines[j] += "Y"
default:
lines[j] += "U"
}
}
}
return lines
}
2018-10-02 20:21:08 -05:00
// 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 blankColor:
switch board.colors[i][j] {
case blankColor:
lines[j] += "."
case termbox.ColorBlue:
lines[j] += "B"
case termbox.ColorCyan:
lines[j] += "C"
case termbox.ColorGreen:
lines[j] += "G"
case termbox.ColorMagenta:
lines[j] += "M"
case termbox.ColorRed:
lines[j] += "R"
case termbox.ColorWhite:
lines[j] += "W"
case termbox.ColorYellow:
lines[j] += "Y"
default:
lines[j] += "U"
}
case termbox.ColorBlue:
lines[j] += "b"
case termbox.ColorCyan:
lines[j] += "c"
case termbox.ColorGreen:
lines[j] += "g"
case termbox.ColorMagenta:
lines[j] += "m"
case termbox.ColorRed:
lines[j] += "r"
case termbox.ColorWhite:
lines[j] += "w"
case termbox.ColorYellow:
lines[j] += "y"
default:
lines[j] += "u"
}
}
}
return lines
}