文章目錄
- 效果圖
- 游戲概念
- 操作設計
- 演算法規則
- 整體演算法概述
- 代碼實操
- 操作設計
- 可視化View
- MainActivity
- 原始碼下載
效果圖
廢話不多說,先干效果圖,原始碼在文章末尾


游戲概念
《俄羅斯方塊》是由七種方塊,開始時,一個 落下期間,玩家可以以90度為單位旋轉方塊,以格子為單位左右移動方塊,或讓方塊加速落下,當方塊下落到區域最下方或著落到其他方塊上無法再向下移動時,就會固定在該處,然后一個新的隨機的方塊會出現在區域上方開始落下,當區域中某一橫行(同時消除的行數越多,得分指數級上升,當固定的方塊堆到區域最頂端而無法消除層數時,游戲就會結束,
操作設計
起初我想的是在螢屏底部做按鍵操作的,可后來一想,這也太扯淡了

現在是觸屏手機,做按鈕的話不好按,容易誤觸,所定以下的手勢:
- 向上滑動:順時針旋轉正在下落的方塊90°
- 向左滑動:正在下落的方塊向左移動一個方格單位
- 向右滑動,正在下落的方塊向右移動一個方格單位
- 向左持續滑動:正在下落的方塊向左持續移動
- 向右持續滑動:正在下落的方塊向右持續移動
- 向下滑動:方塊直落底部
說到扯淡但,來個更扯淡的操作吧,你可以設定成
向下滑動和向上滑動操作相反
向左滑動和向右滑動操作相反
向左持續滑動和向右持續滑動操作相反
(反正這個操作沒多大作業量,寫個if調換以下方法位置就行…)
演算法規則
當行滿足無空格時,消除當前行,有幾行會消除幾行,一次消除的越多,得分也就越多,咱就不定1分2分的了,這么玩著沒啥意思

干脆消除1行100分
一次性消除2行是200分
一次性消除3行是400分
一次性消除4行是800分
方塊下落速度會越來越快,剛開始下落速度為750ms/格,逐漸累加,最快是150ms/格
按方塊下落的個數計算,每5個方塊為1個遞增,遞增20ms/格(這邊是我自己想的)
整體演算法概述
UI使用自定義View來實作,命名為GameView,背后的運行邏輯通過二維陣列來控制,10X20布局;
當方塊每下落一格和消除時,重繪整個GameView;
0表示可操作空間占位;
1表示已固定方塊型別一方塊占位,11表示正在下落的型別一方塊
2表示已固定方塊型別二方塊占位,12表示正在下落的型別二方塊
3表示已固定方塊型別三方塊占位,13表示正在下落的型別三方塊
以此類推共7種
代碼實操
“只說不練的把式,光練不說傻把式”,這是程式員的最大忌諱,昨天媳婦給力(做的飯好吃),今天快快地擼代碼!
操作設計
創建二維陣列,并允許可以靜態呼叫,其默認值為0,封裝多個公共呼叫方法!詳細看以下注釋
package com.blog.tetris.operation
import java.lang.Thread
import kotlin.math.pow
/**
* 操作類,包含地圖、游戲的主運行邏輯、操作入口
*/
object Operation {
//地圖
//0表示可操作空間占位(默認值)
//1表示已固定方塊占位
//2表示正在下落方塊占位
val gameMap = Array(20) { Array(10) { 0 } }
//方塊下落速度
//初始默認值750毫秒(0.75s)/格
var speed = 750L
var isInGame = false
//當前旋轉度,默認是1
private var rotate = 1
//游戲分數
var score = 0L
//要生成的方塊
var forecastType = 0
//已經生成的方塊個數,根據此變數提升方塊下落速度
var boxNum = 0
//游戲執行緒
private lateinit var thread: Thread
/**
* 開始游戲
*/
fun startGame() {
if (isInGame) return
isInGame = true
thread = Thread {
while (isInGame) {
Thread.sleep(speed)
val downBox = downBox()
//是否無法繼續下移
if (!downBox) {
//固定模塊
fixedMoveBox()
//消除方塊
eliminate()
//生成一個新的模塊
createTopBox()
}
changeListener?.onChange()
}
}
thread.start()
}
//消除行數量
var fullLine = 0
/**
* 消除方塊
* 從下到上遞回掃描方塊,一行一行消除
*/
private fun eliminate(): Int {
for (i in gameMap.size - 1 downTo 0) {
//當前行是否滿足
var isFull = true
for (j in gameMap[i].indices) if (gameMap[i][j] == 0) isFull = false
if (isFull) {
fullLine++
for (j in gameMap[i].indices) {
gameMap[i][j] = 0
}
for (k in i downTo 1) for (n in gameMap[k].indices)
gameMap[k][n] = gameMap[k - 1][n]
eliminate()
}
}
if (fullLine > 0) {
//根據消除行算出分數
score += (2.toDouble().pow((fullLine - 1).toDouble())).toLong() * 100
changeListener?.onChange()
Thread.sleep(200)
fullLine = 0
}
return fullLine
}
/**
* 變形-正在下落的模塊順時針旋轉90度
*/
fun deformation() {
//掃描正在下落的方塊
val boxArr = mutableListOf<Coordinate>()
/**
* 重置置為空
*/
fun reset() {
for (coordinate in boxArr) gameMap[coordinate.x][coordinate.y] = 0
}
//當前下落模塊左上角坐標
var boxMinX = 20
var boxMinY = 10
for (i in gameMap.indices) for (j in gameMap[i].indices) if (gameMap[i][j] in 12..17) {
boxArr.add(Coordinate(i, j, gameMap[i][j]))
if (i < boxMinX) boxMinX = i
if (j < boxMinY) boxMinY = j
}
if (0 == boxArr.size) return
val boxType = boxArr[0].value
if (0 != boxArr.size) {
//判斷是否符合旋轉標準
//有足夠的空間變形(不觸碰左、右、下邊界,不觸碰其他固定方塊)
//取出模塊型別
when (boxType) {
12 -> {
when (rotate) {
1 -> {
//判斷是否符合旋轉條件
if (boxMinX + 2 < 20
&& boxMinY + 1 < 10
&& gameMap[boxMinX][boxMinY] !in 1..7
&& gameMap[boxMinX][boxMinY + 1] !in 1..7
&& gameMap[boxMinX + 1][boxMinY + 1] !in 1..7
&& gameMap[boxMinX + 2][boxMinY + 1] !in 1..7
) {
reset()
gameMap[boxMinX][boxMinY] = 12
gameMap[boxMinX][boxMinY + 1] = 12
gameMap[boxMinX + 1][boxMinY + 1] = 12
gameMap[boxMinX + 2][boxMinY + 1] = 12
rotate = 2
}
}
2 -> {
if (boxMinX + 1 < 20
&& boxMinY + 2 < 10
&& gameMap[boxMinX][boxMinY] !in 1..7
&& gameMap[boxMinX][boxMinY + 1] !in 1..7
&& gameMap[boxMinX][boxMinY + 2] !in 1..7
&& gameMap[boxMinX + 1][boxMinY] !in 1..7
) {
reset()
gameMap[boxMinX][boxMinY] = 12
gameMap[boxMinX][boxMinY + 1] = 12
gameMap[boxMinX][boxMinY + 2] = 12
gameMap[boxMinX + 1][boxMinY] = 12
rotate = 3
}
}
3 -> {
if (boxMinX + 2 < 20
&& boxMinY + 1 < 10
&& gameMap[boxMinX][boxMinY] !in 1..7
&& gameMap[boxMinX + 1][boxMinY] !in 1..7
&& gameMap[boxMinX + 2][boxMinY] !in 1..7
&& gameMap[boxMinX + 2][boxMinY + 1] !in 1..7
) {
reset()
gameMap[boxMinX][boxMinY] = 12
gameMap[boxMinX + 1][boxMinY] = 12
gameMap[boxMinX + 2][boxMinY] = 12
gameMap[boxMinX + 2][boxMinY + 1] = 12
rotate = 4
}
}
4 -> {
if (boxMinX + 1 < 20
&& boxMinY + 2 < 10
&& gameMap[boxMinX][boxMinY + 2] !in 1..7
&& gameMap[boxMinX + 1][boxMinY] !in 1..7
&& gameMap[boxMinX + 1][boxMinY + 1] !in 1..7
&& gameMap[boxMinX + 1][boxMinY + 2] !in 1..7
) {
reset()
gameMap[boxMinX][boxMinY + 2] = 12
gameMap[boxMinX + 1][boxMinY] = 12
gameMap[boxMinX + 1][boxMinY + 1] = 12
gameMap[boxMinX + 1][boxMinY + 2] = 12
rotate = 1
}
}
}
}
13 -> {
when (rotate) {
1 -> {
if (boxMinX + 2 < 20
&& boxMinY + 1 < 10
&& gameMap[boxMinX][boxMinY + 1] !in 1..7
&& gameMap[boxMinX + 1][boxMinY] !in 1..7
&& gameMap[boxMinX + 1][boxMinY + 1] !in 1..7
&& gameMap[boxMinX + 2][boxMinY] !in 1..7
) {
reset()
gameMap[boxMinX][boxMinY + 1] = 13
gameMap[boxMinX + 1][boxMinY] = 13
gameMap[boxMinX + 1][boxMinY + 1] = 13
gameMap[boxMinX + 2][boxMinY] = 13
rotate = 2
}
}
2 -> {
if (
boxMinX + 1 < 20
&& boxMinY + 2 < 10
&& gameMap[boxMinX][boxMinY] !in 1..7
&& gameMap[boxMinX][boxMinY + 1] !in 1..7
&& gameMap[boxMinX + 1][boxMinY + 1] !in 1..7
&& gameMap[boxMinX + 1][boxMinY + 2] !in 1..7
) {
reset()
gameMap[boxMinX][boxMinY] = 13
gameMap[boxMinX][boxMinY + 1] = 13
gameMap[boxMinX + 1][boxMinY + 1] = 13
gameMap[boxMinX + 1][boxMinY + 2] = 13
rotate = 1
}
}
}
}
14 -> {
when (rotate) {
1 -> {
if (boxMinX + 2 < 20
&& boxMinY + 1 < 10
&& gameMap[boxMinX][boxMinY] !in 1..7
&& gameMap[boxMinX + 1][boxMinY] !in 1..7
&& gameMap[boxMinX + 1][boxMinY + 1] !in 1..7
&& gameMap[boxMinX + 2][boxMinY + 1] !in 1..7
) {
reset()
gameMap[boxMinX][boxMinY] = 14
gameMap[boxMinX + 1][boxMinY] = 14
gameMap[boxMinX + 1][boxMinY + 1] = 14
gameMap[boxMinX + 2][boxMinY + 1] = 14
rotate = 2
}
}
2 -> {
if (boxMinX + 2 < 20
&& boxMinY + 1 < 10
&& gameMap[boxMinX][boxMinY + 1] !in 1..7
&& gameMap[boxMinX][boxMinY + 2] !in 1..7
&& gameMap[boxMinX + 1][boxMinY] !in 1..7
&& gameMap[boxMinX + 1][boxMinY + 1] !in 1..7
) {
reset()
gameMap[boxMinX][boxMinY + 1] = 14
gameMap[boxMinX][boxMinY + 2] = 14
gameMap[boxMinX + 1][boxMinY] = 14
gameMap[boxMinX + 1][boxMinY + 1] = 14
rotate = 1
}
}
}
}
15 -> {
when (rotate) {
1 -> {
if (boxMinX + 2 < 20
&& boxMinY + 1 < 10
&& gameMap[boxMinX][boxMinY + 1] !in 1..7
&& gameMap[boxMinX + 1][boxMinY + 1] !in 1..7
&& gameMap[boxMinX + 2][boxMinY] !in 1..7
&& gameMap[boxMinX + 2][boxMinY + 1] !in 1..7
) {
reset()
gameMap[boxMinX][boxMinY + 1] = 15
gameMap[boxMinX + 1][boxMinY + 1] = 15
gameMap[boxMinX + 2][boxMinY] = 15
gameMap[boxMinX + 2][boxMinY + 1] = 15
rotate = 2
}
}
2 -> {
if (boxMinX + 1 < 20
&& boxMinY + 2 < 10
&& gameMap[boxMinX][boxMinY] !in 1..7
&& gameMap[boxMinX][boxMinY + 1] !in 1..7
&& gameMap[boxMinX][boxMinY + 2] !in 1..7
&& gameMap[boxMinX + 1][boxMinY + 2] !in 1..7
) {
reset()
gameMap[boxMinX][boxMinY] = 15
gameMap[boxMinX][boxMinY + 1] = 15
gameMap[boxMinX][boxMinY + 2] = 15
gameMap[boxMinX + 1][boxMinY + 2] = 15
rotate = 3
}
}
3 -> {
if (boxMinX + 1 < 20
&& boxMinY + 2 < 10
&& gameMap[boxMinX][boxMinY] !in 1..7
&& gameMap[boxMinX][boxMinY + 1] !in 1..7
&& gameMap[boxMinX + 1][boxMinY] !in 1..7
&& gameMap[boxMinX + 2][boxMinY] !in 1..7
) {
reset()
gameMap[boxMinX][boxMinY] = 15
gameMap[boxMinX][boxMinY + 1] = 15
gameMap[boxMinX + 1][boxMinY] = 15
gameMap[boxMinX + 2][boxMinY] = 15
rotate = 4
}
}
4 -> {
if (boxMinX + 1 < 20
&& boxMinY + 2 < 10
&& gameMap[boxMinX][boxMinY] !in 1..7
&& gameMap[boxMinX + 1][boxMinY] !in 1..7
&& gameMap[boxMinX + 1][boxMinY + 1] !in 1..7
&& gameMap[boxMinX + 1][boxMinY + 2] !in 1..7
) {
reset()
gameMap[boxMinX][boxMinY] = 15
gameMap[boxMinX + 1][boxMinY] = 15
gameMap[boxMinX + 1][boxMinY + 1] = 15
gameMap[boxMinX + 1][boxMinY + 2] = 15
rotate = 1
}
}
}
}
16 -> {
when (rotate) {
1 -> {
val midpoint = boxArr[2]
if (midpoint.x - 1 >= 0
&& midpoint.x + 1 < 20
&& midpoint.y - 1 >= 0
) {
reset()
gameMap[midpoint.x - 1][midpoint.y] = 16
gameMap[midpoint.x][midpoint.y] = 16
gameMap[midpoint.x][midpoint.y - 1] = 16
gameMap[midpoint.x + 1][midpoint.y] = 16
rotate = 2
}
}
2 -> {
val midpoint = boxArr[2]
if (midpoint.x - 1 >= 0
&& midpoint.x + 1 < 20
&& midpoint.y - 1 >= 0
&& midpoint.y + 1 < 10
) {
reset()
gameMap[midpoint.x][midpoint.y - 1] = 16
gameMap[midpoint.x][midpoint.y] = 16
gameMap[midpoint.x][midpoint.y + 1] = 16
gameMap[midpoint.x + 1][midpoint.y] = 16
rotate = 3
}
}
3 -> {
val midpoint = boxArr[1]
reset()
gameMap[midpoint.x - 1][midpoint.y] = 16
gameMap[midpoint.x][midpoint.y] = 16
gameMap[midpoint.x][midpoint.y + 1] = 16
gameMap[midpoint.x + 1][midpoint.y] = 16
rotate = 4
}
4 -> {
val midpoint = boxArr[1]
if (midpoint.y - 1 >= 0) {
reset()
gameMap[midpoint.x - 1][midpoint.y] = 16
gameMap[midpoint.x][midpoint.y - 1] = 16
gameMap[midpoint.x][midpoint.y] = 16
gameMap[midpoint.x][midpoint.y + 1] = 16
rotate = 1
}
}
}
}
17 -> {
val midpoint = boxArr[1]
when (rotate) {
1 -> {
if (midpoint.x - 1 >= 0 && midpoint.x + 2 < 20) {
reset()
gameMap[midpoint.x - 1][midpoint.y] = 17
gameMap[midpoint.x][midpoint.y] = 17
gameMap[midpoint.x + 1][midpoint.y] = 17
gameMap[midpoint.x + 2][midpoint.y] = 17
rotate = 2
}
}
2 -> {
if (midpoint.y - 1 >= 0 && midpoint.y + 2 < 10) {
reset()
gameMap[midpoint.x][midpoint.y - 1] = 17
gameMap[midpoint.x][midpoint.y] = 17
gameMap[midpoint.x][midpoint.y + 1] = 17
gameMap[midpoint.x][midpoint.y + 2] = 17
rotate = 1
}
}
}
}
}
changeListener?.onChange()
}
}
/**
* 向左移動正在下落的方塊
*/
fun toLeft() {
val leftArr = mutableListOf<Coordinate>()
for (i in gameMap.indices) for (j in gameMap[i].indices) if (gameMap[i][j] in 11..17)
leftArr.add(Coordinate(i, j, gameMap[i][j]))
if (0 == leftArr.size) return
//是否允許向左移動
var toLeftStatus = true
//向左移動條件:符合移動方塊并且任何左側為空方塊
for (coordinate in leftArr) {
if (coordinate.y == 0 || (coordinate.y > 0 && gameMap[coordinate.x][coordinate.y - 1] in 1..7)) {
toLeftStatus = false
break
}
}
//向左移動
if (toLeftStatus) {
for (i in 0 until leftArr.size) {
gameMap[leftArr[i].x][leftArr[i].y - 1] = leftArr[0].value
gameMap[leftArr[i].x][leftArr[i].y] = 0
}
changeListener?.onChange()
}
}
/**
* 向右移動正在下落的方塊
*/
fun toRight() {
val rightArr = mutableListOf<Coordinate>()
for (i in gameMap.indices) for (j in gameMap[i].indices) if (gameMap[i][j] in 11..17)
rightArr.add(Coordinate(i, j, gameMap[i][j]))
if (0 == rightArr.size) return
//是否允許向右移動
var toRightStatus = true
//向右移動條件:符合移動方塊并且任何右側為空方塊
for (coordinate in rightArr) if (coordinate.y == 9 || (coordinate.y < 10 && gameMap[coordinate.x][coordinate.y + 1] in 1..7)) {
toRightStatus = false
break
}
//向右移動
if (toRightStatus) {
for (i in rightArr.size - 1 downTo 0) {
gameMap[rightArr[i].x][rightArr[i].y + 1] = rightArr[0].value
gameMap[rightArr[i].x][rightArr[i].y] = 0
}
changeListener?.onChange()
}
}
/**
* 直接下落到底部
*/
fun downBottom() {
val downArr = mutableListOf<Coordinate>()
for (i in gameMap.size - 1 downTo 0) for (j in gameMap[i].size - 1 downTo 0) {
val coordinate = gameMap[i][j]
if (coordinate in 11..17) downArr.add(Coordinate(i, j, coordinate))
}
if (0 != downArr.size) {
var isFixed = false
val valueTemp = downArr[0].value
for (coordinate in downArr) if (coordinate.x + 1 >= 20 || gameMap[coordinate.x + 1][coordinate.y] in 1..7) return
for (coordinate in downArr) {
gameMap[coordinate.x][coordinate.y] = 0
gameMap[coordinate.x + 1][coordinate.y] = valueTemp
//判斷是否落到了最后
if (coordinate.x + 1 >= 19 || gameMap[coordinate.x + 2][coordinate.y] in 1..7)
isFixed = true
}
if (!isFixed) downBottom() else changeListener?.onChange()
} else changeListener?.onChange()
}
/**
* 在頂部創建一個新的模塊
*/
private fun createTopBox() {
//判斷是否有預測,沒有預測則生成方塊
if (0 == forecastType) forecastType = (1..7).random()
//重置旋轉度數
rotate = 1
//每生成5個方塊增加一次方塊下落速度
if (++boxNum % 5 == 0 && speed > 150) speed -= 20
//游戲是否結束
var gameOverStatus = false
//生成下落方塊
when (forecastType) {
1 -> if (gameMap[0][4] !in 1..7
&& gameMap[0][5] !in 1..7
&& gameMap[1][4] !in 1..7
&& gameMap[1][5] !in 1..7
) {
gameMap[0][4] = 11
gameMap[0][5] = 11
gameMap[1][4] = 11
gameMap[1][5] = 11
} else gameOverStatus = true
2 -> if (gameMap[0][5] !in 1..7
&& gameMap[1][3] !in 1..7
&& gameMap[1][4] !in 1..7
&& gameMap[1][5] !in 1..7
) {
gameMap[0][5] = 12
gameMap[1][3] = 12
gameMap[1][4] = 12
gameMap[1][5] = 12
} else gameOverStatus = true
3 -> if (gameMap[0][3] !in 1..7
&& gameMap[0][4] !in 1..7
&& gameMap[1][4] !in 1..7
&& gameMap[1][5] !in 1..7
) {
gameMap[0][3] = 13
gameMap[0][4] = 13
gameMap[1][4] = 13
gameMap[1][5] = 13
} else gameOverStatus = true
4 -> if (gameMap[0][4] !in 1..7
&& gameMap[0][5] !in 1..7
&& gameMap[1][3] !in 1..7
&& gameMap[1][4] !in 1..7
) {
gameMap[0][4] = 14
gameMap[0][5] = 14
gameMap[1][3] = 14
gameMap[1][4] = 14
} else gameOverStatus = true
5 -> if (gameMap[0][3] !in 1..7
&& gameMap[1][3] !in 1..7
&& gameMap[1][4] !in 1..7
&& gameMap[1][5] !in 1..7
) {
gameMap[0][3] = 15
gameMap[1][3] = 15
gameMap[1][4] = 15
gameMap[1][5] = 15
} else gameOverStatus = true
6 -> if (gameMap[0][4] !in 1..7
&& gameMap[1][3] !in 1..7
&& gameMap[1][4] !in 1..7
&& gameMap[1][5] !in 1..7
) {
gameMap[0][4] = 16
gameMap[1][3] = 16
gameMap[1][4] = 16
gameMap[1][5] = 16
} else gameOverStatus = true
7 -> if (gameMap[0][3] !in 1..7
&& gameMap[0][4] !in 1..7
&& gameMap[0][5] !in 1..7
&& gameMap[0][6] !in 1..7
) {
gameMap[0][3] = 17
gameMap[0][4] = 17
gameMap[0][5] = 17
gameMap[0][6] = 17
} else gameOverStatus = true
}
//生成預測方塊
forecastType = (1..7).random()
if (gameOverStatus) {
//回呼游戲結束
changeListener?.gameOver(score)
//重置游戲資料
speed = 750L
isInGame = false
rotate = 1
score = 0L
forecastType = 0
boxNum = 0
for (i in gameMap.indices) for (j in gameMap[i].indices) gameMap[i][j] = 0
}
}
/**
* 固定移動中的方塊方塊
*/
private fun fixedMoveBox() {
for (i in gameMap.indices) for (j in gameMap[i].indices) if (gameMap[i][j] in 11..17)
gameMap[i][j] = gameMap[i][j] - 10
}
/**
* 方塊下落
*/
private fun downBox(): Boolean {
//掃描需要下落的方塊
val downArr = mutableListOf<Coordinate>()
for (i in gameMap.size - 1 downTo 0) for (j in gameMap[i].size - 1 downTo 0) if (gameMap[i][j] in 11..17)
downArr.add(Coordinate(i, j, gameMap[i][j]))
//判斷是否無法繼續下移
if (downArr.size == 0) return false
//判斷是否還能繼續下移
//判斷條件:任何一個正在移動的方塊是否達到了第20行或者觸碰到了固定方塊
for (i in 0 until downArr.size) if (downArr[i].x + 1 > 19 || gameMap[downArr[i].x + 1][downArr[i].y] in 1..7) return false
val coordinateTempValue = downArr[0]
for (j in 0 until downArr.size) {
val coordinateTemp = downArr[j]
gameMap[coordinateTemp.x][coordinateTemp.y] = 0
gameMap[coordinateTemp.x + 1][coordinateTemp.y] = coordinateTempValue.value
}
return true
}
//游戲實時變化回呼
var changeListener: ChangeListener? = null
/**
* 游戲實時變化回呼
*/
interface ChangeListener {
fun onChange()
fun gameOver(score: Long)
}
/**
* 坐標物體類
*/
data class Coordinate(
var x: Int,
var y: Int,
var value: Int
)
}
可視化View
資料邏輯寫完了,要讓用戶看的見呀,通過自定義View來實作,詳細看以下注釋
package com.blog.tetris.view
import android.content.Context
import android.graphics.*
import android.util.AttributeSet
import android.util.Log
import android.view.View
import com.blog.tetris.operation.Operation
class GameView(context: Context?, attrs: AttributeSet?) : View(context, attrs) {
//方塊大小
val boxSize = 70F
//方塊邊框大小
private val edge = boxSize / 4F
//地圖繪制
private var mapPaint: Paint = Paint()
//方塊繪制
private var boxPaint: Paint = Paint()
//預測方塊繪制
private var forecastPaint: Paint = Paint()
//分數繪制
private var scorePaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)
private var leftMargin = 0
private var topMargin = 5 * boxSize
//方塊顏色
private var color10 = Color.parseColor("#0302FF")
private var color11 = Color.parseColor("#3437F9")
private var color12 = Color.parseColor("#6568FC")
private var color13 = Color.parseColor("#00089C")
private var color14 = Color.parseColor("#00027B")
private var color20 = Color.parseColor("#FEFF04")
private var color21 = Color.parseColor("#FDFE3E")
private var color22 = Color.parseColor("#FCFD65")
private var color23 = Color.parseColor("#ACA706")
private var color24 = Color.parseColor("#717804")
private var color30 = Color.parseColor("#FB0101")
private var color31 = Color.parseColor("#F33A36")
private var color32 = Color.parseColor("#F66A64")
private var color33 = Color.parseColor("#A80400")
private var color34 = Color.parseColor("#7E0003")
private var color40 = Color.parseColor("#06FD06")
private var color41 = Color.parseColor("#37F936")
private var color42 = Color.parseColor("#68FD67")
private var color43 = Color.parseColor("#0AA109")
private var color44 = Color.parseColor("#037407")
private var color50 = Color.parseColor("#FF7F04")
private var color51 = Color.parseColor("#F89A38")
private var color52 = Color.parseColor("#F8B56A")
private var color53 = Color.parseColor("#A75304")
private var color54 = Color.parseColor("#723709")
private var color60 = Color.parseColor("#F505F8")
private var color61 = Color.parseColor("#FB36FB")
private var color62 = Color.parseColor("#FF66F9")
private var color63 = Color.parseColor("#A801A7")
private var color64 = Color.parseColor("#780378")
private var color70 = Color.parseColor("#01FFFD")
private var color71 = Color.parseColor("#37FDFE")
private var color72 = Color.parseColor("#69FAF9")
private var color73 = Color.parseColor("#08A5AA")
private var color74 = Color.parseColor("#01746F")
init {
scorePaint.color = Color.parseColor("#FFFFFF")
mapPaint.color = Color.parseColor("#000000")
scorePaint.textSize = boxSize / 1.8F
boxPaint.isAntiAlias = true
forecastPaint.isAntiAlias = true
}
override fun onDraw(canvas: Canvas?) {
canvas?.let {
//算出地圖邊界距離螢屏左側距離(游戲地圖放置在View中間)
leftMargin = width / 2 - boxSize.toInt() * 5
//繪制地圖
drawMap(it)
//繪制方塊
drawBoxByArr(it)
//繪制分數
drawScore(it)
//繪制預測
drawForecast(it)
}
}
/**
* 繪制分數
*/
private fun drawScore(canvas: Canvas) {
//繪制標題
canvas.drawText(
"得分",
leftMargin.toFloat() + boxSize / 2,
scorePaint.textSize + boxSize / 2,
scorePaint
)
//繪制得分
canvas.drawText(
Operation.score.toString(),
leftMargin.toFloat() + boxSize / 2,
scorePaint.textSize + boxSize / 2 + boxSize,
scorePaint
)
}
/**
* 繪制預測
*/
private fun drawForecast(canvas: Canvas) {
//繪制標題
canvas.drawText(
"下一個",
width / 2F,
scorePaint.textSize + boxSize / 2,
scorePaint
)
//繪制方塊
val forecastMap = Array(2) { Array(4) { 0 } }
when (Operation.forecastType) {
1 -> {
forecastMap[0][0] = 1
forecastMap[0][1] = 1
forecastMap[1][0] = 1
forecastMap[1][1] = 1
}
2 -> {
forecastMap[0][2] = 2
forecastMap[1][0] = 2
forecastMap[1][1] = 2
forecastMap[1][2] = 2
}
3 -> {
forecastMap[0][0] = 3
forecastMap[0][1] = 3
forecastMap[1][1] = 3
forecastMap[1][2] = 3
}
4 -> {
forecastMap[0][1] = 4
forecastMap[0][2] = 4
forecastMap[1][0] = 4
forecastMap[1][1] = 4
}
5 -> {
forecastMap[0][0] = 5
forecastMap[1][0] = 5
forecastMap[1][1] = 5
forecastMap[1][2] = 5
}
6 -> {
forecastMap[0][1] = 6
forecastMap[1][0] = 6
forecastMap[1][1] = 6
forecastMap[1][2] = 6
}
7 -> {
forecastMap[0][0] = 7
forecastMap[0][1] = 7
forecastMap[0][2] = 7
forecastMap[0][3] = 7
}
}
//繪制預測方塊
val boxColor = getBoxColor(Operation.forecastType)
for (i in forecastMap.indices) {
for (j in forecastMap[i].indices) {
if (forecastMap[i][j] > 0) {
val left = width / 2F + boxSize * j
val top = i * boxSize + boxSize * 1.5F
val right = width / 2F + boxSize * j + boxSize
val bottom = (i + 1) * boxSize + boxSize * 1.5F
//繪制方塊
forecastPaint.color = boxColor[0]
canvas.drawRect(left, top, right, bottom, forecastPaint)
//繪制單個模塊四周梯形
val path1 = Path()
path1.moveTo(left, bottom)
path1.lineTo(left, bottom - boxSize)
path1.lineTo(left + edge, bottom - boxSize + edge)
path1.lineTo(left + edge, bottom - edge)
path1.close()
val path2 = Path()
path2.moveTo(left + edge, top + edge)
path2.lineTo(left, top)
path2.lineTo(right, top)
path2.lineTo(right - edge, top + edge)
path2.close()
val path3 = Path()
path3.moveTo(right - edge, bottom - edge)
path3.lineTo(right - edge, top + edge)
path3.lineTo(right, top)
path3.lineTo(right, bottom)
path3.close()
val path4 = Path()
path4.moveTo(left, bottom)
path4.lineTo(left + edge, bottom - edge)
path4.lineTo(right - edge, bottom - edge)
path4.lineTo(right, bottom)
path4.close()
boxPaint.color = boxColor[1]
canvas.drawPath(path1, boxPaint)
boxPaint.color = boxColor[2]
canvas.drawPath(path2, boxPaint)
boxPaint.color = boxColor[3]
canvas.drawPath(path3, boxPaint)
boxPaint.color = boxColor[4]
canvas.drawPath(path4, boxPaint)
}
}
}
}
/**
* 繪制地圖
*/
private fun drawMap(canvas: Canvas) {
//繪制地圖
for (i in 0 until 20) for (j in 0 until 10) canvas.drawRect(
j * boxSize + leftMargin,
i * boxSize + topMargin,
(j + 1) * boxSize + leftMargin,
(i + 1) * boxSize + topMargin,
mapPaint
)
//繪制頂部狀態圖
for (i in 0 until 4) for (j in 0 until 10) canvas.drawRect(
j * boxSize + leftMargin,
i * boxSize,
(j + 1) * boxSize + leftMargin,
(i + 1) * boxSize,
mapPaint
)
}
/**
* 根據陣列繪制圖形
*/
private fun drawBoxByArr(canvas: Canvas) {
val mapArr = Operation.gameMap
for (i in mapArr.indices) {
for (j in mapArr[i].indices) {
if (mapArr[i][j] > 0) {
boxPaint.color = getBoxColor(mapArr[i][j])[0]
val left = j * boxSize + leftMargin
val top = i * boxSize + topMargin
val right = (j + 1) * boxSize + leftMargin
val bottom = (i + 1) * boxSize + topMargin
canvas.drawRect(left, top, right, bottom, boxPaint)
//繪制單個模塊四周梯形
val path1 = Path()
path1.moveTo(left, top)
path1.lineTo(left + edge, i * boxSize + edge + topMargin)
path1.lineTo(left + edge, bottom - edge)
path1.lineTo(left, bottom)
path1.close()
val path2 = Path()
path2.moveTo(left, top)
path2.lineTo(left + boxSize, top)
path2.lineTo(left + boxSize - edge, top + edge)
path2.lineTo(left + edge, top + edge)
path2.close()
val path3 = Path()
path3.moveTo(left + boxSize - edge, top + edge)
path3.lineTo(left + boxSize, top)
path3.lineTo(left + boxSize, bottom)
path3.lineTo(left + boxSize - edge, bottom - edge)
path3.close()
val path4 = Path()
path4.moveTo(left, bottom)
path4.lineTo(left + edge, bottom - edge)
path4.lineTo(left + boxSize - edge, bottom - edge)
path4.lineTo(left + boxSize, bottom)
path4.close()
boxPaint.color = getBoxColor(mapArr[i][j])[1]
canvas.drawPath(path1, boxPaint)
boxPaint.color = getBoxColor(mapArr[i][j])[2]
canvas.drawPath(path2, boxPaint)
boxPaint.color = getBoxColor(mapArr[i][j])[3]
canvas.drawPath(path3, boxPaint)
boxPaint.color = getBoxColor(mapArr[i][j])[4]
canvas.drawPath(path4, boxPaint)
}
}
}
}
private fun getBoxColor(type: Int): Array<Int> {
return when (type) {
1, 11 -> arrayOf(color10, color11, color12, color13, color14)
2, 12 -> arrayOf(color20, color21, color22, color23, color24)
3, 13 -> arrayOf(color30, color31, color32, color33, color34)
4, 14 -> arrayOf(color40, color41, color42, color43, color44)
5, 15 -> arrayOf(color50, color51, color52, color53, color54)
6, 16 -> arrayOf(color60, color61, color62, color63, color64)
7, 17 -> arrayOf(color70, color71, color72, color73, color74)
else -> arrayOf(color10, color11, color12, color13, color14)
}
}
/**
* 重繪
*/
fun refresh() {
postInvalidate()
}
}
MainActivity
都到這里了,代碼也就貼全了
package com.blog.tetris
import android.annotation.SuppressLint
import android.content.DialogInterface
import android.os.Bundle
import android.util.Log
import android.view.GestureDetector
import android.view.MotionEvent
import android.view.View
import android.widget.TextView
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.app.AppCompatActivity
import com.blog.tetris.operation.Operation
import com.blog.tetris.view.GameView
import com.blog.tetris.view.StatusBarUtils
class MainActivity : AppCompatActivity() {
private lateinit var gameView: GameView
private lateinit var playGame: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
initView()
initData()
initListener()
}
private fun initView() {
//設定狀態欄字體為黑色
StatusBarUtils.darkMode(this)
gameView = findViewById(R.id.gameView)
playGame = findViewById(R.id.playGame)
}
private fun initData() {
}
@SuppressLint("ClickableViewAccessibility")
private fun initListener() {
var downX = 0F
var downY = 0F
var direction = 0
gameView.setOnTouchListener { _, p1 ->
when (p1?.action) {
MotionEvent.ACTION_DOWN -> {
downX = p1.x
downY = p1.y
}
MotionEvent.ACTION_MOVE -> {
//鎖定方向
if (0 == direction) {
if (p1.y - downY > 200) direction = 4
if ((p1.x - downX < -80 || p1.x - downX > 80)) direction = 1
if (p1.y - downY < -200) direction = 2
} else {
when (direction) {
//橫向滑動
1 -> {
if (p1.x - downX > gameView.boxSize) {
downX = p1.x
Operation.toRight()
} else if (p1.x - downX < -gameView.boxSize) {
downX = p1.x
Operation.toLeft()
}
}
//向上滑動
2 -> {
direction = -1
Operation.deformation()
}
//向下滑動
4 -> {
direction = -1
Operation.downBottom()
}
}
}
}
MotionEvent.ACTION_UP -> {
downX = 0F
downY = 0F
direction = 0
}
}
true
}
//游戲變化回呼
Operation.changeListener = object : Operation.ChangeListener {
override fun onChange() {
gameView.refresh()
}
override fun gameOver(score: Long) {
runOnUiThread {
val dialog = AlertDialog.Builder(this@MainActivity)
dialog.setTitle("提示")
dialog.setMessage("游戲結束! 得分:$score")
dialog.setPositiveButton(
"重新開始"
) { _, _ -> Operation.startGame() }
dialog.setNegativeButton(
"取消"
) { _, _ -> }
dialog.setCancelable(false)
dialog.show()
}
}
}
playGame.setOnClickListener {
Operation.startGame()
}
}
}
原始碼下載
Github:https://github.com/ThirdGoddess/Tetris
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/349671.html
標籤:其他
下一篇:While回圈功能
