Android原生控件大顯身手:打造方塊消除小游戲
方塊消除游戲是一種簡單有趣、老少皆宜的休閑益智類游戲。玩家通過將相同顏色的方塊進行消除,從而獲得分數(shù)。
????下面我們使用Android原生控件來實現(xiàn)這個小游戲(PS:不包含自定義View的方式)。
實現(xiàn)思路
游戲場景
使用不同顏色的方塊繪制一個8x8的游戲板,可以用GridLayout來進行繪制,同時包含游戲分數(shù)和重新開始按鈕。
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:id="@+id/scoreTextView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:text="分數(shù): 0"
android:textSize="18sp" />
<GridLayout
android:id="@+id/gameBoard"
android:layout_width="match_parent"
android:layout_height="400dp"
android:columnCount="8"
android:padding="1dp"
android:rowCount="8" />
<Button
android:id="@+id/restartButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:layout_marginTop="16dp"
android:text="重新開始" />
</LinearLayout>
定義5種不同的顏色,然后隨機生成不同顏色的方塊,最后添加到GridLayout中。
private val blocks = Array(8) { IntArray(8) }
private val blockButtons = Array(8) { arrayOfNulls<Button>(8) }
private val colors = arrayOf(
R.color.empty,
R.color.red,
R.color.blue,
R.color.green,
R.color.yellow,
R.color.cyan
)
private fun initializeGameBoard() {
for (i in 0 until 8) {
for (j in 0 until 8) {
val button = Button(this).apply {
layoutParams = GridLayout.LayoutParams().apply {
width = 0
height = 0
columnSpec = GridLayout.spec(j, 1f)
rowSpec = GridLayout.spec(i, 1f)
}
setOnClickListener {
onBlockClicked(i, j)
}
background = null
isAllCaps = false
}
blockButtons[i][j] = button
gameBoard.addView(button)
}
}
for (i in 0 until 8) {
for (j in 0 until 8) {
blocks[i][j] = Random.nextInt(1, colors.size)
updateBlockColor(i, j)
}
}
}
private fun updateBlockColor(row: Int, col: Int) {
blockButtons[row][col]?.setBackgroundColor(
ContextCompat.getColor(this, colors[blocks[row][col]])
)
}
效果
此時的方塊看起來是連接在一起的,我們可以添加個背景樣式,然后給每個方塊添加間距實現(xiàn)一個網(wǎng)格效果。
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="#CCCCCC" />
<stroke
android:width="1dp"
android:color="#888888" />
</shape>
給GridLayout設(shè)置背景樣式:
<GridLayout
android:id="@+id/gameBoard"
android:layout_width="match_parent"
android:layout_height="400dp"
android:background="@drawable/grid_background"
android:columnCount="8"
android:padding="1dp"
android:rowCount="8" />
給每個方塊添加間距setMargins(1, 1, 1, 1)。
private fun initializeGameBoard() {
for (i in 0 until 8) {
for (j in 0 until 8) {
val button = Button(this).apply {
layoutParams = GridLayout.LayoutParams().apply {
width = 0
height = 0
columnSpec = GridLayout.spec(j, 1f)
rowSpec = GridLayout.spec(i, 1f)
setMargins(1, 1, 1, 1)
}
setOnClickListener {
onBlockClicked(i, j)
}
background = null
isAllCaps = false
}
blockButtons[i][j] = button
gameBoard.addView(button)
}
}
for (i in 0 until 8) {
for (j in 0 until 8) {
blocks[i][j] = Random.nextInt(1, colors.size)
updateBlockColor(i, j)
}
}
}
此時我們的游戲場景已經(jīng)完成。
消除規(guī)則
點擊相鄰方塊顏色相同的塊進行消除,再方塊點擊時找出相鄰的方塊,如果相鄰方塊顏色相同并且數(shù)量大于2消除方塊:
private fun onBlockClicked(row: Int, col: Int) {
if (isGameOver) return
val color = blocks[row][col]
if (color != 0) {
val connectedBlocks = findConnectedBlocks(row, col, color)
if (connectedBlocks.size >= 2) {
removeBlocks(connectedBlocks)
}
}
}
private fun findConnectedBlocks(row: Int, col: Int, color: Int): List<Pair<Int, Int>> {
val visited = Array(8) { BooleanArray(8) }
val connectedBlocks = mutableListOf<Pair<Int, Int>>()
fun dfs(r: Int, c: Int) {
if (r < 0 || r >= 8 || c < 0 || c >= 8 || visited[r][c] || blocks[r][c] != color) return
visited[r][c] = true
connectedBlocks.add(Pair(r, c))
dfs(r + 1, c)
dfs(r - 1, c)
dfs(r, c + 1)
dfs(r, c - 1)
}
dfs(row, col)
return connectedBlocks
}
private fun removeBlocks(connectedBlocks: List<Pair<Int, Int>>) {
for ((row, col) in connectedBlocks) {
blocks[row][col] = 0
updateBlockColor(row, col)
}
}
當玩家點擊一個方塊時,觸發(fā)onBlockClicked方法。調(diào)用findConnectedBlocks方法使用深度優(yōu)先搜索(DFS)來查找連接的相同顏色方塊,當連接的相同顏色方塊數(shù)量大于等于2時調(diào)用removeBlocks方法進行消除。
查找相鄰方塊實現(xiàn)原理:
- 初始化:
創(chuàng)建一個 8x8 的布爾數(shù)組 visited,用于標記已訪問的方塊。
創(chuàng)建一個可變列表 connectedBlocks,用于存儲找到的相連方塊。
- 深度優(yōu)先搜索(DFS):
定義一個內(nèi)部函數(shù) dfs,使用當前方塊的行和列作為參數(shù)。
DFS 從給定的起始方塊開始,然后遞歸地探索相鄰的方塊。
- 邊界和有效性檢查:
檢查當前位置是否在游戲范圍內(nèi)(r < 0 || r >= 8 || c < 0 || c >= 8)。
檢查當前方塊是否已被訪問(visited[r][c])。
檢查當前方塊的顏色是否與目標顏色相同(blocks[r][c] != color)。
如果以上任一條件不滿足,則返回,不繼續(xù)探索。
- 標記和記錄:
通過了所有檢查,將當前方塊標記為已訪問(visited[r][c] = true)。
將當前方塊添加到相連方塊列表中(connectedBlocks.add(Pair(r, c)))。
- 遞歸探索:
對當前方塊的上、下、左、右四個相鄰方塊遞歸調(diào)用 DFS。
保證所有相連的同色方塊都會被探索到。
- 返回結(jié)果:
搜索完成后,返回找到的所有相連方塊的列表。
填補空缺
消除方塊后,上方的方塊會下落填補空缺
private fun dropBlocks() {
for (col in 0 until 8) {
var emptyRow = 7
for (row in 7 downTo 0) {
if (blocks[row][col] != 0) {
blocks[emptyRow][col] = blocks[row][col]
updateBlockColor(emptyRow, col)
if (emptyRow != row) {
blocks[row][col] = 0
updateBlockColor(row, col)
}
emptyRow--
}
}
}
}
生成新方塊
方塊下落后,生成新的隨機顏色方塊填補空缺
private fun fillBlocks() {
for (col in 0 until 8) {
for (row in 0 until 8) {
if (blocks[row][col] == 0) {
blocks[row][col] = Random.nextInt(1, colors.size)
updateBlockColor(row, col)
}
}
}
}
分數(shù)計算
定義分數(shù)score,每消除一個方塊得一分;
private var score = 0
private fun removeBlocks(connectedBlocks: List<Pair<Int, Int>>) {
for ((row, col) in connectedBlocks) {
blocks[row][col] = 0
updateBlockColor(row, col)
score++
}
}
private fun updateScore() {
scoreTextView.text = "分數(shù): $score"
}
在方塊消除時,分數(shù)進行計算,每消除一個方塊加一分;
游戲結(jié)束
private var isGameOver = false
private fun onBlockClicked(row: Int, col: Int) {
if (isGameOver) return
val color = blocks[row][col]
if (color != 0) {
val connectedBlocks = findConnectedBlocks(row, col, color)
if (connectedBlocks.size >= 2) {
removeBlocks(connectedBlocks)
dropBlocks()
fillBlocks()
updateScore()
if (!hasValidMoves()) {
gameOver()
}
}
}
}
private fun hasValidMoves(): Boolean {
for (row in 0 until 8) {
for (col in 0 until 8) {
val color = blocks[row][col]
if (color != 0) {
if (findConnectedBlocks(row, col, color).size >= 2) {
return true
}
}
}
}
return false
}
private fun gameOver() {
isGameOver = true
scoreTextView.text = "Game Over! 最終得分: $score"
}
在每次移動后檢查有沒有相鄰的同顏色方塊,如果沒有有效的移動,游戲結(jié)束。
拓展游戲
固定的顏色數(shù)量和生成規(guī)則致使游戲結(jié)束很難觸發(fā),為了增加游戲可玩性和難度,我們可以增加更多的顏色,在方塊消除分數(shù)到達一定的數(shù)值后增加隨機出來的顏色數(shù)量
<resources>
<color name="white">#FFFFFF</color>
<color name="red">#FF0000</color>
<color name="blue">#0000FF</color>
<color name="green">#00FF00</color>
<color name="yellow">#FFFF00</color>
<color name="cyan">#00FFFF</color>
<color name="empty">#CCCCCC</color>
<color name="magenta">#FF00FF</color>
<color name="orange">#FFA500</color>
<color name="purple">#800080</color>
<color name="pink">#FFC0CB</color>
<color name="lime">#00FF00</color>
<color name="teal">#008080</color>
<color name="brown">#A52A2A</color>
<color name="navy">#000080</color>
</resources>
private val colors = arrayOf(
R.color.empty,
R.color.red,
R.color.blue,
R.color.green,
R.color.yellow,
R.color.cyan,
R.color.magenta,
R.color.orange,
R.color.purple,
R.color.pink,
R.color.lime,
R.color.teal,
R.color.brown,
R.color.navy
)
//初始時顏色個數(shù)為5個
private var currentMaxColor = 5
private fun restartGame() {
score = 0
isGameOver = false
currentMaxColor = 5
updateScore()
for (i in 0 until 8) {
for (j in 0 until 8) {
blocks[i][j] = Random.nextInt(1, currentMaxColor)
updateBlockColor(i, j)
}
}
if (!hasValidMoves()) {
restartGame()
}
}
private fun fillBlocks() {
for (col in 0 until 8) {
for (row in 0 until 8) {
if (blocks[row][col] == 0) {
blocks[row][col] = Random.nextInt(1, currentMaxColor)
updateBlockColor(row, col)
}
}
}
}
private fun removeBlocks(connectedBlocks: List<Pair<Int, Int>>) {
for ((row, col) in connectedBlocks) {
blocks[row][col] = 0
updateBlockColor(row, col)
score++
}
if (score > 50 && currentMaxColor < 5) currentMaxColor = 5
if (score > 100 && currentMaxColor < 6) currentMaxColor = 6
if (score > 200 && currentMaxColor < 7) currentMaxColor = 7
if (score > 300 && currentMaxColor < 8) currentMaxColor = 8
if (score > 400 && currentMaxColor < 9) currentMaxColor = 9
if (score > 500 && currentMaxColor < 10) currentMaxColor = 10
if (score > 600 && currentMaxColor < 11) currentMaxColor = 11
if (score > 700 && currentMaxColor < 12) currentMaxColor = 12
if (score > 800 && currentMaxColor < 13) currentMaxColor = 13
}
經(jīng)過修改后在游戲開始時只使用較少的顏色,隨著分數(shù)的增加引入更多隨機顏色,從而提升游戲的可玩性和難度。
完整代碼
圖片
游戲效果
Github源碼https://github.com/Reathin/Sample-Android/tree/master/module_block