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
|
||||
|
||||
|
||||
## Keys
|
||||
## Keys during game
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
|
@ -30,6 +30,15 @@ Then run the binary created, tetris or tetris.exe
|
|||
| q | quit |
|
||||
| i | toggle AI |
|
||||
|
||||
## Keys start screen
|
||||
|
||||
| Key | Action |
|
||||
| --- | --- |
|
||||
| ← | previous board |
|
||||
| → | next board |
|
||||
| spacebar | start game |
|
||||
| q | quit |
|
||||
|
||||
|
||||
## Features include
|
||||
|
||||
|
@ -38,6 +47,7 @@ Then run the binary created, tetris or tetris.exe
|
|||
- Next piece
|
||||
- Ghost piece
|
||||
- Top scores
|
||||
- Board choices
|
||||
|
||||
|
||||
## 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/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)
|
||||
|
|
173
ai.go
173
ai.go
|
@ -1,26 +1,22 @@
|
|||
package main
|
||||
|
||||
const (
|
||||
aiQueueSize = (boardWidth+1)/2 + 6
|
||||
aiMoveLength = (boardWidth + 1) / 2
|
||||
)
|
||||
|
||||
type Ai struct {
|
||||
queue [aiQueueSize]rune
|
||||
index int
|
||||
}
|
||||
|
||||
func NewAi() *Ai {
|
||||
ai := Ai{}
|
||||
for i := 0; i < aiQueueSize; i++ {
|
||||
ai.queue[i] = 'x'
|
||||
}
|
||||
queue := make([]rune, 1)
|
||||
queue[0] = 'x'
|
||||
ai.queue = &queue
|
||||
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
|
||||
switch ai.queue[ai.index] {
|
||||
switch queue[ai.index] {
|
||||
case 'w':
|
||||
board.MinoDrop()
|
||||
case 'a':
|
||||
|
@ -32,51 +28,31 @@ func (ai *Ai) ProcessQueue() bool {
|
|||
case 'e':
|
||||
board.MinoRotateRight()
|
||||
case 'x':
|
||||
return false
|
||||
return
|
||||
}
|
||||
ai.index++
|
||||
if ai.index == aiQueueSize {
|
||||
ai.index = 0
|
||||
}
|
||||
view.RefreshScreen()
|
||||
return true
|
||||
}
|
||||
|
||||
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
|
||||
var slideScore int
|
||||
bestSlide := 6
|
||||
bestQueue := make([]rune, 0, 0)
|
||||
currentMino := *board.currentMino
|
||||
|
||||
for move1 := 0; move1 <= boardWidth; move1++ {
|
||||
for rotate1 := 0; rotate1 < 5; rotate1++ {
|
||||
for slide1 := 0; slide1 <= 5; slide1++ {
|
||||
for slide1 := 0; slide1 < 5; slide1++ {
|
||||
for move1 := board.width; move1 >= 0; move1-- {
|
||||
for rotate1 := 0; rotate1 < 5; rotate1++ {
|
||||
|
||||
queue, mino1 := ai.getMovesforMino(rotate1, move1, slide1, nil)
|
||||
queue, mino1 := board.getMovesforMino(rotate1, move1, slide1, ¤tMino, nil)
|
||||
if mino1 == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for move2 := 0; move2 <= boardWidth; move2++ {
|
||||
for rotate2 := 0; rotate2 < 5; rotate2++ {
|
||||
for slide2 := 0; slide2 <= 5; slide2++ {
|
||||
for slide2 := 0; slide2 < 5; slide2++ {
|
||||
for move2 := board.width; move2 >= 0; move2-- {
|
||||
for rotate2 := 0; rotate2 < 5; rotate2++ {
|
||||
|
||||
_, mino2 := ai.getMovesforMino(rotate2, move2, slide2, mino1)
|
||||
_, mino2 := board.getMovesforMino(rotate2, move2, slide2, board.previewMino, mino1)
|
||||
if mino2 == nil {
|
||||
continue
|
||||
}
|
||||
|
@ -84,16 +60,9 @@ func (ai *Ai) getBestQueue() []rune {
|
|||
fullLines, holes, bumpy := board.boardStatsWithMinos(mino1, mino2)
|
||||
score := ai.getScoreFromBoardStats(fullLines, holes, bumpy)
|
||||
|
||||
if slide1 < 3 {
|
||||
slideScore = slide1
|
||||
} else {
|
||||
slideScore = slide1 - 2
|
||||
}
|
||||
|
||||
if score > bestScore || (score == bestScore && slideScore < bestSlide) {
|
||||
if score > bestScore {
|
||||
bestScore = score
|
||||
bestQueue = queue
|
||||
bestSlide = slideScore
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -104,100 +73,95 @@ func (ai *Ai) getBestQueue() []rune {
|
|||
}
|
||||
}
|
||||
|
||||
return bestQueue
|
||||
if len(bestQueue) < 1 {
|
||||
bestQueue = append(bestQueue, 'x')
|
||||
}
|
||||
|
||||
ai.newQueue = &bestQueue
|
||||
}
|
||||
|
||||
func (ai *Ai) getMovesforMino(rotate int, move int, slide int, mino1 *Mino) ([]rune, *Mino) {
|
||||
func (board *Board) getMovesforMino(rotate int, move int, slide int, mino1 *Mino, mino2 *Mino) ([]rune, *Mino) {
|
||||
queue := make([]rune, 0, 4)
|
||||
var mino Mino
|
||||
if mino1 != nil {
|
||||
mino = *board.previewMino
|
||||
} else {
|
||||
mino = *board.currentMino
|
||||
}
|
||||
if rotate < 3 {
|
||||
mino := *mino1
|
||||
|
||||
if rotate%2 == 0 {
|
||||
rotate /= 2
|
||||
for i := 0; i < rotate; i++ {
|
||||
mino.RotateRight()
|
||||
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
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for i := 0; i < rotate-2; i++ {
|
||||
rotate = rotate/2 + 1
|
||||
for i := 0; i < rotate; i++ {
|
||||
mino.RotateLeft()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
if move <= aiMoveLength {
|
||||
move = aiMoveLength - move
|
||||
|
||||
if move%2 == 0 {
|
||||
move /= 2
|
||||
for i := 0; i < move; i++ {
|
||||
mino.MoveLeft()
|
||||
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
|
||||
}
|
||||
}
|
||||
} else {
|
||||
move = move - aiMoveLength + 1
|
||||
move = move/2 + 1
|
||||
for i := 0; i < move; i++ {
|
||||
mino.MoveRight()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
for mino.ValidLocation(false) && (mino1 == nil || !mino1.minoOverlap(&mino)) {
|
||||
for mino.ValidLocation(false) && (mino2 == nil || !mino2.minoOverlap(&mino)) {
|
||||
mino.MoveDown()
|
||||
}
|
||||
mino.MoveUp()
|
||||
queue = append(queue, 'w')
|
||||
if slide < 3 {
|
||||
|
||||
if slide%2 == 0 {
|
||||
slide /= 2
|
||||
for i := 0; i < slide; i++ {
|
||||
mino.MoveLeft()
|
||||
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
|
||||
}
|
||||
}
|
||||
} else {
|
||||
slide = slide - 2
|
||||
slide = slide/2 + 1
|
||||
for i := 0; i < slide; i++ {
|
||||
mino.MoveRight()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !mino.ValidLocation(true) {
|
||||
return queue, nil
|
||||
}
|
||||
queue = append(queue, 'x')
|
||||
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) {
|
||||
// fullLines
|
||||
fullLinesY := make(map[int]bool, 2)
|
||||
for j := 0; j < boardHeight; j++ {
|
||||
for j := 0; j < board.height; j++ {
|
||||
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) {
|
||||
isFullLine = false
|
||||
break
|
||||
|
@ -211,10 +175,10 @@ func (board *Board) boardStatsWithMinos(mino1 *Mino, mino2 *Mino) (fullLines int
|
|||
|
||||
// holes and bumpy
|
||||
indexLast := 0
|
||||
for i := 0; i < boardWidth; i++ {
|
||||
index := boardHeight
|
||||
for i := 0; i < board.width; i++ {
|
||||
index := board.height
|
||||
indexOffset := 0
|
||||
for j := 0; j < boardHeight; j++ {
|
||||
for j := 0; j < board.height; j++ {
|
||||
if _, found := fullLinesY[j]; found {
|
||||
indexOffset++
|
||||
} else {
|
||||
|
@ -236,7 +200,7 @@ func (board *Board) boardStatsWithMinos(mino1 *Mino, mino2 *Mino) (fullLines int
|
|||
indexLast = index + fullLines - indexOffset
|
||||
|
||||
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) {
|
||||
holes++
|
||||
}
|
||||
|
@ -245,24 +209,9 @@ func (board *Board) boardStatsWithMinos(mino1 *Mino, mino2 *Mino) (fullLines int
|
|||
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) {
|
||||
if fullLines == 4 {
|
||||
score += 256
|
||||
score += 512
|
||||
}
|
||||
score -= 75 * holes
|
||||
score -= 25 * bumpy
|
||||
|
|
739
ai_test.go
739
ai_test.go
|
@ -1,494 +1,291 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"gopkg.in/inconshreveable/log15.v2"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
setupForTesting()
|
||||
retCode := m.Run()
|
||||
os.Exit(retCode)
|
||||
type testAiStruct struct {
|
||||
info string
|
||||
minos []testMinoStruct
|
||||
fullLines int
|
||||
holes int
|
||||
bumpy int
|
||||
}
|
||||
|
||||
func setupForTesting() {
|
||||
logger = log15.New()
|
||||
logger.SetHandler(log15.StreamHandler(os.Stdout, log15.LogfmtFormat()))
|
||||
func TestAI(t *testing.T) {
|
||||
// this must be set to the blank 10x20 board
|
||||
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},
|
||||
}
|
||||
|
||||
runAiTests(t, tests)
|
||||
}
|
||||
|
||||
func TestBoardStatsFullLines1(t *testing.T) {
|
||||
board = NewBoard()
|
||||
func TestBigBoardAI(t *testing.T) {
|
||||
// this must be set to the blank 20x20 board
|
||||
board.boardsIndex = 3
|
||||
board.Clear()
|
||||
|
||||
// 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 = 2
|
||||
mino1.y = 18
|
||||
|
||||
// minoI
|
||||
mino2 := &Mino{
|
||||
minoRotation: minoBag[0],
|
||||
}
|
||||
mino2.length = len(mino2.minoRotation[0])
|
||||
mino2.x = 6
|
||||
mino2.y = 18
|
||||
|
||||
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)
|
||||
tests := []testAiStruct{
|
||||
{info: "fullLines 4x 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
|
||||
}, 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},
|
||||
}
|
||||
|
||||
// for debuging
|
||||
// mino1.SetOnBoard()
|
||||
// mino2.SetOnBoard()
|
||||
// board.drawDebugBoard()
|
||||
runAiTests(t, tests)
|
||||
}
|
||||
|
||||
func TestBoardStatsFullLines2(t *testing.T) {
|
||||
board = NewBoard()
|
||||
func runAiTests(t *testing.T, tests []testAiStruct) {
|
||||
var mino1 *Mino
|
||||
var mino2 *Mino
|
||||
|
||||
// minoO
|
||||
mino1 := &Mino{
|
||||
minoRotation: minoBag[3],
|
||||
}
|
||||
mino1.length = len(mino1.minoRotation[0])
|
||||
mino1.x = 0
|
||||
mino1.y = 18
|
||||
mino1.SetOnBoard()
|
||||
for _, test := range tests {
|
||||
board.Clear()
|
||||
|
||||
// minoO
|
||||
mino1 = &Mino{
|
||||
minoRotation: minoBag[3],
|
||||
}
|
||||
mino1.length = len(mino1.minoRotation[0])
|
||||
mino1.x = 2
|
||||
mino1.y = 18
|
||||
mino1.SetOnBoard()
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// minoO
|
||||
mino1 = &Mino{
|
||||
minoRotation: minoBag[3],
|
||||
}
|
||||
mino1.length = len(mino1.minoRotation[0])
|
||||
mino1.x = 4
|
||||
mino1.y = 18
|
||||
mino1.SetOnBoard()
|
||||
fullLines, holes, bumpy := board.boardStatsWithMinos(mino1, mino2)
|
||||
|
||||
// minoO
|
||||
mino1 = &Mino{
|
||||
minoRotation: minoBag[3],
|
||||
}
|
||||
mino1.length = len(mino1.minoRotation[0])
|
||||
mino1.x = 6
|
||||
mino1.y = 18
|
||||
mino1.SetOnBoard()
|
||||
if fullLines != test.fullLines {
|
||||
mino1.SetOnBoard()
|
||||
lines := board.getDebugBoardWithMino(mino2)
|
||||
for i := 0; i < len(lines); i++ {
|
||||
t.Log(lines[i])
|
||||
}
|
||||
t.Errorf("AI fullLines - received: %v - expected: %v - info %v", fullLines, test.fullLines, test.info)
|
||||
continue
|
||||
}
|
||||
if holes != test.holes {
|
||||
mino1.SetOnBoard()
|
||||
lines := board.getDebugBoardWithMino(mino2)
|
||||
for i := 0; i < len(lines); i++ {
|
||||
t.Log(lines[i])
|
||||
}
|
||||
t.Errorf("AI holes - received: %v - expected: %v - info %v", holes, test.holes, test.info)
|
||||
continue
|
||||
}
|
||||
if bumpy != test.bumpy {
|
||||
mino1.SetOnBoard()
|
||||
lines := board.getDebugBoardWithMino(mino2)
|
||||
for i := 0; i < len(lines); i++ {
|
||||
t.Log(lines[i])
|
||||
}
|
||||
t.Errorf("AI bumpy - received: %v - expected: %v - info %v", bumpy, test.bumpy, test.info)
|
||||
continue
|
||||
}
|
||||
|
||||
// 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 (
|
||||
"fmt"
|
||||
"github.com/nsf/termbox-go"
|
||||
"time"
|
||||
|
||||
"github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
type Board struct {
|
||||
colors [boardWidth][boardHeight]termbox.Attribute
|
||||
rotation [boardWidth][boardHeight]int
|
||||
previewMino *Mino
|
||||
currentMino *Mino
|
||||
dropDistance int
|
||||
func NewBoard() {
|
||||
board = &Board{}
|
||||
board.Clear()
|
||||
}
|
||||
|
||||
func NewBoard() *Board {
|
||||
board := &Board{}
|
||||
for i := 0; i < boardWidth; i++ {
|
||||
for j := 0; j < boardHeight; j++ {
|
||||
board.colors[i][j] = blankColor
|
||||
}
|
||||
func (board *Board) Clear() {
|
||||
board.width = len(boards[board.boardsIndex].colors)
|
||||
board.height = len(boards[board.boardsIndex].colors[0])
|
||||
board.colors = make([][]termbox.Attribute, len(boards[board.boardsIndex].colors))
|
||||
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.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() {
|
||||
|
@ -160,7 +176,7 @@ func (board *Board) nextMino() {
|
|||
|
||||
board.currentMino = board.previewMino
|
||||
board.previewMino = NewMino()
|
||||
engine.ResetAiTimer()
|
||||
engine.AiGetBestQueue()
|
||||
engine.ResetTimer(0)
|
||||
}
|
||||
|
||||
|
@ -180,7 +196,7 @@ func (board *Board) deleteCheck() {
|
|||
|
||||
func (board *Board) fullLines() []int {
|
||||
fullLines := make([]int, 0, 1)
|
||||
for j := 0; j < boardHeight; j++ {
|
||||
for j := 0; j < board.height; j++ {
|
||||
if board.isFullLine(j) {
|
||||
fullLines = append(fullLines, j)
|
||||
}
|
||||
|
@ -189,7 +205,7 @@ func (board *Board) fullLines() []int {
|
|||
}
|
||||
|
||||
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 {
|
||||
return false
|
||||
}
|
||||
|
@ -198,16 +214,16 @@ func (board *Board) isFullLine(j int) bool {
|
|||
}
|
||||
|
||||
func (board *Board) deleteLine(line int) {
|
||||
for i := 0; i < boardWidth; i++ {
|
||||
for i := 0; i < board.width; i++ {
|
||||
board.colors[i][line] = blankColor
|
||||
}
|
||||
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.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
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
if x < 0 || x >= boardWidth || y >= boardHeight {
|
||||
if x < 0 || x >= board.width || y >= board.height {
|
||||
return false
|
||||
}
|
||||
if mustBeOnBoard {
|
||||
|
@ -239,12 +255,12 @@ func ValidBlockLocation(x int, y int, mustBeOnBoard bool) 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() {
|
||||
for i := 0; i < boardWidth; i++ {
|
||||
for j := 0; j < boardHeight; j++ {
|
||||
for i := 0; i < board.width; i++ {
|
||||
for j := 0; j < board.height; j++ {
|
||||
if board.colors[i][j] != blankColor {
|
||||
view.DrawBlock(i, j, board.colors[i][j], board.rotation[i][j])
|
||||
}
|
||||
|
@ -273,9 +289,9 @@ func (board *Board) DrawDropMino() {
|
|||
}
|
||||
|
||||
// for debuging
|
||||
func (board *Board) drawDebugBoard() {
|
||||
for j := 0; j < boardHeight; j++ {
|
||||
for i := 0; i < boardWidth; i++ {
|
||||
func (board *Board) printDebugBoard() {
|
||||
for j := 0; j < board.height; j++ {
|
||||
for i := 0; i < board.width; i++ {
|
||||
switch board.colors[i][j] {
|
||||
case blankColor:
|
||||
fmt.Print(".")
|
||||
|
@ -293,8 +309,89 @@ func (board *Board) drawDebugBoard() {
|
|||
fmt.Print("W")
|
||||
case termbox.ColorYellow:
|
||||
fmt.Print("Y")
|
||||
default:
|
||||
fmt.Print("U")
|
||||
}
|
||||
}
|
||||
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
|
||||
|
||||
import (
|
||||
"github.com/nsf/termbox-go"
|
||||
"time"
|
||||
|
||||
"github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
type Engine struct {
|
||||
stopped bool
|
||||
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{
|
||||
func NewEngine() {
|
||||
engine = &Engine{
|
||||
chanStop: make(chan struct{}, 1),
|
||||
gameOver: true,
|
||||
tickTime: time.Hour,
|
||||
|
@ -42,7 +26,7 @@ func (engine *Engine) Run() {
|
|||
engine.aiTimer.Stop()
|
||||
|
||||
engine.ranking = NewRanking()
|
||||
board = NewBoard()
|
||||
board.Clear()
|
||||
view.RefreshScreen()
|
||||
|
||||
engine.keyInput = NewKeyInput()
|
||||
|
@ -61,9 +45,8 @@ loop:
|
|||
case <-engine.timer.C:
|
||||
engine.tick()
|
||||
case <-engine.aiTimer.C:
|
||||
if engine.ai.ProcessQueue() {
|
||||
engine.aiTimer.Reset(engine.tickTime / 4)
|
||||
}
|
||||
engine.ai.ProcessQueue()
|
||||
engine.aiTimer.Reset(engine.tickTime / 6)
|
||||
case <-engine.chanStop:
|
||||
break loop
|
||||
}
|
||||
|
@ -105,15 +88,19 @@ func (engine *Engine) Pause() {
|
|||
func (engine *Engine) UnPause() {
|
||||
engine.timer.Reset(engine.tickTime)
|
||||
if engine.aiEnabled {
|
||||
engine.aiTimer.Reset(engine.tickTime / 4)
|
||||
engine.aiTimer.Reset(engine.tickTime / 6)
|
||||
}
|
||||
engine.paused = false
|
||||
}
|
||||
|
||||
func (engine *Engine) PreviewBoard() {
|
||||
engine.previewBoard = true
|
||||
}
|
||||
|
||||
func (engine *Engine) NewGame() {
|
||||
logger.Info("Engine NewGame start")
|
||||
|
||||
board = NewBoard()
|
||||
board.Clear()
|
||||
engine.tickTime = 480 * time.Millisecond
|
||||
engine.score = 0
|
||||
engine.level = 1
|
||||
|
@ -128,6 +115,7 @@ loop:
|
|||
}
|
||||
}
|
||||
|
||||
engine.previewBoard = false
|
||||
engine.gameOver = false
|
||||
if engine.aiEnabled {
|
||||
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 {
|
||||
return
|
||||
}
|
||||
engine.ai.GetBestQueue()
|
||||
engine.aiTimer.Reset(engine.tickTime / 4)
|
||||
go engine.ai.GetBestQueue()
|
||||
}
|
||||
|
||||
func (engine *Engine) tick() {
|
||||
|
@ -190,8 +177,8 @@ func (engine *Engine) AddDeleteLines(lines int) {
|
|||
|
||||
func (engine *Engine) AddScore(add int) {
|
||||
engine.score += add
|
||||
if engine.score > 999999 {
|
||||
engine.score = 999999
|
||||
if engine.score > 9999999 {
|
||||
engine.score = 9999999
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -222,13 +209,9 @@ func (engine *Engine) GameOver() {
|
|||
logger.Info("Engine GameOver start")
|
||||
|
||||
engine.Pause()
|
||||
|
||||
view.ShowGameOverAnimation()
|
||||
|
||||
engine.gameOver = true
|
||||
|
||||
engine.ranking.InsertScore(uint64(engine.score))
|
||||
engine.ranking.Save()
|
||||
view.ShowGameOverAnimation()
|
||||
|
||||
loop:
|
||||
for {
|
||||
|
@ -239,21 +222,24 @@ loop:
|
|||
}
|
||||
}
|
||||
|
||||
engine.ranking.InsertScore(uint64(engine.score))
|
||||
engine.ranking.Save()
|
||||
|
||||
logger.Info("Engine GameOver end")
|
||||
}
|
||||
|
||||
func (engine *Engine) EnabledAi() {
|
||||
engine.aiEnabled = true
|
||||
engine.ai.GetBestQueue()
|
||||
engine.aiTimer.Reset(engine.tickTime / 4)
|
||||
go engine.ai.GetBestQueue()
|
||||
engine.aiTimer.Reset(engine.tickTime / 6)
|
||||
}
|
||||
|
||||
func (engine *Engine) DisableAi() {
|
||||
engine.aiEnabled = false
|
||||
if !engine.aiTimer.Stop() {
|
||||
select {
|
||||
case <-engine.aiTimer.C:
|
||||
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
|
||||
)
|
30
keyInput.go
30
keyInput.go
|
@ -1,15 +1,10 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"github.com/nsf/termbox-go"
|
||||
"runtime"
|
||||
)
|
||||
|
||||
type KeyInput struct {
|
||||
stopped bool
|
||||
chanStop chan struct{}
|
||||
chanKeyInput chan *termbox.Event
|
||||
}
|
||||
"github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
func NewKeyInput() *KeyInput {
|
||||
return &KeyInput{
|
||||
|
@ -29,16 +24,11 @@ loop:
|
|||
default:
|
||||
}
|
||||
event := termbox.PollEvent()
|
||||
if event.Type == termbox.EventKey {
|
||||
if event.Type == termbox.EventKey && len(keyInput.chanKeyInput) < 8 {
|
||||
select {
|
||||
case <-keyInput.chanStop:
|
||||
break loop
|
||||
default:
|
||||
select {
|
||||
case keyInput.chanKeyInput <- &event:
|
||||
case <-keyInput.chanStop:
|
||||
break loop
|
||||
}
|
||||
case keyInput.chanKeyInput <- &event:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -47,7 +37,6 @@ loop:
|
|||
}
|
||||
|
||||
func (keyInput *KeyInput) ProcessEvent(event *termbox.Event) {
|
||||
|
||||
if event.Key == termbox.KeyCtrlI {
|
||||
// ctrl i to log stack trace
|
||||
buffer := make([]byte, 1<<16)
|
||||
|
@ -66,8 +55,15 @@ func (keyInput *KeyInput) ProcessEvent(event *termbox.Event) {
|
|||
}
|
||||
|
||||
if engine.gameOver {
|
||||
if event.Key == termbox.KeySpace {
|
||||
engine.NewGame()
|
||||
if event.Ch == 0 {
|
||||
switch event.Key {
|
||||
case termbox.KeySpace:
|
||||
engine.NewGame()
|
||||
case termbox.KeyArrowLeft:
|
||||
board.PreviousBoard()
|
||||
case termbox.KeyArrowRight:
|
||||
board.NextBoard()
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
81
mino.go
81
mino.go
|
@ -2,47 +2,22 @@ package main
|
|||
|
||||
import (
|
||||
"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 {
|
||||
minoRotation := minoBag[bagRand[bagIndex]]
|
||||
bagIndex++
|
||||
if bagIndex > 6 {
|
||||
bagIndex = 0
|
||||
bagRand = rand.Perm(7)
|
||||
minoRotation := minos.minoBag[minos.bagRand[minos.bagIndex]]
|
||||
minos.bagIndex++
|
||||
if minos.bagIndex > 6 {
|
||||
minos.bagIndex = 0
|
||||
minos.bagRand = rand.Perm(7)
|
||||
}
|
||||
mino := &Mino{
|
||||
minoRotation: minoRotation,
|
||||
length: len(minoRotation[0]),
|
||||
}
|
||||
mino.x = boardWidth/2 - (mino.length+1)/2
|
||||
mino.x = board.width/2 - (mino.length+1)/2
|
||||
mino.y = -1
|
||||
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
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
|
||||
"github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
type MinoBlocks [][]termbox.Attribute
|
||||
|
||||
type MinoRotation [4]MinoBlocks
|
||||
|
||||
var minoBag [7]MinoRotation
|
||||
|
||||
func init() {
|
||||
func NewMinos() {
|
||||
minoI := MinoBlocks{
|
||||
[]termbox.Attribute{blankColor, termbox.ColorCyan, blankColor, blankColor},
|
||||
[]termbox.Attribute{blankColor, termbox.ColorCyan, blankColor, blankColor},
|
||||
|
@ -50,17 +46,17 @@ func init() {
|
|||
var minoRotationI MinoRotation
|
||||
minoRotationI[0] = minoI
|
||||
for i := 1; i < 4; i++ {
|
||||
minoRotationI[i] = initCloneRotateRight(minoRotationI[i-1])
|
||||
minoRotationI[i] = minosCloneRotateRight(minoRotationI[i-1])
|
||||
}
|
||||
var minoRotationJ MinoRotation
|
||||
minoRotationJ[0] = minoJ
|
||||
for i := 1; i < 4; i++ {
|
||||
minoRotationJ[i] = initCloneRotateRight(minoRotationJ[i-1])
|
||||
minoRotationJ[i] = minosCloneRotateRight(minoRotationJ[i-1])
|
||||
}
|
||||
var minoRotationL MinoRotation
|
||||
minoRotationL[0] = minoL
|
||||
for i := 1; i < 4; i++ {
|
||||
minoRotationL[i] = initCloneRotateRight(minoRotationL[i-1])
|
||||
minoRotationL[i] = minosCloneRotateRight(minoRotationL[i-1])
|
||||
}
|
||||
var minoRotationO MinoRotation
|
||||
minoRotationO[0] = minoO
|
||||
|
@ -70,23 +66,26 @@ func init() {
|
|||
var minoRotationS MinoRotation
|
||||
minoRotationS[0] = minoS
|
||||
for i := 1; i < 4; i++ {
|
||||
minoRotationS[i] = initCloneRotateRight(minoRotationS[i-1])
|
||||
minoRotationS[i] = minosCloneRotateRight(minoRotationS[i-1])
|
||||
}
|
||||
var minoRotationT MinoRotation
|
||||
minoRotationT[0] = minoT
|
||||
for i := 1; i < 4; i++ {
|
||||
minoRotationT[i] = initCloneRotateRight(minoRotationT[i-1])
|
||||
minoRotationT[i] = minosCloneRotateRight(minoRotationT[i-1])
|
||||
}
|
||||
var minoRotationZ MinoRotation
|
||||
minoRotationZ[0] = minoZ
|
||||
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)
|
||||
newMinoBlocks := make(MinoBlocks, length, length)
|
||||
for i := 0; i < length; i++ {
|
||||
|
|
17
ranking.go
17
ranking.go
|
@ -8,21 +8,13 @@ import (
|
|||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
rankingFileName = "/tetris.db"
|
||||
)
|
||||
|
||||
type Ranking struct {
|
||||
scores []uint64
|
||||
}
|
||||
|
||||
func NewRanking() *Ranking {
|
||||
ranking := &Ranking{
|
||||
scores: make([]uint64, 10),
|
||||
scores: make([]uint64, 9),
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
return ranking
|
||||
|
@ -35,6 +27,9 @@ func NewRanking() *Ranking {
|
|||
|
||||
scoreStrings := strings.Split(string(scoreBytes), ",")
|
||||
for index, scoreString := range scoreStrings {
|
||||
if index > 8 {
|
||||
break
|
||||
}
|
||||
score, err := strconv.ParseUint(scoreString, 10, 64)
|
||||
if err != nil {
|
||||
logger.Error("NewRanking ParseUint", "error", err.Error())
|
||||
|
@ -49,7 +44,7 @@ func NewRanking() *Ranking {
|
|||
func (ranking *Ranking) Save() {
|
||||
var buffer bytes.Buffer
|
||||
|
||||
for i := 0; i < 10; i++ {
|
||||
for i := 0; i < 9; i++ {
|
||||
if i != 0 {
|
||||
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
|
||||
|
||||
import (
|
||||
"github.com/nsf/termbox-go"
|
||||
"gopkg.in/inconshreveable/log15.v2"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
"time"
|
||||
|
||||
const (
|
||||
boardWidth = 10
|
||||
boardHeight = 20
|
||||
blankColor = termbox.ColorBlack
|
||||
)
|
||||
|
||||
var (
|
||||
baseDir string
|
||||
logger log15.Logger
|
||||
view *View
|
||||
engine *Engine
|
||||
board *Board
|
||||
"gopkg.in/inconshreveable/log15.v2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
@ -29,8 +17,12 @@ func main() {
|
|||
logger.SetHandler(log15.Must.FileHandler(baseDir+"/tetris.log", log15.LogfmtFormat()))
|
||||
}
|
||||
|
||||
view = NewView()
|
||||
engine = NewEngine()
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
|
||||
NewMinos()
|
||||
NewBoard()
|
||||
NewView()
|
||||
NewEngine()
|
||||
|
||||
engine.Run()
|
||||
|
||||
|
|
146
view.go
146
view.go
|
@ -2,27 +2,20 @@ package main
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/nsf/termbox-go"
|
||||
"math/rand"
|
||||
"time"
|
||||
|
||||
"github.com/nsf/termbox-go"
|
||||
)
|
||||
|
||||
const (
|
||||
boardXOffset = 4
|
||||
boardYOffset = 2
|
||||
)
|
||||
|
||||
type View struct {
|
||||
drawDropMarkerDisabled bool
|
||||
}
|
||||
|
||||
func NewView() *View {
|
||||
func NewView() {
|
||||
err := termbox.Init()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
termbox.SetInputMode(termbox.InputEsc)
|
||||
termbox.Flush()
|
||||
return &View{}
|
||||
view = &View{}
|
||||
}
|
||||
|
||||
func (view *View) Stop() {
|
||||
|
@ -38,12 +31,18 @@ func (view *View) RefreshScreen() {
|
|||
|
||||
view.drawBackground()
|
||||
view.drawTexts()
|
||||
board.DrawPreviewMino()
|
||||
|
||||
if engine.gameOver {
|
||||
if engine.previewBoard {
|
||||
board.DrawBoard()
|
||||
view.drawGameOver()
|
||||
} else if engine.gameOver {
|
||||
view.drawGameOver()
|
||||
view.drawRankingScores()
|
||||
} else if engine.paused {
|
||||
view.drawPaused()
|
||||
} else {
|
||||
board.DrawBoard()
|
||||
board.DrawPreviewMino()
|
||||
board.DrawDropMino()
|
||||
board.DrawCurrentMino()
|
||||
}
|
||||
|
@ -55,8 +54,8 @@ func (view *View) drawBackground() {
|
|||
// playing board
|
||||
xOffset := boardXOffset
|
||||
yOffset := boardYOffset
|
||||
xEnd := boardXOffset + boardWidth*2 + 4
|
||||
yEnd := boardYOffset + boardHeight + 2
|
||||
xEnd := boardXOffset + board.width*2 + 4
|
||||
yEnd := boardYOffset + board.height + 2
|
||||
for x := xOffset; x < xEnd; x++ {
|
||||
for y := yOffset; y < yEnd; y++ {
|
||||
if x == xOffset || x == xOffset+1 || x == xEnd-1 || x == xEnd-2 ||
|
||||
|
@ -69,7 +68,7 @@ func (view *View) drawBackground() {
|
|||
}
|
||||
|
||||
// piece preview
|
||||
xOffset = boardXOffset + boardWidth*2 + 8
|
||||
xOffset = boardXOffset + board.width*2 + 8
|
||||
yOffset = boardYOffset
|
||||
xEnd = xOffset + 14
|
||||
yEnd = yOffset + 6
|
||||
|
@ -87,7 +86,7 @@ func (view *View) drawBackground() {
|
|||
}
|
||||
|
||||
func (view *View) drawTexts() {
|
||||
xOffset := boardXOffset + boardWidth*2 + 8
|
||||
xOffset := boardXOffset + board.width*2 + 8
|
||||
yOffset := boardYOffset + 7
|
||||
|
||||
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 = ' '
|
||||
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+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() {
|
||||
xOffset := boardXOffset + 4
|
||||
yOffset := boardYOffset + 2
|
||||
func (view *View) drawPaused() {
|
||||
yOffset := (board.height+1)/2 + boardYOffset
|
||||
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
|
||||
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
|
||||
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 {
|
||||
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) {
|
||||
view.drawDropMarkerDisabled = true
|
||||
func (view *View) drawTextCenter(y int, text string, fg termbox.Attribute, bg termbox.Attribute) {
|
||||
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()
|
||||
|
||||
for times := 0; times < 3; times++ {
|
||||
|
@ -195,26 +214,77 @@ func (view *View) ShowDeleteAnimation(lines []int) {
|
|||
view.RefreshScreen()
|
||||
time.Sleep(140 * time.Millisecond)
|
||||
}
|
||||
|
||||
view.drawDropMarkerDisabled = false
|
||||
}
|
||||
|
||||
func (view *View) ShowGameOverAnimation() {
|
||||
view.drawDropMarkerDisabled = true
|
||||
logger.Info("View ShowGameOverAnimation start")
|
||||
|
||||
view.RefreshScreen()
|
||||
switch rand.Intn(3) {
|
||||
case 0:
|
||||
for y := board.height - 1; y >= 0; y-- {
|
||||
view.colorizeLine(y, termbox.ColorBlack)
|
||||
termbox.Flush()
|
||||
time.Sleep(60 * time.Millisecond)
|
||||
}
|
||||
|
||||
for y := boardHeight - 1; y >= 0; y-- {
|
||||
view.colorizeLine(y, termbox.ColorBlack)
|
||||
termbox.Flush()
|
||||
time.Sleep(60 * time.Millisecond)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
view.drawDropMarkerDisabled = false
|
||||
logger.Info("View ShowGameOverAnimation end")
|
||||
}
|
||||
|
||||
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+3, y+boardYOffset+1, ' ', termbox.ColorDefault, color)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue