Android,jetpack,compose,简单模仿水果消消乐
(这只是简单案例模仿 做不到真正游戏那样)
这是MainActivity代码
package com.example.myapplicationFruitimport android.content.Intent
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.*
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.shadow
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.spclass MainActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {StartScreen()}}@Composablefun StartScreen() {Scaffold(containerColor = Color(0xFF1E3C2B)) { padding ->Box(modifier = Modifier.fillMaxSize().padding(padding),contentAlignment = Alignment.Center) {Column(horizontalAlignment = Alignment.CenterHorizontally,verticalArrangement = Arrangement.Center,modifier = Modifier.padding(16.dp)) {// 标题Text(text = "水果消消乐",fontSize = 42.sp,fontWeight = FontWeight.Bold,color = Color(0xFFFFE599),textAlign = TextAlign.Center,modifier = Modifier.padding(bottom = 32.dp))// 水果图标装饰Row {val fruitDrawables = listOf(R.drawable.apple,R.drawable.banana,R.drawable.orange,R.drawable.grape,R.drawable.strawberry,R.drawable.watermelon)for (resId in fruitDrawables) {Image(painter = painterResource(id = resId),contentDescription = null,modifier = Modifier.size(40.dp))}}Spacer(modifier = Modifier.height(48.dp))// 开始按钮Button(onClick = {startActivity(Intent(this@MainActivity, FruitMatchGameActivity::class.java))},shape = RoundedCornerShape(12.dp),colors = ButtonDefaults.buttonColors(containerColor = Color(0xFF4CAF50),contentColor = Color.White),modifier = Modifier.shadow(4.dp).width(200.dp)) {Text(text = "开始游戏", fontSize = 20.sp)}}}}}@Preview(showBackground = true, backgroundColor = 0xFF1E3C2B, showSystemUi = false)@Composablefun PreviewStartScreen() {StartScreenPreviewWrapper()}
}@Composable
private fun StartScreenPreviewWrapper() {MainActivity().StartScreen()
}
这是FruitMatchGameActivitu代码
package com.example.myapplicationFruitimport android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Info
import androidx.compose.material.icons.filled.Refresh
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.launch
import kotlin.math.abs
import kotlin.random.Random
import androidx.compose.material3.ExperimentalMaterial3Apiclass FruitMatchGameActivity : ComponentActivity() {override fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContent {GameScreen(onExit = { finish() } // 传入 finish() 回调)}}
}@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun GameScreen(onExit: () -> Unit // 接收退出回调
) {var score by remember { mutableStateOf(0) }var board by remember { mutableStateOf(emptyList<List<Int>>()) }var isProcessing by remember { mutableStateOf(false) }var selectedCell by remember { mutableStateOf<Pair<Int, Int>?>(null) }var showGameOver by remember { mutableStateOf(false) } // 控制游戏结束弹窗显示var showDeadlockDialog by remember { mutableStateOf(false) } // 控制死局检测弹窗显示var deadlockMessage by remember { mutableStateOf("") } // 存储死局检测消息val coroutineScope = rememberCoroutineScope()LaunchedEffect(Unit) {isProcessing = trueval stableBoard = generateStableBoard()board = stableBoardisProcessing = false}// 检查是否进入死局(无合法移动)LaunchedEffect(board, score) {if (board.isNotEmpty() && !isProcessing && !hasAnyValidMove(board)) {// 在死局时显示游戏结束弹窗showGameOver = true}}MaterialTheme(colorScheme = lightColorScheme(primary = Color(0xFF2D5F3A),onPrimary = Color.White,secondary = Color(0xFF8BC34A),onSecondary = Color.Black,background = Color(0xFF2D5F3A),onBackground = Color.White,surface = Color(0xFF2D5F3A),onSurface = Color.White,error = Color.Red,onError = Color.White,tertiary = Color.Yellow,onTertiary = Color.Black)) {Scaffold(topBar = {TopAppBar(title = {Text(text = "分数: $score",fontSize = 18.sp,fontWeight = FontWeight.Bold,color = MaterialTheme.colorScheme.onPrimary)},actions = {// 检测死局按钮IconButton(onClick = {if (!isProcessing && board.isNotEmpty()) {val isDeadlock = !hasAnyValidMove(board)deadlockMessage = if (isDeadlock) {"当前棋盘已无任何合法移动,进入死局!"} else {"当前棋盘仍存在合法移动,未进入死局。"}showDeadlockDialog = true}}) {Icon(imageVector = androidx.compose.material.icons.Icons.Default.Info,contentDescription = "检查死局",tint = MaterialTheme.colorScheme.onPrimary)}// 重置按钮IconButton(onClick = {if (!isProcessing) {isProcessing = truecoroutineScope.launch {val newBoard = generateStableBoard()board = newBoardscore = 0selectedCell = nullisProcessing = false}}}) {Icon(Icons.Default.Refresh,contentDescription = "重置",tint = MaterialTheme.colorScheme.onPrimary)}},colors = TopAppBarDefaults.topAppBarColors(containerColor = MaterialTheme.colorScheme.primary))},containerColor = MaterialTheme.colorScheme.surface) { padding ->Column(modifier = Modifier.padding(padding).fillMaxSize(),horizontalAlignment = Alignment.CenterHorizontally) {Spacer(modifier = Modifier.height(8.dp))if (board.isNotEmpty()) {GameBoard(board = board,selectedCell = selectedCell,onCellClick = { row, col ->if (isProcessing) return@GameBoardcoroutineScope.launch {handleCellClick(row, col, board, selectedCell, score) { newBoard, newScore, newSelectedCell ->board = newBoardscore = newScoreselectedCell = newSelectedCell}}})}}// 游戏结束弹窗if (showGameOver) {GameOverDialog(finalScore = score,onRestart = {showGameOver = falsecoroutineScope.launch {isProcessing = trueval newBoard = generateStableBoard()board = newBoardscore = 0selectedCell = nullisProcessing = false}},onExit = {showGameOver = falseonExit() // 调用传入的回调})}// 死局检测结果弹窗if (showDeadlockDialog) {AlertDialog(onDismissRequest = { showDeadlockDialog = false },title = {Text(text = if (deadlockMessage.contains("死局")) "死局检测" else "非死局",color = MaterialTheme.colorScheme.onSurface)},text = {Text(text = deadlockMessage)},confirmButton = {Button(onClick = { showDeadlockDialog = false }) {Text("确定")}})}}}
}@Composable
fun GameOverDialog(finalScore: Int,onRestart: () -> Unit,onExit: () -> Unit
) {AlertDialog(onDismissRequest = { /* 不允许点击外部关闭 */ },title = {Text(text = "游戏结束!", color = MaterialTheme.colorScheme.onSurface)},text = {Text(text = "你的最终分数是:$finalScore\n\n棋盘上已无可能通过交换形成三连。")},confirmButton = {Button(onClick = onRestart) {Text("重新开始")}},dismissButton = {Button(onClick = onExit) {Text("退出游戏")}})
}val fruits = listOf(R.drawable.apple,R.drawable.banana,R.drawable.orange,R.drawable.grape,R.drawable.strawberry,R.drawable.watermelon
)fun generateStableBoard(size: Int = 8): List<List<Int>> {var board = generateRandomBoard(size)while (hasAnyMatch(board)) {board = generateRandomBoard(size)}return board
}fun generateRandomBoard(size: Int): List<List<Int>> {return List(size) { List(size) { Random.nextInt(fruits.size) } }
}fun hasAnyMatch(board: List<List<Int>>): Boolean {if (board.isEmpty() || board[0].isEmpty()) return falsefor (i in board.indices) {for (j in board[0].indices) {if (hasMatch(board, i, j)) {return true}}}return false
}@Composable
fun GameBoard(board: List<List<Int>>,selectedCell: Pair<Int, Int>?,onCellClick: (Int, Int) -> Unit
) {val cellSize = 70.dpval spacing = 4.dpColumn(modifier = Modifier.padding(8.dp),verticalArrangement = Arrangement.spacedBy(spacing)) {for ((rowIndex, row) in board.withIndex()) {Row(horizontalArrangement = Arrangement.spacedBy(spacing),modifier = Modifier.fillMaxWidth().height(cellSize)) {for ((colIndex, _) in row.withIndex()) {val isSelected = selectedCell?.first == rowIndex && selectedCell?.second == colIndexval borderColor = if (isSelected) MaterialTheme.colorScheme.tertiary else Color.Transparentval borderWidth = if (isSelected) 3.dp else 0.dpBox(modifier = Modifier.size(cellSize).clip(RoundedCornerShape(8.dp)).background(MaterialTheme.colorScheme.secondary).border(borderWidth, borderColor, RoundedCornerShape(8.dp)).clickable { onCellClick(rowIndex, colIndex) },contentAlignment = Alignment.Center) {Image(painter = painterResource(id = fruits[board[rowIndex][colIndex]]),contentDescription = "Fruit",contentScale = ContentScale.Fit,modifier = Modifier.fillMaxSize().padding(4.dp))}}}}}
}fun isAdjacent(pos1: Pair<Int, Int>, pos2: Pair<Int, Int>): Boolean {return (pos1.first == pos2.first && abs(pos1.second - pos2.second) == 1) ||(pos1.second == pos2.second && abs(pos1.first - pos2.first) == 1)
}fun hasMatch(board: List<List<Int>>, row: Int, col: Int): Boolean {if (board.isEmpty() || board[0].isEmpty()) return falseif (row !in board.indices || col !in board[0].indices) return falseval target = board[row][col]var count = 1for (c in col + 1 until board[0].size) if (board[row][c] == target) count++ else breakfor (c in col - 1 downTo 0) if (board[row][c] == target) count++ else breakif (count >= 3) return truecount = 1for (r in row + 1 until board.size) if (board[r][col] == target) count++ else breakfor (r in row - 1 downTo 0) if (board[r][col] == target) count++ else breakreturn count >= 3
}// 检查任意一次交换是否能形成三连
fun hasAnyValidMove(board: List<List<Int>>): Boolean {if (board.isEmpty() || board[0].isEmpty()) return falseval size = board.sizefor (i in 0 until size) {for (j in 0 until size) {// 尝试与右边交换if (j < size - 1) {if (wouldCreateMatch(board, i, j, i, j + 1)) return true}// 尝试与下边交换if (i < size - 1) {if (wouldCreateMatch(board, i, j, i + 1, j)) return true}}}return false
}// 模拟交换后是否会形成三连
fun wouldCreateMatch(board: List<List<Int>>,r1: Int, c1: Int,r2: Int, c2: Int
): Boolean {val newBoard = board.map { it.toMutableList() }.toMutableList()val temp = newBoard[r1][c1]newBoard[r1][c1] = newBoard[r2][c2]newBoard[r2][c2] = tempreturn hasMatch(newBoard, r1, c1) || hasMatch(newBoard, r2, c2)
}fun eliminateMatches(board: List<List<Int>>): Pair<List<List<Int>>, Int> {if (board.isEmpty() || board[0].isEmpty()) return board to 0val mark = MutableList(board.size) { BooleanArray(board[0].size) }var eliminatedCount = 0for (i in board.indices) {for (j in board[0].indices) {if (hasMatch(board, i, j)) {mark[i][j] = trueeliminatedCount++}}}if (eliminatedCount == 0) return board to 0val newBoard = List(board.size) { MutableList(board[0].size) { 0 } }for (j in board[0].indices) {var writeRow = board.size - 1for (i in board.size - 1 downTo 0) {if (!mark[i][j]) {newBoard[writeRow][j] = board[i][j]writeRow--}}while (writeRow >= 0) {newBoard[writeRow][j] = Random.nextInt(fruits.size)writeRow--}}return newBoard to eliminatedCount
}suspend fun processChainElimination(initialBoard: List<List<Int>>,onUpdate: (List<List<Int>>, Int) -> Unit
) {var currentBoard = initialBoardvar totalScore = 0while (true) {val (newBoard, count) = eliminateMatches(currentBoard)if (count == 0) breaktotalScore += count * 10currentBoard = newBoard// 可加 delay(200) 实现动画节奏感}onUpdate(currentBoard, totalScore)
}suspend fun handleCellClick(row: Int,col: Int,currentBoard: List<List<Int>>,selectedCell: Pair<Int, Int>?,currentScore: Int,onResult: (newBoard: List<List<Int>>, newScore: Int, newSelectedCell: Pair<Int, Int>?) -> Unit
) {val clickPos = row to colif (selectedCell == null) {onResult(currentBoard, currentScore, clickPos)return}if (clickPos == selectedCell) {onResult(currentBoard, currentScore, null)return}if (!isAdjacent(selectedCell, clickPos)) {// 点击非相邻格子时,取消选中状态并选中新格子onResult(currentBoard, currentScore, clickPos)return}val boardAfterSwap = currentBoard.map { it.toMutableList() }.toMutableList()val temp = boardAfterSwap[selectedCell.first][selectedCell.second]boardAfterSwap[selectedCell.first][selectedCell.second] = boardAfterSwap[clickPos.first][clickPos.second]boardAfterSwap[clickPos.first][clickPos.second] = tempif (hasMatch(boardAfterSwap, selectedCell.first, selectedCell.second) || hasMatch(boardAfterSwap, clickPos.first, clickPos.second)) {processChainElimination(boardAfterSwap) { finalBoard, chainScore ->onResult(finalBoard, currentScore + chainScore, null)}} else {// 交换无效时,取消选中状态onResult(currentBoard, currentScore, null)}
}