parent
f67b7198e6
commit
e9ec8cd1dd
21
README.md
21
README.md
|
@ -15,7 +15,7 @@ go install github.com/MichaelS11/tetris
|
||||||
Then run the binary created, tetris or tetris.exe
|
Then run the binary created, tetris or tetris.exe
|
||||||
|
|
||||||
|
|
||||||
## Keys
|
## Keys during game
|
||||||
|
|
||||||
| Key | Action |
|
| Key | Action |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
|
@ -30,6 +30,15 @@ Then run the binary created, tetris or tetris.exe
|
||||||
| q | quit |
|
| q | quit |
|
||||||
| i | toggle AI |
|
| i | toggle AI |
|
||||||
|
|
||||||
|
## Keys start screen
|
||||||
|
|
||||||
|
| Key | Action |
|
||||||
|
| --- | --- |
|
||||||
|
| ← | previous board |
|
||||||
|
| → | next board |
|
||||||
|
| spacebar | start game |
|
||||||
|
| q | quit |
|
||||||
|
|
||||||
|
|
||||||
## Features include
|
## Features include
|
||||||
|
|
||||||
|
@ -38,6 +47,7 @@ Then run the binary created, tetris or tetris.exe
|
||||||
- Next piece
|
- Next piece
|
||||||
- Ghost piece
|
- Ghost piece
|
||||||
- Top scores
|
- Top scores
|
||||||
|
- Board choices
|
||||||
|
|
||||||
|
|
||||||
## Screenshots
|
## Screenshots
|
||||||
|
@ -46,4 +56,11 @@ Then run the binary created, tetris or tetris.exe
|
||||||
|
|
||||||
![alt text](https://raw.githubusercontent.com/MichaelS11/tetris/master/screenshots/screenshot2.png "Golang Tetris")
|
![alt text](https://raw.githubusercontent.com/MichaelS11/tetris/master/screenshots/screenshot2.png "Golang Tetris")
|
||||||
|
|
||||||
![alt text](https://raw.githubusercontent.com/MichaelS11/tetris/master/screenshots/screenshot3.png "Tetris Top Scores")
|
![alt text](https://raw.githubusercontent.com/MichaelS11/tetris/master/screenshots/screenshot3.png "Golang Tetris Heart")
|
||||||
|
|
||||||
|
![alt text](https://raw.githubusercontent.com/MichaelS11/tetris/master/screenshots/screenshot4.png "Tetris High Scores")
|
||||||
|
|
||||||
|
|
||||||
|
## To do
|
||||||
|
|
||||||
|
* Improve AI speed (slow on large boards)
|
||||||
|
|
169
ai.go
169
ai.go
|
@ -1,26 +1,22 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
const (
|
|
||||||
aiQueueSize = (boardWidth+1)/2 + 6
|
|
||||||
aiMoveLength = (boardWidth + 1) / 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type Ai struct {
|
|
||||||
queue [aiQueueSize]rune
|
|
||||||
index int
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAi() *Ai {
|
func NewAi() *Ai {
|
||||||
ai := Ai{}
|
ai := Ai{}
|
||||||
for i := 0; i < aiQueueSize; i++ {
|
queue := make([]rune, 1)
|
||||||
ai.queue[i] = 'x'
|
queue[0] = 'x'
|
||||||
}
|
ai.queue = &queue
|
||||||
return &ai
|
return &ai
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ai *Ai) ProcessQueue() bool {
|
func (ai *Ai) ProcessQueue() {
|
||||||
|
if ai.newQueue != nil {
|
||||||
|
ai.queue = ai.newQueue
|
||||||
|
ai.newQueue = nil
|
||||||
|
ai.index = 0
|
||||||
|
}
|
||||||
|
queue := *ai.queue
|
||||||
// wasd + qe keyboard keys
|
// wasd + qe keyboard keys
|
||||||
switch ai.queue[ai.index] {
|
switch queue[ai.index] {
|
||||||
case 'w':
|
case 'w':
|
||||||
board.MinoDrop()
|
board.MinoDrop()
|
||||||
case 'a':
|
case 'a':
|
||||||
|
@ -32,51 +28,31 @@ func (ai *Ai) ProcessQueue() bool {
|
||||||
case 'e':
|
case 'e':
|
||||||
board.MinoRotateRight()
|
board.MinoRotateRight()
|
||||||
case 'x':
|
case 'x':
|
||||||
return false
|
return
|
||||||
}
|
}
|
||||||
ai.index++
|
ai.index++
|
||||||
if ai.index == aiQueueSize {
|
|
||||||
ai.index = 0
|
|
||||||
}
|
|
||||||
view.RefreshScreen()
|
view.RefreshScreen()
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ai *Ai) GetBestQueue() {
|
func (ai *Ai) GetBestQueue() {
|
||||||
ai.addMovesToQueue(ai.getBestQueue())
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ai *Ai) addMovesToQueue(queue []rune) {
|
|
||||||
insertIndex := ai.index
|
|
||||||
for _, char := range queue {
|
|
||||||
ai.queue[insertIndex] = char
|
|
||||||
insertIndex++
|
|
||||||
if insertIndex == aiQueueSize {
|
|
||||||
insertIndex = 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ai *Ai) getBestQueue() []rune {
|
|
||||||
bestQueue := make([]rune, 0, 0)
|
|
||||||
bestScore := -9999999
|
bestScore := -9999999
|
||||||
var slideScore int
|
bestQueue := make([]rune, 0, 0)
|
||||||
bestSlide := 6
|
currentMino := *board.currentMino
|
||||||
|
|
||||||
for move1 := 0; move1 <= boardWidth; move1++ {
|
for slide1 := 0; slide1 < 5; slide1++ {
|
||||||
|
for move1 := board.width; move1 >= 0; move1-- {
|
||||||
for rotate1 := 0; rotate1 < 5; rotate1++ {
|
for rotate1 := 0; rotate1 < 5; rotate1++ {
|
||||||
for slide1 := 0; slide1 <= 5; slide1++ {
|
|
||||||
|
|
||||||
queue, mino1 := ai.getMovesforMino(rotate1, move1, slide1, nil)
|
queue, mino1 := board.getMovesforMino(rotate1, move1, slide1, ¤tMino, nil)
|
||||||
if mino1 == nil {
|
if mino1 == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
for move2 := 0; move2 <= boardWidth; move2++ {
|
for slide2 := 0; slide2 < 5; slide2++ {
|
||||||
|
for move2 := board.width; move2 >= 0; move2-- {
|
||||||
for rotate2 := 0; rotate2 < 5; rotate2++ {
|
for rotate2 := 0; rotate2 < 5; rotate2++ {
|
||||||
for slide2 := 0; slide2 <= 5; slide2++ {
|
|
||||||
|
|
||||||
_, mino2 := ai.getMovesforMino(rotate2, move2, slide2, mino1)
|
_, mino2 := board.getMovesforMino(rotate2, move2, slide2, board.previewMino, mino1)
|
||||||
if mino2 == nil {
|
if mino2 == nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -84,16 +60,9 @@ func (ai *Ai) getBestQueue() []rune {
|
||||||
fullLines, holes, bumpy := board.boardStatsWithMinos(mino1, mino2)
|
fullLines, holes, bumpy := board.boardStatsWithMinos(mino1, mino2)
|
||||||
score := ai.getScoreFromBoardStats(fullLines, holes, bumpy)
|
score := ai.getScoreFromBoardStats(fullLines, holes, bumpy)
|
||||||
|
|
||||||
if slide1 < 3 {
|
if score > bestScore {
|
||||||
slideScore = slide1
|
|
||||||
} else {
|
|
||||||
slideScore = slide1 - 2
|
|
||||||
}
|
|
||||||
|
|
||||||
if score > bestScore || (score == bestScore && slideScore < bestSlide) {
|
|
||||||
bestScore = score
|
bestScore = score
|
||||||
bestQueue = queue
|
bestQueue = queue
|
||||||
bestSlide = slideScore
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -104,100 +73,95 @@ func (ai *Ai) getBestQueue() []rune {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return bestQueue
|
if len(bestQueue) < 1 {
|
||||||
|
bestQueue = append(bestQueue, 'x')
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ai *Ai) getMovesforMino(rotate int, move int, slide int, mino1 *Mino) ([]rune, *Mino) {
|
ai.newQueue = &bestQueue
|
||||||
|
}
|
||||||
|
|
||||||
|
func (board *Board) getMovesforMino(rotate int, move int, slide int, mino1 *Mino, mino2 *Mino) ([]rune, *Mino) {
|
||||||
queue := make([]rune, 0, 4)
|
queue := make([]rune, 0, 4)
|
||||||
var mino Mino
|
mino := *mino1
|
||||||
if mino1 != nil {
|
|
||||||
mino = *board.previewMino
|
if rotate%2 == 0 {
|
||||||
} else {
|
rotate /= 2
|
||||||
mino = *board.currentMino
|
|
||||||
}
|
|
||||||
if rotate < 3 {
|
|
||||||
for i := 0; i < rotate; i++ {
|
for i := 0; i < rotate; i++ {
|
||||||
mino.RotateRight()
|
mino.RotateRight()
|
||||||
queue = append(queue, 'e')
|
queue = append(queue, 'e')
|
||||||
if !mino.ValidLocation(false) || (mino1 != nil && mino1.minoOverlap(&mino)) {
|
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
|
||||||
return queue, nil
|
return queue, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for i := 0; i < rotate-2; i++ {
|
rotate = rotate/2 + 1
|
||||||
|
for i := 0; i < rotate; i++ {
|
||||||
mino.RotateLeft()
|
mino.RotateLeft()
|
||||||
queue = append(queue, 'q')
|
queue = append(queue, 'q')
|
||||||
if !mino.ValidLocation(false) || (mino1 != nil && mino1.minoOverlap(&mino)) {
|
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
|
||||||
return queue, nil
|
return queue, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if move <= aiMoveLength {
|
|
||||||
move = aiMoveLength - move
|
if move%2 == 0 {
|
||||||
|
move /= 2
|
||||||
for i := 0; i < move; i++ {
|
for i := 0; i < move; i++ {
|
||||||
mino.MoveLeft()
|
mino.MoveLeft()
|
||||||
queue = append(queue, 'a')
|
queue = append(queue, 'a')
|
||||||
if !mino.ValidLocation(false) || (mino1 != nil && mino1.minoOverlap(&mino)) {
|
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
|
||||||
return queue, nil
|
return queue, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
move = move - aiMoveLength + 1
|
move = move/2 + 1
|
||||||
for i := 0; i < move; i++ {
|
for i := 0; i < move; i++ {
|
||||||
mino.MoveRight()
|
mino.MoveRight()
|
||||||
queue = append(queue, 'd')
|
queue = append(queue, 'd')
|
||||||
if !mino.ValidLocation(false) || (mino1 != nil && mino1.minoOverlap(&mino)) {
|
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
|
||||||
return queue, nil
|
return queue, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for mino.ValidLocation(false) && (mino1 == nil || !mino1.minoOverlap(&mino)) {
|
for mino.ValidLocation(false) && (mino2 == nil || !mino2.minoOverlap(&mino)) {
|
||||||
mino.MoveDown()
|
mino.MoveDown()
|
||||||
}
|
}
|
||||||
mino.MoveUp()
|
mino.MoveUp()
|
||||||
queue = append(queue, 'w')
|
queue = append(queue, 'w')
|
||||||
if slide < 3 {
|
|
||||||
|
if slide%2 == 0 {
|
||||||
|
slide /= 2
|
||||||
for i := 0; i < slide; i++ {
|
for i := 0; i < slide; i++ {
|
||||||
mino.MoveLeft()
|
mino.MoveLeft()
|
||||||
queue = append(queue, 'a')
|
queue = append(queue, 'a')
|
||||||
if !mino.ValidLocation(false) || (mino1 != nil && mino1.minoOverlap(&mino)) {
|
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
|
||||||
return queue, nil
|
return queue, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
slide = slide - 2
|
slide = slide/2 + 1
|
||||||
for i := 0; i < slide; i++ {
|
for i := 0; i < slide; i++ {
|
||||||
mino.MoveRight()
|
mino.MoveRight()
|
||||||
queue = append(queue, 'd')
|
queue = append(queue, 'd')
|
||||||
if !mino.ValidLocation(false) || (mino1 != nil && mino1.minoOverlap(&mino)) {
|
if !mino.ValidLocation(false) || (mino2 != nil && mino2.minoOverlap(&mino)) {
|
||||||
return queue, nil
|
return queue, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !mino.ValidLocation(true) {
|
||||||
|
return queue, nil
|
||||||
|
}
|
||||||
queue = append(queue, 'x')
|
queue = append(queue, 'x')
|
||||||
return queue, &mino
|
return queue, &mino
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mino *Mino) minoOverlap(mino1 *Mino) bool {
|
|
||||||
minoBlocks := mino.minoRotation[mino.rotation]
|
|
||||||
for i := 0; i < mino.length; i++ {
|
|
||||||
for j := 0; j < mino.length; j++ {
|
|
||||||
if minoBlocks[i][j] != blankColor {
|
|
||||||
if mino1.isMinoAtLocation(mino.x+i, mino.y+j) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (board *Board) boardStatsWithMinos(mino1 *Mino, mino2 *Mino) (fullLines int, holes int, bumpy int) {
|
func (board *Board) boardStatsWithMinos(mino1 *Mino, mino2 *Mino) (fullLines int, holes int, bumpy int) {
|
||||||
// fullLines
|
// fullLines
|
||||||
fullLinesY := make(map[int]bool, 2)
|
fullLinesY := make(map[int]bool, 2)
|
||||||
for j := 0; j < boardHeight; j++ {
|
for j := 0; j < board.height; j++ {
|
||||||
isFullLine := true
|
isFullLine := true
|
||||||
for i := 0; i < boardWidth; i++ {
|
for i := 0; i < board.width; i++ {
|
||||||
if board.colors[i][j] == blankColor && !mino1.isMinoAtLocation(i, j) && !mino2.isMinoAtLocation(i, j) {
|
if board.colors[i][j] == blankColor && !mino1.isMinoAtLocation(i, j) && !mino2.isMinoAtLocation(i, j) {
|
||||||
isFullLine = false
|
isFullLine = false
|
||||||
break
|
break
|
||||||
|
@ -211,10 +175,10 @@ func (board *Board) boardStatsWithMinos(mino1 *Mino, mino2 *Mino) (fullLines int
|
||||||
|
|
||||||
// holes and bumpy
|
// holes and bumpy
|
||||||
indexLast := 0
|
indexLast := 0
|
||||||
for i := 0; i < boardWidth; i++ {
|
for i := 0; i < board.width; i++ {
|
||||||
index := boardHeight
|
index := board.height
|
||||||
indexOffset := 0
|
indexOffset := 0
|
||||||
for j := 0; j < boardHeight; j++ {
|
for j := 0; j < board.height; j++ {
|
||||||
if _, found := fullLinesY[j]; found {
|
if _, found := fullLinesY[j]; found {
|
||||||
indexOffset++
|
indexOffset++
|
||||||
} else {
|
} else {
|
||||||
|
@ -236,7 +200,7 @@ func (board *Board) boardStatsWithMinos(mino1 *Mino, mino2 *Mino) (fullLines int
|
||||||
indexLast = index + fullLines - indexOffset
|
indexLast = index + fullLines - indexOffset
|
||||||
|
|
||||||
index++
|
index++
|
||||||
for j := index; j < boardHeight; j++ {
|
for j := index; j < board.height; j++ {
|
||||||
if board.colors[i][j] == blankColor && !mino1.isMinoAtLocation(i, j) && !mino2.isMinoAtLocation(i, j) {
|
if board.colors[i][j] == blankColor && !mino1.isMinoAtLocation(i, j) && !mino2.isMinoAtLocation(i, j) {
|
||||||
holes++
|
holes++
|
||||||
}
|
}
|
||||||
|
@ -245,24 +209,9 @@ func (board *Board) boardStatsWithMinos(mino1 *Mino, mino2 *Mino) (fullLines int
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mino *Mino) isMinoAtLocation(x int, y int) bool {
|
|
||||||
xIndex := x - mino.x
|
|
||||||
yIndex := y - mino.y
|
|
||||||
if xIndex < 0 || xIndex >= mino.length || yIndex < 0 || yIndex >= mino.length {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
minoBlocks := mino.minoRotation[mino.rotation]
|
|
||||||
if minoBlocks[xIndex][yIndex] != blankColor {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ai *Ai) getScoreFromBoardStats(fullLines int, holes int, bumpy int) (score int) {
|
func (ai *Ai) getScoreFromBoardStats(fullLines int, holes int, bumpy int) (score int) {
|
||||||
if fullLines == 4 {
|
if fullLines == 4 {
|
||||||
score += 256
|
score += 512
|
||||||
}
|
}
|
||||||
score -= 75 * holes
|
score -= 75 * holes
|
||||||
score -= 25 * bumpy
|
score -= 25 * bumpy
|
||||||
|
|
729
ai_test.go
729
ai_test.go
|
@ -1,494 +1,291 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gopkg.in/inconshreveable/log15.v2"
|
|
||||||
"os"
|
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMain(m *testing.M) {
|
type testAiStruct struct {
|
||||||
setupForTesting()
|
info string
|
||||||
retCode := m.Run()
|
minos []testMinoStruct
|
||||||
os.Exit(retCode)
|
fullLines int
|
||||||
|
holes int
|
||||||
|
bumpy int
|
||||||
}
|
}
|
||||||
|
|
||||||
func setupForTesting() {
|
func TestAI(t *testing.T) {
|
||||||
logger = log15.New()
|
// this must be set to the blank 10x20 board
|
||||||
logger.SetHandler(log15.StreamHandler(os.Stdout, log15.LogfmtFormat()))
|
board.boardsIndex = 0
|
||||||
|
board.Clear()
|
||||||
|
|
||||||
engine = NewEngine()
|
tests := []testAiStruct{
|
||||||
|
{info: "fullLines 2x minoI", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 0, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 4, y: 18}, // minoI
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 1},
|
||||||
|
{info: "fullLines 2x2 minoI", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 0, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 4, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 0, y: 17}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 4, y: 17}, // minoI
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 2},
|
||||||
|
{info: "fullLines 2x minoI minoO", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 0, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 4, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 8, y: 18}, // minoO
|
||||||
|
}, fullLines: 1, holes: 0, bumpy: 1},
|
||||||
|
{info: "fullLines 2x2 minoI minoO", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 0, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 4, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 0, y: 17}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 4, y: 17}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 8, y: 18}, // minoO
|
||||||
|
}, fullLines: 2, holes: 0, bumpy: 0},
|
||||||
|
{info: "fullLines 5x minoO", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 2, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 4, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 6, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 8, y: 18}, // minoO
|
||||||
|
}, fullLines: 2, holes: 0, bumpy: 0},
|
||||||
|
{info: "fullLines 4x4 minoI 2x minoO", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 0, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 4, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 0, y: 17}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 4, y: 17}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 0, y: 16}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 4, y: 16}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 0, y: 15}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 4, y: 15}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 8, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 8, y: 16}, // minoO
|
||||||
|
}, fullLines: 4, holes: 0, bumpy: 0},
|
||||||
|
{info: "holes 2x minoI minoO", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 0, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 6, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 4, y: 17}, // minoO
|
||||||
|
}, fullLines: 0, holes: 2, bumpy: 4},
|
||||||
|
{info: "holes 6x minoO", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 4, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 8, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 2, y: 16}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 6, y: 16}, // minoO
|
||||||
|
}, fullLines: 0, holes: 8, bumpy: 8},
|
||||||
|
{info: "holes 4x minoT 2x minoI", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[5], x: 0, y: 18}, // minoT
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[5], x: 7, y: 18}, // minoT
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[5], x: 3, y: 16}, // minoT
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[5], x: 7, y: 16}, // minoT
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 2, y: 14}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 6, y: 14}, // minoI
|
||||||
|
}, fullLines: 0, holes: 19, bumpy: 4},
|
||||||
|
{info: "holes 3x minoZ", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[6], x: 0, y: 18}, // minoZ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[6], x: 3, y: 18}, // minoZ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[6], x: 6, y: 18}, // minoZ
|
||||||
|
}, fullLines: 0, holes: 3, bumpy: 6},
|
||||||
|
{info: "bumpy 2x minoT - 1", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[5], x: 0, y: 18}, // minoT
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[5], x: 5, y: 18}, // minoT
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 7},
|
||||||
|
{info: "bumpy 2x minoT - 2", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[5], x: 1, y: 18}, // minoT
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[5], x: 6, y: 18}, // minoT
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 8},
|
||||||
|
{info: "bumpy 2x minoT - 3", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[5], x: 2, y: 18}, // minoT
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[5], x: 7, y: 18}, // minoT
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 7},
|
||||||
|
{info: "bumpy 2x minoJ - 1", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 0, y: 18}, // minoJ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 5, y: 18}, // minoJ
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 6},
|
||||||
|
{info: "bumpy 2x minoJ - 2", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 1, y: 18}, // minoJ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 6, y: 18}, // minoJ
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 8},
|
||||||
|
{info: "bumpy 2x minoJ - 2", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 2, y: 18}, // minoJ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 7, y: 18}, // minoJ
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 7},
|
||||||
|
{info: "bumpy 2x minoL - 1", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 0, y: 18}, // minoL
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 5, y: 18}, // minoL
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 7},
|
||||||
|
{info: "bumpy 2x minoL - 2", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 1, y: 18}, // minoL
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 6, y: 18}, // minoL
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 8},
|
||||||
|
{info: "bumpy 2x minoL - 3", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 2, y: 18}, // minoL
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 7, y: 18}, // minoL
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 6},
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoardStatsFullLines1(t *testing.T) {
|
runAiTests(t, tests)
|
||||||
board = NewBoard()
|
|
||||||
|
|
||||||
// minoO
|
|
||||||
mino1 := &Mino{
|
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
}
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 0
|
|
||||||
mino1.y = 18
|
|
||||||
mino1.SetOnBoard()
|
|
||||||
|
|
||||||
// minoI
|
func TestBigBoardAI(t *testing.T) {
|
||||||
mino1 = &Mino{
|
// this must be set to the blank 20x20 board
|
||||||
minoRotation: minoBag[0],
|
board.boardsIndex = 3
|
||||||
}
|
board.Clear()
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 2
|
|
||||||
mino1.y = 18
|
|
||||||
|
|
||||||
// minoI
|
tests := []testAiStruct{
|
||||||
mino2 := &Mino{
|
{info: "fullLines 4x minoI", minos: []testMinoStruct{
|
||||||
minoRotation: minoBag[0],
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 0, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 4, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 8, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 12, y: 18}, // minoI
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 1},
|
||||||
|
{info: "fullLines 5x minoI", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 0, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 4, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 8, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 12, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 16, y: 18}, // minoI
|
||||||
|
}, fullLines: 1, holes: 0, bumpy: 0},
|
||||||
|
{info: "fullLines 5x2 minoI", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 0, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 4, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 8, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 12, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 16, y: 18}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 0, y: 17}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 4, y: 17}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 8, y: 17}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 12, y: 17}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 16, y: 17}, // minoI
|
||||||
|
}, fullLines: 2, holes: 0, bumpy: 0},
|
||||||
|
{info: "fullLines 9x minoO", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 2, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 4, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 6, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 8, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 10, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 12, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 14, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 16, y: 18}, // minoO
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 2},
|
||||||
|
{info: "fullLines 10x minoO", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 2, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 4, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 6, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 8, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 10, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 12, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 14, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 16, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 18, y: 18}, // minoO
|
||||||
|
}, fullLines: 2, holes: 0, bumpy: 0},
|
||||||
|
{info: "holes 3x minoO 3x minoI", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 6, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[3], x: 12, y: 18}, // minoO
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 2, y: 16}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 8, y: 16}, // minoI
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[0], x: 14, y: 16}, // minoI
|
||||||
|
}, fullLines: 0, holes: 24, bumpy: 8},
|
||||||
|
{info: "holes 5x minoZ", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[6], x: 0, y: 18}, // minoZ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[6], x: 4, y: 18}, // minoZ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[6], x: 8, y: 18}, // minoZ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[6], x: 12, y: 18}, // minoZ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[6], x: 16, y: 18}, // minoZ
|
||||||
|
}, fullLines: 0, holes: 5, bumpy: 18},
|
||||||
|
{info: "bumpy 4x minoJ - 1", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 0, y: 18}, // minoJ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 5, y: 18}, // minoJ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 10, y: 18}, // minoJ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 15, y: 18}, // minoJ
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 14},
|
||||||
|
{info: "bumpy 4x minoJ - 2", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 1, y: 18}, // minoJ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 6, y: 18}, // minoJ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 11, y: 18}, // minoJ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 16, y: 18}, // minoJ
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 16},
|
||||||
|
{info: "bumpy 4x minoJ - 3", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 2, y: 18}, // minoJ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 7, y: 18}, // minoJ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 12, y: 18}, // minoJ
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[1], x: 17, y: 18}, // minoJ
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 15},
|
||||||
|
{info: "bumpy 4x minoL - 1", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 0, y: 18}, // minoL
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 5, y: 18}, // minoL
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 10, y: 18}, // minoL
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 15, y: 18}, // minoL
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 15},
|
||||||
|
{info: "bumpy 4x minoL - 2", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 1, y: 18}, // minoL
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 6, y: 18}, // minoL
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 11, y: 18}, // minoL
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 16, y: 18}, // minoL
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 16},
|
||||||
|
{info: "bumpy 4x minoL - 3", minos: []testMinoStruct{
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 2, y: 18}, // minoL
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 7, y: 18}, // minoL
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 12, y: 18}, // minoL
|
||||||
|
testMinoStruct{minoRotation: minos.minoBag[2], x: 17, y: 18}, // minoL
|
||||||
|
}, fullLines: 0, holes: 0, bumpy: 14},
|
||||||
|
}
|
||||||
|
|
||||||
|
runAiTests(t, tests)
|
||||||
|
}
|
||||||
|
|
||||||
|
func runAiTests(t *testing.T, tests []testAiStruct) {
|
||||||
|
var mino1 *Mino
|
||||||
|
var mino2 *Mino
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
board.Clear()
|
||||||
|
|
||||||
|
for i, minoTest := range test.minos {
|
||||||
|
mino := NewMino()
|
||||||
|
mino.minoRotation = minoTest.minoRotation
|
||||||
|
mino.length = len(mino.minoRotation[0])
|
||||||
|
mino.x = minoTest.x
|
||||||
|
mino.y = minoTest.y
|
||||||
|
if i < len(test.minos)-2 {
|
||||||
|
mino.SetOnBoard()
|
||||||
|
} else if i == len(test.minos)-2 {
|
||||||
|
mino1 = mino
|
||||||
|
} else {
|
||||||
|
mino2 = mino
|
||||||
|
}
|
||||||
}
|
}
|
||||||
mino2.length = len(mino2.minoRotation[0])
|
|
||||||
mino2.x = 6
|
|
||||||
mino2.y = 18
|
|
||||||
|
|
||||||
fullLines, holes, bumpy := board.boardStatsWithMinos(mino1, mino2)
|
fullLines, holes, bumpy := board.boardStatsWithMinos(mino1, mino2)
|
||||||
expected := 1
|
|
||||||
if fullLines != expected {
|
|
||||||
t.Error("fullLines expected", expected, "got", fullLines)
|
|
||||||
}
|
|
||||||
expected = 0
|
|
||||||
if holes != expected {
|
|
||||||
t.Error("holes expected", expected, "got", holes)
|
|
||||||
}
|
|
||||||
expected = 1
|
|
||||||
if bumpy != expected {
|
|
||||||
t.Error("bumpy expected", expected, "got", bumpy)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for debuging
|
if fullLines != test.fullLines {
|
||||||
// mino1.SetOnBoard()
|
|
||||||
// mino2.SetOnBoard()
|
|
||||||
// board.drawDebugBoard()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoardStatsFullLines2(t *testing.T) {
|
|
||||||
board = NewBoard()
|
|
||||||
|
|
||||||
// minoO
|
|
||||||
mino1 := &Mino{
|
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 0
|
|
||||||
mino1.y = 18
|
|
||||||
mino1.SetOnBoard()
|
mino1.SetOnBoard()
|
||||||
|
lines := board.getDebugBoardWithMino(mino2)
|
||||||
// minoO
|
for i := 0; i < len(lines); i++ {
|
||||||
mino1 = &Mino{
|
t.Log(lines[i])
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
}
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
t.Errorf("AI fullLines - received: %v - expected: %v - info %v", fullLines, test.fullLines, test.info)
|
||||||
mino1.x = 2
|
continue
|
||||||
mino1.y = 18
|
}
|
||||||
|
if holes != test.holes {
|
||||||
mino1.SetOnBoard()
|
mino1.SetOnBoard()
|
||||||
|
lines := board.getDebugBoardWithMino(mino2)
|
||||||
// minoO
|
for i := 0; i < len(lines); i++ {
|
||||||
mino1 = &Mino{
|
t.Log(lines[i])
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
}
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
t.Errorf("AI holes - received: %v - expected: %v - info %v", holes, test.holes, test.info)
|
||||||
mino1.x = 4
|
continue
|
||||||
mino1.y = 18
|
}
|
||||||
|
if bumpy != test.bumpy {
|
||||||
mino1.SetOnBoard()
|
mino1.SetOnBoard()
|
||||||
|
lines := board.getDebugBoardWithMino(mino2)
|
||||||
// minoO
|
for i := 0; i < len(lines); i++ {
|
||||||
mino1 = &Mino{
|
t.Log(lines[i])
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
}
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
t.Errorf("AI bumpy - received: %v - expected: %v - info %v", bumpy, test.bumpy, test.info)
|
||||||
mino1.x = 6
|
continue
|
||||||
mino1.y = 18
|
|
||||||
mino1.SetOnBoard()
|
|
||||||
|
|
||||||
// minoO
|
|
||||||
mino1 = &Mino{
|
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 8
|
|
||||||
mino1.y = 18
|
|
||||||
mino1.SetOnBoard()
|
|
||||||
|
|
||||||
// minoO
|
|
||||||
mino1 = &Mino{
|
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 0
|
|
||||||
mino1.y = 16
|
|
||||||
mino1.SetOnBoard()
|
|
||||||
|
|
||||||
// minoI
|
|
||||||
mino1 = &Mino{
|
|
||||||
minoRotation: minoBag[0],
|
|
||||||
}
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 2
|
|
||||||
mino1.y = 16
|
|
||||||
|
|
||||||
// minoI
|
|
||||||
mino2 := &Mino{
|
|
||||||
minoRotation: minoBag[0],
|
|
||||||
}
|
|
||||||
mino2.length = len(mino2.minoRotation[0])
|
|
||||||
mino2.x = 6
|
|
||||||
mino2.y = 16
|
|
||||||
|
|
||||||
fullLines, holes, bumpy := board.boardStatsWithMinos(mino1, mino2)
|
|
||||||
expected := 3
|
|
||||||
if fullLines != expected {
|
|
||||||
t.Error("fullLines expected", expected, "got", fullLines)
|
|
||||||
}
|
|
||||||
expected = 0
|
|
||||||
if holes != expected {
|
|
||||||
t.Error("holes expected", expected, "got", holes)
|
|
||||||
}
|
|
||||||
expected = 1
|
|
||||||
if bumpy != expected {
|
|
||||||
t.Error("bumpy expected", expected, "got", bumpy)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// for debuging
|
|
||||||
// mino1.SetOnBoard()
|
|
||||||
// mino2.SetOnBoard()
|
|
||||||
// board.drawDebugBoard()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestBoardStatsFullLines3(t *testing.T) {
|
|
||||||
board = NewBoard()
|
|
||||||
|
|
||||||
// minoO
|
|
||||||
mino1 := &Mino{
|
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 0
|
|
||||||
mino1.y = 18
|
|
||||||
mino1.SetOnBoard()
|
|
||||||
|
|
||||||
// minoI
|
|
||||||
mino1 = &Mino{
|
|
||||||
minoRotation: minoBag[0],
|
|
||||||
}
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 0
|
|
||||||
mino1.y = 16
|
|
||||||
mino1.SetOnBoard()
|
|
||||||
|
|
||||||
// minoO
|
|
||||||
mino1 = &Mino{
|
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 4
|
|
||||||
mino1.y = 18
|
|
||||||
mino1.SetOnBoard()
|
|
||||||
|
|
||||||
// minoI
|
|
||||||
mino1 = &Mino{
|
|
||||||
minoRotation: minoBag[0],
|
|
||||||
}
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 4
|
|
||||||
mino1.y = 16
|
|
||||||
mino1.SetOnBoard()
|
|
||||||
|
|
||||||
// minoO
|
|
||||||
mino1 = &Mino{
|
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 8
|
|
||||||
mino1.y = 18
|
|
||||||
|
|
||||||
// minoO
|
|
||||||
mino2 := &Mino{
|
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
|
||||||
mino2.length = len(mino2.minoRotation[0])
|
|
||||||
mino2.x = 8
|
|
||||||
mino2.y = 16
|
|
||||||
|
|
||||||
fullLines, holes, bumpy := board.boardStatsWithMinos(mino1, mino2)
|
|
||||||
expected := 1
|
|
||||||
if fullLines != expected {
|
|
||||||
t.Error("fullLines expected", expected, "got", fullLines)
|
|
||||||
}
|
|
||||||
expected = 0
|
|
||||||
if holes != expected {
|
|
||||||
t.Error("holes expected", expected, "got", holes)
|
|
||||||
}
|
|
||||||
expected = 9
|
|
||||||
if bumpy != expected {
|
|
||||||
t.Error("bumpy expected", expected, "got", bumpy)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for debuging
|
|
||||||
// mino1.SetOnBoard()
|
|
||||||
// mino2.SetOnBoard()
|
|
||||||
// board.drawDebugBoard()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoardStatsBumpy1(t *testing.T) {
|
|
||||||
board = NewBoard()
|
|
||||||
|
|
||||||
// minoO
|
|
||||||
mino1 := &Mino{
|
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 0
|
|
||||||
mino1.y = 18
|
|
||||||
|
|
||||||
// minoO
|
|
||||||
mino2 := &Mino{
|
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
|
||||||
mino2.length = len(mino2.minoRotation[0])
|
|
||||||
mino2.x = 0
|
|
||||||
mino2.y = 16
|
|
||||||
|
|
||||||
fullLines, holes, bumpy := board.boardStatsWithMinos(mino1, mino2)
|
|
||||||
expected := 0
|
|
||||||
if fullLines != expected {
|
|
||||||
t.Error("fullLines expected", expected, "got", fullLines)
|
|
||||||
}
|
|
||||||
expected = 0
|
|
||||||
if holes != expected {
|
|
||||||
t.Error("holes expected", expected, "got", holes)
|
|
||||||
}
|
|
||||||
expected = 4
|
|
||||||
if bumpy != expected {
|
|
||||||
t.Error("bumpy expected", expected, "got", bumpy)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for debuging
|
|
||||||
// mino1.SetOnBoard()
|
|
||||||
// mino2.SetOnBoard()
|
|
||||||
// board.drawDebugBoard()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoardStatsBumpy2(t *testing.T) {
|
|
||||||
board = NewBoard()
|
|
||||||
|
|
||||||
// minoO
|
|
||||||
mino1 := &Mino{
|
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 0
|
|
||||||
mino1.y = 18
|
|
||||||
mino1.SetOnBoard()
|
|
||||||
|
|
||||||
// minoI
|
|
||||||
mino1 = &Mino{
|
|
||||||
minoRotation: minoBag[0],
|
|
||||||
}
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 0
|
|
||||||
mino1.y = 16
|
|
||||||
|
|
||||||
// minoI
|
|
||||||
mino2 := &Mino{
|
|
||||||
minoRotation: minoBag[0],
|
|
||||||
}
|
|
||||||
mino2.length = len(mino2.minoRotation[0])
|
|
||||||
mino2.x = 0
|
|
||||||
mino2.y = 15
|
|
||||||
|
|
||||||
fullLines, holes, bumpy := board.boardStatsWithMinos(mino1, mino2)
|
|
||||||
expected := 0
|
|
||||||
if fullLines != expected {
|
|
||||||
t.Error("fullLines expected", expected, "got", fullLines)
|
|
||||||
}
|
|
||||||
expected = 4
|
|
||||||
if holes != expected {
|
|
||||||
t.Error("holes expected", expected, "got", holes)
|
|
||||||
}
|
|
||||||
expected = 4
|
|
||||||
if bumpy != expected {
|
|
||||||
t.Error("bumpy expected", expected, "got", bumpy)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for debuging
|
|
||||||
// mino1.SetOnBoard()
|
|
||||||
// mino2.SetOnBoard()
|
|
||||||
// board.drawDebugBoard()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoardStatsBumpy3(t *testing.T) {
|
|
||||||
board = NewBoard()
|
|
||||||
|
|
||||||
// minoO
|
|
||||||
mino1 := &Mino{
|
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 0
|
|
||||||
mino1.y = 18
|
|
||||||
mino1.SetOnBoard()
|
|
||||||
|
|
||||||
// minoO
|
|
||||||
mino1 = &Mino{
|
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 1
|
|
||||||
mino1.y = 16
|
|
||||||
|
|
||||||
// minoO
|
|
||||||
mino2 := &Mino{
|
|
||||||
minoRotation: minoBag[3],
|
|
||||||
}
|
|
||||||
mino2.length = len(mino2.minoRotation[0])
|
|
||||||
mino2.x = 2
|
|
||||||
mino2.y = 14
|
|
||||||
|
|
||||||
fullLines, holes, bumpy := board.boardStatsWithMinos(mino1, mino2)
|
|
||||||
expected := 0
|
|
||||||
if fullLines != expected {
|
|
||||||
t.Error("fullLines expected", expected, "got", fullLines)
|
|
||||||
}
|
|
||||||
expected = 6
|
|
||||||
if holes != expected {
|
|
||||||
t.Error("holes expected", expected, "got", holes)
|
|
||||||
}
|
|
||||||
expected = 10
|
|
||||||
if bumpy != expected {
|
|
||||||
t.Error("bumpy expected", expected, "got", bumpy)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for debuging
|
|
||||||
// mino1.SetOnBoard()
|
|
||||||
// mino2.SetOnBoard()
|
|
||||||
// board.drawDebugBoard()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoardStatsBumpy4(t *testing.T) {
|
|
||||||
board = NewBoard()
|
|
||||||
|
|
||||||
// minoI
|
|
||||||
mino1 := &Mino{
|
|
||||||
minoRotation: minoBag[0],
|
|
||||||
}
|
|
||||||
mino1.rotation = 1
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = 6
|
|
||||||
mino1.y = 16
|
|
||||||
|
|
||||||
// minoI
|
|
||||||
mino2 := &Mino{
|
|
||||||
minoRotation: minoBag[0],
|
|
||||||
}
|
|
||||||
mino2.rotation = 1
|
|
||||||
mino2.length = len(mino2.minoRotation[0])
|
|
||||||
mino2.x = 6
|
|
||||||
mino2.y = 12
|
|
||||||
|
|
||||||
fullLines, holes, bumpy := board.boardStatsWithMinos(mino1, mino2)
|
|
||||||
expected := 0
|
|
||||||
if fullLines != expected {
|
|
||||||
t.Error("fullLines expected", expected, "got", fullLines)
|
|
||||||
}
|
|
||||||
expected = 0
|
|
||||||
if holes != expected {
|
|
||||||
t.Error("holes expected", expected, "got", holes)
|
|
||||||
}
|
|
||||||
expected = 16
|
|
||||||
if bumpy != expected {
|
|
||||||
t.Error("bumpy expected", expected, "got", bumpy)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for debuging
|
|
||||||
// mino1.SetOnBoard()
|
|
||||||
// mino2.SetOnBoard()
|
|
||||||
// board.drawDebugBoard()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoardStatsholes1(t *testing.T) {
|
|
||||||
board = NewBoard()
|
|
||||||
|
|
||||||
// minoJ
|
|
||||||
mino1 := &Mino{
|
|
||||||
minoRotation: minoBag[1],
|
|
||||||
}
|
|
||||||
mino1.rotation = 1
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = -1
|
|
||||||
mino1.y = 17
|
|
||||||
|
|
||||||
// minoJ
|
|
||||||
mino2 := &Mino{
|
|
||||||
minoRotation: minoBag[1],
|
|
||||||
}
|
|
||||||
mino2.rotation = 1
|
|
||||||
mino2.length = len(mino2.minoRotation[0])
|
|
||||||
mino2.x = 1
|
|
||||||
mino2.y = 17
|
|
||||||
|
|
||||||
fullLines, holes, bumpy := board.boardStatsWithMinos(mino1, mino2)
|
|
||||||
expected := 0
|
|
||||||
if fullLines != expected {
|
|
||||||
t.Error("fullLines expected", expected, "got", fullLines)
|
|
||||||
}
|
|
||||||
expected = 4
|
|
||||||
if holes != expected {
|
|
||||||
t.Error("holes expected", expected, "got", holes)
|
|
||||||
}
|
|
||||||
expected = 3
|
|
||||||
if bumpy != expected {
|
|
||||||
t.Error("bumpy expected", expected, "got", bumpy)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for debuging
|
|
||||||
// mino1.SetOnBoard()
|
|
||||||
// mino2.SetOnBoard()
|
|
||||||
// board.drawDebugBoard()
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestBoardStatsholes2(t *testing.T) {
|
|
||||||
board = NewBoard()
|
|
||||||
|
|
||||||
// minoJ
|
|
||||||
mino1 := &Mino{
|
|
||||||
minoRotation: minoBag[1],
|
|
||||||
}
|
|
||||||
mino1.rotation = 1
|
|
||||||
mino1.length = len(mino1.minoRotation[0])
|
|
||||||
mino1.x = -1
|
|
||||||
mino1.y = 17
|
|
||||||
|
|
||||||
// minoJ
|
|
||||||
mino2 := &Mino{
|
|
||||||
minoRotation: minoBag[1],
|
|
||||||
}
|
|
||||||
mino2.rotation = 1
|
|
||||||
mino2.length = len(mino2.minoRotation[0])
|
|
||||||
mino2.x = -1
|
|
||||||
mino2.y = 14
|
|
||||||
|
|
||||||
fullLines, holes, bumpy := board.boardStatsWithMinos(mino1, mino2)
|
|
||||||
expected := 0
|
|
||||||
if fullLines != expected {
|
|
||||||
t.Error("fullLines expected", expected, "got", fullLines)
|
|
||||||
}
|
|
||||||
expected = 4
|
|
||||||
if holes != expected {
|
|
||||||
t.Error("holes expected", expected, "got", holes)
|
|
||||||
}
|
|
||||||
expected = 6
|
|
||||||
if bumpy != expected {
|
|
||||||
t.Error("bumpy expected", expected, "got", bumpy)
|
|
||||||
}
|
|
||||||
|
|
||||||
// for debuging
|
|
||||||
// mino1.SetOnBoard()
|
|
||||||
// mino2.SetOnBoard()
|
|
||||||
// board.drawDebugBoard()
|
|
||||||
}
|
}
|
||||||
|
|
159
board.go
159
board.go
|
@ -2,33 +2,49 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/nsf/termbox-go"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/nsf/termbox-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Board struct {
|
func NewBoard() {
|
||||||
colors [boardWidth][boardHeight]termbox.Attribute
|
board = &Board{}
|
||||||
rotation [boardWidth][boardHeight]int
|
board.Clear()
|
||||||
previewMino *Mino
|
|
||||||
currentMino *Mino
|
|
||||||
dropDistance int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBoard() *Board {
|
func (board *Board) Clear() {
|
||||||
board := &Board{}
|
board.width = len(boards[board.boardsIndex].colors)
|
||||||
for i := 0; i < boardWidth; i++ {
|
board.height = len(boards[board.boardsIndex].colors[0])
|
||||||
for j := 0; j < boardHeight; j++ {
|
board.colors = make([][]termbox.Attribute, len(boards[board.boardsIndex].colors))
|
||||||
board.colors[i][j] = blankColor
|
for i := 0; i < len(boards[board.boardsIndex].colors); i++ {
|
||||||
}
|
board.colors[i] = make([]termbox.Attribute, len(boards[board.boardsIndex].colors[i]))
|
||||||
}
|
copy(board.colors[i], boards[board.boardsIndex].colors[i])
|
||||||
for i := 0; i < boardWidth; i++ {
|
|
||||||
for j := 0; j < boardHeight; j++ {
|
|
||||||
board.rotation[i][j] = 0
|
|
||||||
}
|
}
|
||||||
|
board.rotation = make([][]int, len(boards[board.boardsIndex].rotation))
|
||||||
|
for i := 0; i < len(boards[board.boardsIndex].rotation); i++ {
|
||||||
|
board.rotation[i] = make([]int, len(boards[board.boardsIndex].rotation[i]))
|
||||||
|
copy(board.rotation[i], boards[board.boardsIndex].rotation[i])
|
||||||
}
|
}
|
||||||
board.previewMino = NewMino()
|
board.previewMino = NewMino()
|
||||||
board.currentMino = NewMino()
|
board.currentMino = NewMino()
|
||||||
return board
|
}
|
||||||
|
|
||||||
|
func (board *Board) PreviousBoard() {
|
||||||
|
board.boardsIndex--
|
||||||
|
if board.boardsIndex < 0 {
|
||||||
|
board.boardsIndex = len(boards) - 1
|
||||||
|
}
|
||||||
|
engine.PreviewBoard()
|
||||||
|
board.Clear()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (board *Board) NextBoard() {
|
||||||
|
board.boardsIndex++
|
||||||
|
if board.boardsIndex == len(boards) {
|
||||||
|
board.boardsIndex = 0
|
||||||
|
}
|
||||||
|
engine.PreviewBoard()
|
||||||
|
board.Clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (board *Board) MinoMoveLeft() {
|
func (board *Board) MinoMoveLeft() {
|
||||||
|
@ -160,7 +176,7 @@ func (board *Board) nextMino() {
|
||||||
|
|
||||||
board.currentMino = board.previewMino
|
board.currentMino = board.previewMino
|
||||||
board.previewMino = NewMino()
|
board.previewMino = NewMino()
|
||||||
engine.ResetAiTimer()
|
engine.AiGetBestQueue()
|
||||||
engine.ResetTimer(0)
|
engine.ResetTimer(0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,7 +196,7 @@ func (board *Board) deleteCheck() {
|
||||||
|
|
||||||
func (board *Board) fullLines() []int {
|
func (board *Board) fullLines() []int {
|
||||||
fullLines := make([]int, 0, 1)
|
fullLines := make([]int, 0, 1)
|
||||||
for j := 0; j < boardHeight; j++ {
|
for j := 0; j < board.height; j++ {
|
||||||
if board.isFullLine(j) {
|
if board.isFullLine(j) {
|
||||||
fullLines = append(fullLines, j)
|
fullLines = append(fullLines, j)
|
||||||
}
|
}
|
||||||
|
@ -189,7 +205,7 @@ func (board *Board) fullLines() []int {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (board *Board) isFullLine(j int) bool {
|
func (board *Board) isFullLine(j int) bool {
|
||||||
for i := 0; i < boardWidth; i++ {
|
for i := 0; i < board.width; i++ {
|
||||||
if board.colors[i][j] == blankColor {
|
if board.colors[i][j] == blankColor {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -198,16 +214,16 @@ func (board *Board) isFullLine(j int) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (board *Board) deleteLine(line int) {
|
func (board *Board) deleteLine(line int) {
|
||||||
for i := 0; i < boardWidth; i++ {
|
for i := 0; i < board.width; i++ {
|
||||||
board.colors[i][line] = blankColor
|
board.colors[i][line] = blankColor
|
||||||
}
|
}
|
||||||
for j := line; j > 0; j-- {
|
for j := line; j > 0; j-- {
|
||||||
for i := 0; i < boardWidth; i++ {
|
for i := 0; i < board.width; i++ {
|
||||||
board.colors[i][j] = board.colors[i][j-1]
|
board.colors[i][j] = board.colors[i][j-1]
|
||||||
board.rotation[i][j] = board.rotation[i][j-1]
|
board.rotation[i][j] = board.rotation[i][j-1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for i := 0; i < boardWidth; i++ {
|
for i := 0; i < board.width; i++ {
|
||||||
board.colors[i][0] = blankColor
|
board.colors[i][0] = blankColor
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -218,7 +234,7 @@ func (board *Board) SetColor(x int, y int, color termbox.Attribute, rotation int
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidBlockLocation(x int, y int, mustBeOnBoard bool) bool {
|
func ValidBlockLocation(x int, y int, mustBeOnBoard bool) bool {
|
||||||
if x < 0 || x >= boardWidth || y >= boardHeight {
|
if x < 0 || x >= board.width || y >= board.height {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if mustBeOnBoard {
|
if mustBeOnBoard {
|
||||||
|
@ -239,12 +255,12 @@ func ValidBlockLocation(x int, y int, mustBeOnBoard bool) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func ValidDisplayLocation(x int, y int) bool {
|
func ValidDisplayLocation(x int, y int) bool {
|
||||||
return x >= 0 && x < boardWidth && y >= 0 && y < boardHeight
|
return x >= 0 && x < board.width && y >= 0 && y < board.height
|
||||||
}
|
}
|
||||||
|
|
||||||
func (board *Board) DrawBoard() {
|
func (board *Board) DrawBoard() {
|
||||||
for i := 0; i < boardWidth; i++ {
|
for i := 0; i < board.width; i++ {
|
||||||
for j := 0; j < boardHeight; j++ {
|
for j := 0; j < board.height; j++ {
|
||||||
if board.colors[i][j] != blankColor {
|
if board.colors[i][j] != blankColor {
|
||||||
view.DrawBlock(i, j, board.colors[i][j], board.rotation[i][j])
|
view.DrawBlock(i, j, board.colors[i][j], board.rotation[i][j])
|
||||||
}
|
}
|
||||||
|
@ -273,9 +289,9 @@ func (board *Board) DrawDropMino() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// for debuging
|
// for debuging
|
||||||
func (board *Board) drawDebugBoard() {
|
func (board *Board) printDebugBoard() {
|
||||||
for j := 0; j < boardHeight; j++ {
|
for j := 0; j < board.height; j++ {
|
||||||
for i := 0; i < boardWidth; i++ {
|
for i := 0; i < board.width; i++ {
|
||||||
switch board.colors[i][j] {
|
switch board.colors[i][j] {
|
||||||
case blankColor:
|
case blankColor:
|
||||||
fmt.Print(".")
|
fmt.Print(".")
|
||||||
|
@ -293,8 +309,89 @@ func (board *Board) drawDebugBoard() {
|
||||||
fmt.Print("W")
|
fmt.Print("W")
|
||||||
case termbox.ColorYellow:
|
case termbox.ColorYellow:
|
||||||
fmt.Print("Y")
|
fmt.Print("Y")
|
||||||
|
default:
|
||||||
|
fmt.Print("U")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Println("")
|
fmt.Println("")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for debuging
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
// for debuging
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestBoards(t *testing.T) {
|
||||||
|
for i := 0; i < len(boards); i++ {
|
||||||
|
b := boards[i]
|
||||||
|
width := len(b.colors)
|
||||||
|
height := len(b.colors[0])
|
||||||
|
|
||||||
|
for j := 1; j < width; j++ {
|
||||||
|
if len(b.colors[j]) != height {
|
||||||
|
t.Fatalf("board height - received: %v - expected: %v - index %v", len(b.colors[j]), height, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(b.rotation) != width {
|
||||||
|
t.Fatalf("rotation width - received: %v - expected: %v - index %v", len(b.rotation), width, i)
|
||||||
|
}
|
||||||
|
|
||||||
|
for j := 0; j < width; j++ {
|
||||||
|
if len(b.rotation[j]) != height {
|
||||||
|
t.Fatalf("rotation height - received: %v - expected: %v - index %v", len(b.rotation[j]), height, i)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
64
engine.go
64
engine.go
|
@ -1,29 +1,13 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/nsf/termbox-go"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/nsf/termbox-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Engine struct {
|
func NewEngine() {
|
||||||
stopped bool
|
engine = &Engine{
|
||||||
chanStop chan struct{}
|
|
||||||
keyInput *KeyInput
|
|
||||||
ranking *Ranking
|
|
||||||
timer *time.Timer
|
|
||||||
tickTime time.Duration
|
|
||||||
paused bool
|
|
||||||
gameOver bool
|
|
||||||
score int
|
|
||||||
level int
|
|
||||||
deleteLines int
|
|
||||||
ai *Ai
|
|
||||||
aiEnabled bool
|
|
||||||
aiTimer *time.Timer
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewEngine() *Engine {
|
|
||||||
return &Engine{
|
|
||||||
chanStop: make(chan struct{}, 1),
|
chanStop: make(chan struct{}, 1),
|
||||||
gameOver: true,
|
gameOver: true,
|
||||||
tickTime: time.Hour,
|
tickTime: time.Hour,
|
||||||
|
@ -42,7 +26,7 @@ func (engine *Engine) Run() {
|
||||||
engine.aiTimer.Stop()
|
engine.aiTimer.Stop()
|
||||||
|
|
||||||
engine.ranking = NewRanking()
|
engine.ranking = NewRanking()
|
||||||
board = NewBoard()
|
board.Clear()
|
||||||
view.RefreshScreen()
|
view.RefreshScreen()
|
||||||
|
|
||||||
engine.keyInput = NewKeyInput()
|
engine.keyInput = NewKeyInput()
|
||||||
|
@ -61,9 +45,8 @@ loop:
|
||||||
case <-engine.timer.C:
|
case <-engine.timer.C:
|
||||||
engine.tick()
|
engine.tick()
|
||||||
case <-engine.aiTimer.C:
|
case <-engine.aiTimer.C:
|
||||||
if engine.ai.ProcessQueue() {
|
engine.ai.ProcessQueue()
|
||||||
engine.aiTimer.Reset(engine.tickTime / 4)
|
engine.aiTimer.Reset(engine.tickTime / 6)
|
||||||
}
|
|
||||||
case <-engine.chanStop:
|
case <-engine.chanStop:
|
||||||
break loop
|
break loop
|
||||||
}
|
}
|
||||||
|
@ -105,15 +88,19 @@ func (engine *Engine) Pause() {
|
||||||
func (engine *Engine) UnPause() {
|
func (engine *Engine) UnPause() {
|
||||||
engine.timer.Reset(engine.tickTime)
|
engine.timer.Reset(engine.tickTime)
|
||||||
if engine.aiEnabled {
|
if engine.aiEnabled {
|
||||||
engine.aiTimer.Reset(engine.tickTime / 4)
|
engine.aiTimer.Reset(engine.tickTime / 6)
|
||||||
}
|
}
|
||||||
engine.paused = false
|
engine.paused = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (engine *Engine) PreviewBoard() {
|
||||||
|
engine.previewBoard = true
|
||||||
|
}
|
||||||
|
|
||||||
func (engine *Engine) NewGame() {
|
func (engine *Engine) NewGame() {
|
||||||
logger.Info("Engine NewGame start")
|
logger.Info("Engine NewGame start")
|
||||||
|
|
||||||
board = NewBoard()
|
board.Clear()
|
||||||
engine.tickTime = 480 * time.Millisecond
|
engine.tickTime = 480 * time.Millisecond
|
||||||
engine.score = 0
|
engine.score = 0
|
||||||
engine.level = 1
|
engine.level = 1
|
||||||
|
@ -128,6 +115,7 @@ loop:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
engine.previewBoard = false
|
||||||
engine.gameOver = false
|
engine.gameOver = false
|
||||||
if engine.aiEnabled {
|
if engine.aiEnabled {
|
||||||
engine.ai.GetBestQueue()
|
engine.ai.GetBestQueue()
|
||||||
|
@ -153,12 +141,11 @@ func (engine *Engine) ResetTimer(duration time.Duration) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) ResetAiTimer() {
|
func (engine *Engine) AiGetBestQueue() {
|
||||||
if !engine.aiEnabled {
|
if !engine.aiEnabled {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
engine.ai.GetBestQueue()
|
go engine.ai.GetBestQueue()
|
||||||
engine.aiTimer.Reset(engine.tickTime / 4)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) tick() {
|
func (engine *Engine) tick() {
|
||||||
|
@ -190,8 +177,8 @@ func (engine *Engine) AddDeleteLines(lines int) {
|
||||||
|
|
||||||
func (engine *Engine) AddScore(add int) {
|
func (engine *Engine) AddScore(add int) {
|
||||||
engine.score += add
|
engine.score += add
|
||||||
if engine.score > 999999 {
|
if engine.score > 9999999 {
|
||||||
engine.score = 999999
|
engine.score = 9999999
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,13 +209,9 @@ func (engine *Engine) GameOver() {
|
||||||
logger.Info("Engine GameOver start")
|
logger.Info("Engine GameOver start")
|
||||||
|
|
||||||
engine.Pause()
|
engine.Pause()
|
||||||
|
|
||||||
view.ShowGameOverAnimation()
|
|
||||||
|
|
||||||
engine.gameOver = true
|
engine.gameOver = true
|
||||||
|
|
||||||
engine.ranking.InsertScore(uint64(engine.score))
|
view.ShowGameOverAnimation()
|
||||||
engine.ranking.Save()
|
|
||||||
|
|
||||||
loop:
|
loop:
|
||||||
for {
|
for {
|
||||||
|
@ -239,21 +222,24 @@ loop:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
engine.ranking.InsertScore(uint64(engine.score))
|
||||||
|
engine.ranking.Save()
|
||||||
|
|
||||||
logger.Info("Engine GameOver end")
|
logger.Info("Engine GameOver end")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) EnabledAi() {
|
func (engine *Engine) EnabledAi() {
|
||||||
engine.aiEnabled = true
|
engine.aiEnabled = true
|
||||||
engine.ai.GetBestQueue()
|
go engine.ai.GetBestQueue()
|
||||||
engine.aiTimer.Reset(engine.tickTime / 4)
|
engine.aiTimer.Reset(engine.tickTime / 6)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (engine *Engine) DisableAi() {
|
func (engine *Engine) DisableAi() {
|
||||||
|
engine.aiEnabled = false
|
||||||
if !engine.aiTimer.Stop() {
|
if !engine.aiTimer.Stop() {
|
||||||
select {
|
select {
|
||||||
case <-engine.aiTimer.C:
|
case <-engine.aiTimer.C:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
engine.aiEnabled = false
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,103 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nsf/termbox-go"
|
||||||
|
"gopkg.in/inconshreveable/log15.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
blankColor = termbox.ColorBlack
|
||||||
|
boardXOffset = 4
|
||||||
|
boardYOffset = 2
|
||||||
|
rankingFileName = "/tetris.db"
|
||||||
|
|
||||||
|
MinoPreview MinoType = iota
|
||||||
|
MinoCurrent = iota
|
||||||
|
MinoDrop = iota
|
||||||
|
)
|
||||||
|
|
||||||
|
type (
|
||||||
|
MinoType int
|
||||||
|
MinoBlocks [][]termbox.Attribute
|
||||||
|
MinoRotation [4]MinoBlocks
|
||||||
|
|
||||||
|
Mino struct {
|
||||||
|
x int
|
||||||
|
y int
|
||||||
|
length int
|
||||||
|
rotation int
|
||||||
|
minoRotation MinoRotation
|
||||||
|
}
|
||||||
|
|
||||||
|
Minos struct {
|
||||||
|
minoBag [7]MinoRotation
|
||||||
|
bagRand []int
|
||||||
|
bagIndex int
|
||||||
|
}
|
||||||
|
|
||||||
|
Board struct {
|
||||||
|
boardsIndex int
|
||||||
|
width int
|
||||||
|
height int
|
||||||
|
colors [][]termbox.Attribute
|
||||||
|
rotation [][]int
|
||||||
|
previewMino *Mino
|
||||||
|
currentMino *Mino
|
||||||
|
dropDistance int
|
||||||
|
}
|
||||||
|
|
||||||
|
Boards struct {
|
||||||
|
colors [][]termbox.Attribute
|
||||||
|
rotation [][]int
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyInput struct {
|
||||||
|
stopped bool
|
||||||
|
chanStop chan struct{}
|
||||||
|
chanKeyInput chan *termbox.Event
|
||||||
|
}
|
||||||
|
|
||||||
|
View struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
Ai struct {
|
||||||
|
queue *[]rune
|
||||||
|
newQueue *[]rune
|
||||||
|
index int
|
||||||
|
}
|
||||||
|
|
||||||
|
Ranking struct {
|
||||||
|
scores []uint64
|
||||||
|
}
|
||||||
|
|
||||||
|
Engine struct {
|
||||||
|
stopped bool
|
||||||
|
chanStop chan struct{}
|
||||||
|
keyInput *KeyInput
|
||||||
|
ranking *Ranking
|
||||||
|
timer *time.Timer
|
||||||
|
tickTime time.Duration
|
||||||
|
paused bool
|
||||||
|
gameOver bool
|
||||||
|
previewBoard bool
|
||||||
|
score int
|
||||||
|
level int
|
||||||
|
deleteLines int
|
||||||
|
ai *Ai
|
||||||
|
aiEnabled bool
|
||||||
|
aiTimer *time.Timer
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
boards []Boards
|
||||||
|
|
||||||
|
baseDir string
|
||||||
|
logger log15.Logger
|
||||||
|
minos *Minos
|
||||||
|
board *Board
|
||||||
|
view *View
|
||||||
|
engine *Engine
|
||||||
|
)
|
26
keyInput.go
26
keyInput.go
|
@ -1,15 +1,10 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/nsf/termbox-go"
|
|
||||||
"runtime"
|
"runtime"
|
||||||
)
|
|
||||||
|
|
||||||
type KeyInput struct {
|
"github.com/nsf/termbox-go"
|
||||||
stopped bool
|
)
|
||||||
chanStop chan struct{}
|
|
||||||
chanKeyInput chan *termbox.Event
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewKeyInput() *KeyInput {
|
func NewKeyInput() *KeyInput {
|
||||||
return &KeyInput{
|
return &KeyInput{
|
||||||
|
@ -29,16 +24,11 @@ loop:
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
event := termbox.PollEvent()
|
event := termbox.PollEvent()
|
||||||
if event.Type == termbox.EventKey {
|
if event.Type == termbox.EventKey && len(keyInput.chanKeyInput) < 8 {
|
||||||
select {
|
select {
|
||||||
case <-keyInput.chanStop:
|
case <-keyInput.chanStop:
|
||||||
break loop
|
break loop
|
||||||
default:
|
|
||||||
select {
|
|
||||||
case keyInput.chanKeyInput <- &event:
|
case keyInput.chanKeyInput <- &event:
|
||||||
case <-keyInput.chanStop:
|
|
||||||
break loop
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -47,7 +37,6 @@ loop:
|
||||||
}
|
}
|
||||||
|
|
||||||
func (keyInput *KeyInput) ProcessEvent(event *termbox.Event) {
|
func (keyInput *KeyInput) ProcessEvent(event *termbox.Event) {
|
||||||
|
|
||||||
if event.Key == termbox.KeyCtrlI {
|
if event.Key == termbox.KeyCtrlI {
|
||||||
// ctrl i to log stack trace
|
// ctrl i to log stack trace
|
||||||
buffer := make([]byte, 1<<16)
|
buffer := make([]byte, 1<<16)
|
||||||
|
@ -66,8 +55,15 @@ func (keyInput *KeyInput) ProcessEvent(event *termbox.Event) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if engine.gameOver {
|
if engine.gameOver {
|
||||||
if event.Key == termbox.KeySpace {
|
if event.Ch == 0 {
|
||||||
|
switch event.Key {
|
||||||
|
case termbox.KeySpace:
|
||||||
engine.NewGame()
|
engine.NewGame()
|
||||||
|
case termbox.KeyArrowLeft:
|
||||||
|
board.PreviousBoard()
|
||||||
|
case termbox.KeyArrowRight:
|
||||||
|
board.NextBoard()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
81
mino.go
81
mino.go
|
@ -2,47 +2,22 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"time"
|
|
||||||
|
"github.com/nsf/termbox-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MinoType int
|
|
||||||
|
|
||||||
const (
|
|
||||||
MinoPreview MinoType = iota
|
|
||||||
MinoCurrent = iota
|
|
||||||
MinoDrop = iota
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
bagRand []int
|
|
||||||
bagIndex int
|
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
bagRand = rand.Perm(7)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Mino struct {
|
|
||||||
x int
|
|
||||||
y int
|
|
||||||
length int
|
|
||||||
rotation int
|
|
||||||
minoRotation MinoRotation
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMino() *Mino {
|
func NewMino() *Mino {
|
||||||
minoRotation := minoBag[bagRand[bagIndex]]
|
minoRotation := minos.minoBag[minos.bagRand[minos.bagIndex]]
|
||||||
bagIndex++
|
minos.bagIndex++
|
||||||
if bagIndex > 6 {
|
if minos.bagIndex > 6 {
|
||||||
bagIndex = 0
|
minos.bagIndex = 0
|
||||||
bagRand = rand.Perm(7)
|
minos.bagRand = rand.Perm(7)
|
||||||
}
|
}
|
||||||
mino := &Mino{
|
mino := &Mino{
|
||||||
minoRotation: minoRotation,
|
minoRotation: minoRotation,
|
||||||
length: len(minoRotation[0]),
|
length: len(minoRotation[0]),
|
||||||
}
|
}
|
||||||
mino.x = boardWidth/2 - (mino.length+1)/2
|
mino.x = board.width/2 - (mino.length+1)/2
|
||||||
mino.y = -1
|
mino.y = -1
|
||||||
return mino
|
return mino
|
||||||
}
|
}
|
||||||
|
@ -152,3 +127,43 @@ func (mino *Mino) DrawMino(minoType MinoType) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (mino *Mino) minoOverlap(mino1 *Mino) bool {
|
||||||
|
minoBlocks := mino.minoRotation[mino.rotation]
|
||||||
|
for i := 0; i < mino.length; i++ {
|
||||||
|
for j := 0; j < mino.length; j++ {
|
||||||
|
if minoBlocks[i][j] != blankColor {
|
||||||
|
if mino1.isMinoAtLocation(mino.x+i, mino.y+j) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mino *Mino) isMinoAtLocation(x int, y int) bool {
|
||||||
|
xIndex := x - mino.x
|
||||||
|
yIndex := y - mino.y
|
||||||
|
if xIndex < 0 || xIndex >= mino.length || yIndex < 0 || yIndex >= mino.length {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
minoBlocks := mino.minoRotation[mino.rotation]
|
||||||
|
if minoBlocks[xIndex][yIndex] != blankColor {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (mino *Mino) getMinoColorAtLocation(x int, y int) termbox.Attribute {
|
||||||
|
xIndex := x - mino.x
|
||||||
|
yIndex := y - mino.y
|
||||||
|
if xIndex < 0 || xIndex >= mino.length || yIndex < 0 || yIndex >= mino.length {
|
||||||
|
return blankColor
|
||||||
|
}
|
||||||
|
|
||||||
|
minoBlocks := mino.minoRotation[mino.rotation]
|
||||||
|
return minoBlocks[xIndex][yIndex]
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,120 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"gopkg.in/inconshreveable/log15.v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
type testMinoStruct struct {
|
||||||
|
minoRotation MinoRotation
|
||||||
|
x int
|
||||||
|
y int
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMain(m *testing.M) {
|
||||||
|
setupForTesting()
|
||||||
|
retCode := m.Run()
|
||||||
|
os.Exit(retCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setupForTesting() {
|
||||||
|
logger = log15.New()
|
||||||
|
logger.SetHandler(log15.StreamHandler(os.Stdout, log15.LogfmtFormat()))
|
||||||
|
|
||||||
|
rand.Seed(1)
|
||||||
|
|
||||||
|
NewMinos()
|
||||||
|
NewBoard()
|
||||||
|
NewEngine()
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestMinoValidLocation(t *testing.T) {
|
||||||
|
// this must be set to the blank boards
|
||||||
|
for _, i := range []int{0, 3} {
|
||||||
|
board.boardsIndex = i
|
||||||
|
board.Clear()
|
||||||
|
|
||||||
|
tests := []struct {
|
||||||
|
info string
|
||||||
|
mino testMinoStruct
|
||||||
|
changeLocation bool
|
||||||
|
mustBeOnBoard bool
|
||||||
|
validLocation bool
|
||||||
|
}{
|
||||||
|
{info: "start 0 false", mino: testMinoStruct{minoRotation: minos.minoBag[0]}, mustBeOnBoard: false, validLocation: true},
|
||||||
|
{info: "start 0 true", mino: testMinoStruct{minoRotation: minos.minoBag[0]}, mustBeOnBoard: true, validLocation: true},
|
||||||
|
{info: "start 1 false", mino: testMinoStruct{minoRotation: minos.minoBag[1]}, mustBeOnBoard: false, validLocation: true},
|
||||||
|
{info: "start 1 true", mino: testMinoStruct{minoRotation: minos.minoBag[1]}, mustBeOnBoard: true, validLocation: false},
|
||||||
|
{info: "start 2 false", mino: testMinoStruct{minoRotation: minos.minoBag[2]}, mustBeOnBoard: false, validLocation: true},
|
||||||
|
{info: "start 2 true", mino: testMinoStruct{minoRotation: minos.minoBag[2]}, mustBeOnBoard: true, validLocation: false},
|
||||||
|
{info: "start 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3]}, mustBeOnBoard: false, validLocation: true},
|
||||||
|
{info: "start 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3]}, mustBeOnBoard: true, validLocation: false},
|
||||||
|
{info: "start 4 false", mino: testMinoStruct{minoRotation: minos.minoBag[4]}, mustBeOnBoard: false, validLocation: true},
|
||||||
|
{info: "start 4 true", mino: testMinoStruct{minoRotation: minos.minoBag[4]}, mustBeOnBoard: true, validLocation: false},
|
||||||
|
{info: "start 5 false", mino: testMinoStruct{minoRotation: minos.minoBag[5]}, mustBeOnBoard: false, validLocation: true},
|
||||||
|
{info: "start 5 true", mino: testMinoStruct{minoRotation: minos.minoBag[5]}, mustBeOnBoard: true, validLocation: false},
|
||||||
|
{info: "start 6 false", mino: testMinoStruct{minoRotation: minos.minoBag[6]}, mustBeOnBoard: false, validLocation: true},
|
||||||
|
{info: "start 6 true", mino: testMinoStruct{minoRotation: minos.minoBag[6]}, mustBeOnBoard: true, validLocation: false},
|
||||||
|
|
||||||
|
{info: "top left 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: 0}, changeLocation: true, mustBeOnBoard: false, validLocation: true},
|
||||||
|
{info: "top left 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: 0}, changeLocation: true, mustBeOnBoard: true, validLocation: true},
|
||||||
|
{info: "top right 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: board.width - 2, y: 0}, changeLocation: true, mustBeOnBoard: false, validLocation: true},
|
||||||
|
{info: "top right 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: board.width - 2, y: 0}, changeLocation: true, mustBeOnBoard: true, validLocation: true},
|
||||||
|
{info: "bottom right 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: board.width - 2, y: board.height - 2}, changeLocation: true, mustBeOnBoard: false, validLocation: true},
|
||||||
|
{info: "bottom right 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: board.width - 2, y: board.height - 2}, changeLocation: true, mustBeOnBoard: true, validLocation: true},
|
||||||
|
{info: "bottom left 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: board.height - 2}, changeLocation: true, mustBeOnBoard: false, validLocation: true},
|
||||||
|
{info: "bottom left 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: board.height - 2}, changeLocation: true, mustBeOnBoard: true, validLocation: true},
|
||||||
|
|
||||||
|
{info: "up 1 top left 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: -1}, changeLocation: true, mustBeOnBoard: false, validLocation: true},
|
||||||
|
{info: "up 1 top left 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: -1}, changeLocation: true, mustBeOnBoard: true, validLocation: false},
|
||||||
|
{info: "up 1 top right 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: board.width - 2, y: -1}, changeLocation: true, mustBeOnBoard: false, validLocation: true},
|
||||||
|
{info: "up 1 top right 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: board.width - 2, y: -1}, changeLocation: true, mustBeOnBoard: true, validLocation: false},
|
||||||
|
|
||||||
|
{info: "up 2 top left 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: -2}, changeLocation: true, mustBeOnBoard: false, validLocation: true},
|
||||||
|
{info: "up 2 top left 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: -2}, changeLocation: true, mustBeOnBoard: true, validLocation: false},
|
||||||
|
{info: "up 2 top right 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: board.width - 2, y: -2}, changeLocation: true, mustBeOnBoard: false, validLocation: true},
|
||||||
|
{info: "up 2 top right 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: board.width - 2, y: -2}, changeLocation: true, mustBeOnBoard: true, validLocation: false},
|
||||||
|
|
||||||
|
{info: "up 3 top left 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: -3}, changeLocation: true, mustBeOnBoard: false, validLocation: false},
|
||||||
|
{info: "up 3 top left 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: -3}, changeLocation: true, mustBeOnBoard: true, validLocation: false},
|
||||||
|
{info: "up 3 top right 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: board.width - 2, y: -3}, changeLocation: true, mustBeOnBoard: false, validLocation: false},
|
||||||
|
{info: "up 3 top right 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: board.width - 2, y: -3}, changeLocation: true, mustBeOnBoard: true, validLocation: false},
|
||||||
|
|
||||||
|
{info: "off top left 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: -1, y: 0}, changeLocation: true, mustBeOnBoard: false, validLocation: false},
|
||||||
|
{info: "off top left 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: -1, y: 0}, changeLocation: true, mustBeOnBoard: true, validLocation: false},
|
||||||
|
{info: "off top right 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: board.width - 1, y: 0}, changeLocation: true, mustBeOnBoard: false, validLocation: false},
|
||||||
|
{info: "off top right 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: board.width - 1, y: 0}, changeLocation: true, mustBeOnBoard: true, validLocation: false},
|
||||||
|
{info: "off bottom right 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: board.width - 1, y: board.height - 2}, changeLocation: true, mustBeOnBoard: false, validLocation: false},
|
||||||
|
{info: "off bottom right 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: board.width - 1, y: board.height - 2}, changeLocation: true, mustBeOnBoard: true, validLocation: false},
|
||||||
|
{info: "off bottom right 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: board.width - 2, y: board.height - 1}, changeLocation: true, mustBeOnBoard: false, validLocation: false},
|
||||||
|
{info: "off bottom right 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: board.width - 2, y: board.height - 1}, changeLocation: true, mustBeOnBoard: true, validLocation: false},
|
||||||
|
{info: "off bottom left 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: -1, y: board.height - 2}, changeLocation: true, mustBeOnBoard: false, validLocation: false},
|
||||||
|
{info: "off bottom left 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: -1, y: board.height - 2}, changeLocation: true, mustBeOnBoard: true, validLocation: false},
|
||||||
|
{info: "off bottom left 3 false", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: board.height - 1}, changeLocation: true, mustBeOnBoard: false, validLocation: false},
|
||||||
|
{info: "off bottom left 3 true", mino: testMinoStruct{minoRotation: minos.minoBag[3], x: 0, y: board.height - 1}, changeLocation: true, mustBeOnBoard: true, validLocation: false},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
mino := NewMino()
|
||||||
|
mino.minoRotation = test.mino.minoRotation
|
||||||
|
mino.length = len(mino.minoRotation[0])
|
||||||
|
if test.changeLocation {
|
||||||
|
mino.x = test.mino.x
|
||||||
|
mino.y = test.mino.y
|
||||||
|
}
|
||||||
|
validLocation := mino.ValidLocation(test.mustBeOnBoard)
|
||||||
|
if validLocation != test.validLocation {
|
||||||
|
lines := board.getDebugBoardWithMino(mino)
|
||||||
|
for i := 0; i < len(lines); i++ {
|
||||||
|
t.Log(lines[i])
|
||||||
|
}
|
||||||
|
t.Errorf("MinoValidLocation validLocation - received: %v - expected: %v - info %v", validLocation, test.validLocation, test.info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
29
minos.go
29
minos.go
|
@ -1,16 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"math/rand"
|
||||||
|
|
||||||
"github.com/nsf/termbox-go"
|
"github.com/nsf/termbox-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
type MinoBlocks [][]termbox.Attribute
|
func NewMinos() {
|
||||||
|
|
||||||
type MinoRotation [4]MinoBlocks
|
|
||||||
|
|
||||||
var minoBag [7]MinoRotation
|
|
||||||
|
|
||||||
func init() {
|
|
||||||
minoI := MinoBlocks{
|
minoI := MinoBlocks{
|
||||||
[]termbox.Attribute{blankColor, termbox.ColorCyan, blankColor, blankColor},
|
[]termbox.Attribute{blankColor, termbox.ColorCyan, blankColor, blankColor},
|
||||||
[]termbox.Attribute{blankColor, termbox.ColorCyan, blankColor, blankColor},
|
[]termbox.Attribute{blankColor, termbox.ColorCyan, blankColor, blankColor},
|
||||||
|
@ -50,17 +46,17 @@ func init() {
|
||||||
var minoRotationI MinoRotation
|
var minoRotationI MinoRotation
|
||||||
minoRotationI[0] = minoI
|
minoRotationI[0] = minoI
|
||||||
for i := 1; i < 4; i++ {
|
for i := 1; i < 4; i++ {
|
||||||
minoRotationI[i] = initCloneRotateRight(minoRotationI[i-1])
|
minoRotationI[i] = minosCloneRotateRight(minoRotationI[i-1])
|
||||||
}
|
}
|
||||||
var minoRotationJ MinoRotation
|
var minoRotationJ MinoRotation
|
||||||
minoRotationJ[0] = minoJ
|
minoRotationJ[0] = minoJ
|
||||||
for i := 1; i < 4; i++ {
|
for i := 1; i < 4; i++ {
|
||||||
minoRotationJ[i] = initCloneRotateRight(minoRotationJ[i-1])
|
minoRotationJ[i] = minosCloneRotateRight(minoRotationJ[i-1])
|
||||||
}
|
}
|
||||||
var minoRotationL MinoRotation
|
var minoRotationL MinoRotation
|
||||||
minoRotationL[0] = minoL
|
minoRotationL[0] = minoL
|
||||||
for i := 1; i < 4; i++ {
|
for i := 1; i < 4; i++ {
|
||||||
minoRotationL[i] = initCloneRotateRight(minoRotationL[i-1])
|
minoRotationL[i] = minosCloneRotateRight(minoRotationL[i-1])
|
||||||
}
|
}
|
||||||
var minoRotationO MinoRotation
|
var minoRotationO MinoRotation
|
||||||
minoRotationO[0] = minoO
|
minoRotationO[0] = minoO
|
||||||
|
@ -70,23 +66,26 @@ func init() {
|
||||||
var minoRotationS MinoRotation
|
var minoRotationS MinoRotation
|
||||||
minoRotationS[0] = minoS
|
minoRotationS[0] = minoS
|
||||||
for i := 1; i < 4; i++ {
|
for i := 1; i < 4; i++ {
|
||||||
minoRotationS[i] = initCloneRotateRight(minoRotationS[i-1])
|
minoRotationS[i] = minosCloneRotateRight(minoRotationS[i-1])
|
||||||
}
|
}
|
||||||
var minoRotationT MinoRotation
|
var minoRotationT MinoRotation
|
||||||
minoRotationT[0] = minoT
|
minoRotationT[0] = minoT
|
||||||
for i := 1; i < 4; i++ {
|
for i := 1; i < 4; i++ {
|
||||||
minoRotationT[i] = initCloneRotateRight(minoRotationT[i-1])
|
minoRotationT[i] = minosCloneRotateRight(minoRotationT[i-1])
|
||||||
}
|
}
|
||||||
var minoRotationZ MinoRotation
|
var minoRotationZ MinoRotation
|
||||||
minoRotationZ[0] = minoZ
|
minoRotationZ[0] = minoZ
|
||||||
for i := 1; i < 4; i++ {
|
for i := 1; i < 4; i++ {
|
||||||
minoRotationZ[i] = initCloneRotateRight(minoRotationZ[i-1])
|
minoRotationZ[i] = minosCloneRotateRight(minoRotationZ[i-1])
|
||||||
}
|
}
|
||||||
|
|
||||||
minoBag = [7]MinoRotation{minoRotationI, minoRotationJ, minoRotationL, minoRotationO, minoRotationS, minoRotationT, minoRotationZ}
|
minos = &Minos{
|
||||||
|
minoBag: [7]MinoRotation{minoRotationI, minoRotationJ, minoRotationL, minoRotationO, minoRotationS, minoRotationT, minoRotationZ},
|
||||||
|
bagRand: rand.Perm(7),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func initCloneRotateRight(minoBlocks MinoBlocks) MinoBlocks {
|
func minosCloneRotateRight(minoBlocks MinoBlocks) MinoBlocks {
|
||||||
length := len(minoBlocks)
|
length := len(minoBlocks)
|
||||||
newMinoBlocks := make(MinoBlocks, length, length)
|
newMinoBlocks := make(MinoBlocks, length, length)
|
||||||
for i := 0; i < length; i++ {
|
for i := 0; i < length; i++ {
|
||||||
|
|
17
ranking.go
17
ranking.go
|
@ -8,21 +8,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
|
||||||
rankingFileName = "/tetris.db"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Ranking struct {
|
|
||||||
scores []uint64
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRanking() *Ranking {
|
func NewRanking() *Ranking {
|
||||||
ranking := &Ranking{
|
ranking := &Ranking{
|
||||||
scores: make([]uint64, 10),
|
scores: make([]uint64, 9),
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, err := os.Stat(baseDir + rankingFileName); os.IsNotExist(err) {
|
if _, err := os.Stat(baseDir + rankingFileName); os.IsNotExist(err) {
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 9; i++ {
|
||||||
ranking.scores[i] = 0
|
ranking.scores[i] = 0
|
||||||
}
|
}
|
||||||
return ranking
|
return ranking
|
||||||
|
@ -35,6 +27,9 @@ func NewRanking() *Ranking {
|
||||||
|
|
||||||
scoreStrings := strings.Split(string(scoreBytes), ",")
|
scoreStrings := strings.Split(string(scoreBytes), ",")
|
||||||
for index, scoreString := range scoreStrings {
|
for index, scoreString := range scoreStrings {
|
||||||
|
if index > 8 {
|
||||||
|
break
|
||||||
|
}
|
||||||
score, err := strconv.ParseUint(scoreString, 10, 64)
|
score, err := strconv.ParseUint(scoreString, 10, 64)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Error("NewRanking ParseUint", "error", err.Error())
|
logger.Error("NewRanking ParseUint", "error", err.Error())
|
||||||
|
@ -49,7 +44,7 @@ func NewRanking() *Ranking {
|
||||||
func (ranking *Ranking) Save() {
|
func (ranking *Ranking) Save() {
|
||||||
var buffer bytes.Buffer
|
var buffer bytes.Buffer
|
||||||
|
|
||||||
for i := 0; i < 10; i++ {
|
for i := 0; i < 9; i++ {
|
||||||
if i != 0 {
|
if i != 0 {
|
||||||
buffer.WriteRune(',')
|
buffer.WriteRune(',')
|
||||||
}
|
}
|
||||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 6.0 KiB After Width: | Height: | Size: 7.9 KiB |
Binary file not shown.
After Width: | Height: | Size: 6.8 KiB |
26
tetris.go
26
tetris.go
|
@ -1,24 +1,12 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/nsf/termbox-go"
|
"math/rand"
|
||||||
"gopkg.in/inconshreveable/log15.v2"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
)
|
"time"
|
||||||
|
|
||||||
const (
|
"gopkg.in/inconshreveable/log15.v2"
|
||||||
boardWidth = 10
|
|
||||||
boardHeight = 20
|
|
||||||
blankColor = termbox.ColorBlack
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
baseDir string
|
|
||||||
logger log15.Logger
|
|
||||||
view *View
|
|
||||||
engine *Engine
|
|
||||||
board *Board
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
@ -29,8 +17,12 @@ func main() {
|
||||||
logger.SetHandler(log15.Must.FileHandler(baseDir+"/tetris.log", log15.LogfmtFormat()))
|
logger.SetHandler(log15.Must.FileHandler(baseDir+"/tetris.log", log15.LogfmtFormat()))
|
||||||
}
|
}
|
||||||
|
|
||||||
view = NewView()
|
rand.Seed(time.Now().UnixNano())
|
||||||
engine = NewEngine()
|
|
||||||
|
NewMinos()
|
||||||
|
NewBoard()
|
||||||
|
NewView()
|
||||||
|
NewEngine()
|
||||||
|
|
||||||
engine.Run()
|
engine.Run()
|
||||||
|
|
||||||
|
|
142
view.go
142
view.go
|
@ -2,27 +2,20 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/nsf/termbox-go"
|
"math/rand"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/nsf/termbox-go"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
func NewView() {
|
||||||
boardXOffset = 4
|
|
||||||
boardYOffset = 2
|
|
||||||
)
|
|
||||||
|
|
||||||
type View struct {
|
|
||||||
drawDropMarkerDisabled bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewView() *View {
|
|
||||||
err := termbox.Init()
|
err := termbox.Init()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
termbox.SetInputMode(termbox.InputEsc)
|
termbox.SetInputMode(termbox.InputEsc)
|
||||||
termbox.Flush()
|
termbox.Flush()
|
||||||
return &View{}
|
view = &View{}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *View) Stop() {
|
func (view *View) Stop() {
|
||||||
|
@ -38,12 +31,18 @@ func (view *View) RefreshScreen() {
|
||||||
|
|
||||||
view.drawBackground()
|
view.drawBackground()
|
||||||
view.drawTexts()
|
view.drawTexts()
|
||||||
board.DrawPreviewMino()
|
|
||||||
|
|
||||||
if engine.gameOver {
|
if engine.previewBoard {
|
||||||
|
board.DrawBoard()
|
||||||
view.drawGameOver()
|
view.drawGameOver()
|
||||||
|
} else if engine.gameOver {
|
||||||
|
view.drawGameOver()
|
||||||
|
view.drawRankingScores()
|
||||||
|
} else if engine.paused {
|
||||||
|
view.drawPaused()
|
||||||
} else {
|
} else {
|
||||||
board.DrawBoard()
|
board.DrawBoard()
|
||||||
|
board.DrawPreviewMino()
|
||||||
board.DrawDropMino()
|
board.DrawDropMino()
|
||||||
board.DrawCurrentMino()
|
board.DrawCurrentMino()
|
||||||
}
|
}
|
||||||
|
@ -55,8 +54,8 @@ func (view *View) drawBackground() {
|
||||||
// playing board
|
// playing board
|
||||||
xOffset := boardXOffset
|
xOffset := boardXOffset
|
||||||
yOffset := boardYOffset
|
yOffset := boardYOffset
|
||||||
xEnd := boardXOffset + boardWidth*2 + 4
|
xEnd := boardXOffset + board.width*2 + 4
|
||||||
yEnd := boardYOffset + boardHeight + 2
|
yEnd := boardYOffset + board.height + 2
|
||||||
for x := xOffset; x < xEnd; x++ {
|
for x := xOffset; x < xEnd; x++ {
|
||||||
for y := yOffset; y < yEnd; y++ {
|
for y := yOffset; y < yEnd; y++ {
|
||||||
if x == xOffset || x == xOffset+1 || x == xEnd-1 || x == xEnd-2 ||
|
if x == xOffset || x == xOffset+1 || x == xEnd-1 || x == xEnd-2 ||
|
||||||
|
@ -69,7 +68,7 @@ func (view *View) drawBackground() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// piece preview
|
// piece preview
|
||||||
xOffset = boardXOffset + boardWidth*2 + 8
|
xOffset = boardXOffset + board.width*2 + 8
|
||||||
yOffset = boardYOffset
|
yOffset = boardYOffset
|
||||||
xEnd = xOffset + 14
|
xEnd = xOffset + 14
|
||||||
yEnd = yOffset + 6
|
yEnd = yOffset + 6
|
||||||
|
@ -87,7 +86,7 @@ func (view *View) drawBackground() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *View) drawTexts() {
|
func (view *View) drawTexts() {
|
||||||
xOffset := boardXOffset + boardWidth*2 + 8
|
xOffset := boardXOffset + board.width*2 + 8
|
||||||
yOffset := boardYOffset + 7
|
yOffset := boardYOffset + 7
|
||||||
|
|
||||||
view.drawText(xOffset, yOffset, "SCORE:", termbox.ColorWhite, termbox.ColorBlue)
|
view.drawText(xOffset, yOffset, "SCORE:", termbox.ColorWhite, termbox.ColorBlue)
|
||||||
|
@ -135,7 +134,7 @@ func (view *View) DrawPreviewMinoBlock(x int, y int, color termbox.Attribute, ro
|
||||||
char1 = ' '
|
char1 = ' '
|
||||||
char2 = '▓'
|
char2 = '▓'
|
||||||
}
|
}
|
||||||
xOffset := 2*x + 2*boardWidth + boardXOffset + 11 + (4 - length)
|
xOffset := 2*x + 2*board.width + boardXOffset + 11 + (4 - length)
|
||||||
termbox.SetCell(xOffset, y+boardYOffset+2, char1, color, color^termbox.AttrBold)
|
termbox.SetCell(xOffset, y+boardYOffset+2, char1, color, color^termbox.AttrBold)
|
||||||
termbox.SetCell(xOffset+1, y+boardYOffset+2, char2, color, color^termbox.AttrBold)
|
termbox.SetCell(xOffset+1, y+boardYOffset+2, char2, color, color^termbox.AttrBold)
|
||||||
}
|
}
|
||||||
|
@ -160,17 +159,32 @@ func (view *View) DrawBlock(x int, y int, color termbox.Attribute, rotation int)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *View) drawGameOver() {
|
func (view *View) drawPaused() {
|
||||||
xOffset := boardXOffset + 4
|
yOffset := (board.height+1)/2 + boardYOffset
|
||||||
yOffset := boardYOffset + 2
|
view.drawTextCenter(yOffset, "Paused", termbox.ColorWhite, termbox.ColorBlack)
|
||||||
|
}
|
||||||
|
|
||||||
view.drawText(xOffset, yOffset, " GAME OVER", termbox.ColorWhite, termbox.ColorBlack)
|
func (view *View) drawGameOver() {
|
||||||
|
yOffset := boardYOffset + 2
|
||||||
|
view.drawTextCenter(yOffset, " GAME OVER", termbox.ColorWhite, termbox.ColorBlack)
|
||||||
yOffset += 2
|
yOffset += 2
|
||||||
view.drawText(xOffset, yOffset, "sbar for new game", termbox.ColorWhite, termbox.ColorBlack)
|
view.drawTextCenter(yOffset, "sbar for new game", termbox.ColorWhite, termbox.ColorBlack)
|
||||||
|
|
||||||
|
if engine.previewBoard {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
yOffset += 2
|
yOffset += 2
|
||||||
xOffset += 2
|
// ascii arrow characters add extra two spaces
|
||||||
|
view.drawTextCenter(yOffset, "←previous board", termbox.ColorWhite, termbox.ColorBlack)
|
||||||
|
yOffset += 2
|
||||||
|
view.drawTextCenter(yOffset, "→next board", termbox.ColorWhite, termbox.ColorBlack)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (view *View) drawRankingScores() {
|
||||||
|
yOffset := boardYOffset + 10
|
||||||
for index, line := range engine.ranking.scores {
|
for index, line := range engine.ranking.scores {
|
||||||
view.drawText(xOffset, yOffset+index, fmt.Sprintf("%2d: %6d", index+1, line), termbox.ColorWhite, termbox.ColorBlack)
|
view.drawTextCenter(yOffset+index, fmt.Sprintf("%1d: %6d", index+1, line), termbox.ColorWhite, termbox.ColorBlack)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -180,9 +194,14 @@ func (view *View) drawText(x int, y int, text string, fg termbox.Attribute, bg t
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *View) ShowDeleteAnimation(lines []int) {
|
func (view *View) drawTextCenter(y int, text string, fg termbox.Attribute, bg termbox.Attribute) {
|
||||||
view.drawDropMarkerDisabled = true
|
xOffset := board.width - (len(text)+1)/2 + boardXOffset + 2
|
||||||
|
for index, char := range text {
|
||||||
|
termbox.SetCell(index+xOffset, y, rune(char), fg, bg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (view *View) ShowDeleteAnimation(lines []int) {
|
||||||
view.RefreshScreen()
|
view.RefreshScreen()
|
||||||
|
|
||||||
for times := 0; times < 3; times++ {
|
for times := 0; times < 3; times++ {
|
||||||
|
@ -195,26 +214,77 @@ func (view *View) ShowDeleteAnimation(lines []int) {
|
||||||
view.RefreshScreen()
|
view.RefreshScreen()
|
||||||
time.Sleep(140 * time.Millisecond)
|
time.Sleep(140 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
view.drawDropMarkerDisabled = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *View) ShowGameOverAnimation() {
|
func (view *View) ShowGameOverAnimation() {
|
||||||
view.drawDropMarkerDisabled = true
|
logger.Info("View ShowGameOverAnimation start")
|
||||||
|
|
||||||
view.RefreshScreen()
|
switch rand.Intn(3) {
|
||||||
|
case 0:
|
||||||
for y := boardHeight - 1; y >= 0; y-- {
|
for y := board.height - 1; y >= 0; y-- {
|
||||||
view.colorizeLine(y, termbox.ColorBlack)
|
view.colorizeLine(y, termbox.ColorBlack)
|
||||||
termbox.Flush()
|
termbox.Flush()
|
||||||
time.Sleep(60 * time.Millisecond)
|
time.Sleep(60 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
view.drawDropMarkerDisabled = false
|
case 1:
|
||||||
|
for y := 0; y < board.height; y++ {
|
||||||
|
view.colorizeLine(y, termbox.ColorBlack)
|
||||||
|
termbox.Flush()
|
||||||
|
time.Sleep(60 * time.Millisecond)
|
||||||
|
}
|
||||||
|
|
||||||
|
case 2:
|
||||||
|
sleepTime := 50 * time.Millisecond
|
||||||
|
topStartX := boardXOffset + 3
|
||||||
|
topEndX := board.width*2 + boardXOffset + 1
|
||||||
|
topY := boardYOffset + 1
|
||||||
|
rightStartY := boardYOffset + 1
|
||||||
|
rightEndY := board.height + boardYOffset + 1
|
||||||
|
rightX := board.width*2 + boardXOffset + 1
|
||||||
|
bottomStartX := topEndX - 1
|
||||||
|
bottomEndX := topStartX - 1
|
||||||
|
bottomY := board.height + boardYOffset
|
||||||
|
leftStartY := rightEndY - 1
|
||||||
|
leftEndY := rightStartY - 1
|
||||||
|
leftX := boardXOffset + 2
|
||||||
|
|
||||||
|
for topStartX <= topEndX && rightStartY <= rightEndY {
|
||||||
|
for x := topStartX; x < topEndX; x++ {
|
||||||
|
termbox.SetCell(x, topY, ' ', termbox.ColorBlack, termbox.ColorBlack)
|
||||||
|
}
|
||||||
|
topStartX++
|
||||||
|
topEndX--
|
||||||
|
topY++
|
||||||
|
for y := rightStartY; y < rightEndY; y++ {
|
||||||
|
termbox.SetCell(rightX, y, ' ', termbox.ColorBlack, termbox.ColorBlack)
|
||||||
|
}
|
||||||
|
rightStartY++
|
||||||
|
rightEndY--
|
||||||
|
rightX--
|
||||||
|
for x := bottomStartX; x > bottomEndX; x-- {
|
||||||
|
termbox.SetCell(x, bottomY, ' ', termbox.ColorBlack, termbox.ColorBlack)
|
||||||
|
}
|
||||||
|
bottomStartX--
|
||||||
|
bottomEndX++
|
||||||
|
bottomY--
|
||||||
|
for y := leftStartY; y > leftEndY; y-- {
|
||||||
|
termbox.SetCell(leftX, y, ' ', termbox.ColorBlack, termbox.ColorBlack)
|
||||||
|
}
|
||||||
|
leftStartY--
|
||||||
|
leftEndY++
|
||||||
|
leftX++
|
||||||
|
termbox.Flush()
|
||||||
|
time.Sleep(sleepTime)
|
||||||
|
sleepTime += 4 * time.Millisecond
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Info("View ShowGameOverAnimation end")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (view *View) colorizeLine(y int, color termbox.Attribute) {
|
func (view *View) colorizeLine(y int, color termbox.Attribute) {
|
||||||
for x := 0; x < boardWidth; x++ {
|
for x := 0; x < board.width; x++ {
|
||||||
termbox.SetCell(x*2+boardXOffset+2, y+boardYOffset+1, ' ', termbox.ColorDefault, color)
|
termbox.SetCell(x*2+boardXOffset+2, y+boardYOffset+1, ' ', termbox.ColorDefault, color)
|
||||||
termbox.SetCell(x*2+boardXOffset+3, y+boardYOffset+1, ' ', termbox.ColorDefault, color)
|
termbox.SetCell(x*2+boardXOffset+3, y+boardYOffset+1, ' ', termbox.ColorDefault, color)
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue