package com.example.myxiaoxiaole import android.content.Context import android.os.Bundle import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.activity.enableEdgeToEdge import androidx.compose.animation.AnimatedVisibility import androidx.compose.animation.core.animateDpAsState import androidx.compose.animation.core.spring import androidx.compose.animation.core.tween import androidx.compose.animation.expandIn import androidx.compose.animation.fadeIn import androidx.compose.animation.fadeOut import androidx.compose.animation.shrinkOut import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.padding import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview import com.example.myxiaoxiaole.ui.theme.MyXiaoXiaoLeTheme import androidx.compose.foundation.Canvas import androidx.compose.foundation.background import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.size import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.setValue import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import kotlin.random.Random import androidx.compose.ui.Alignment import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.foundation.gestures.detectTapGestures import androidx.compose.foundation.layout.aspectRatio import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.derivedStateOf import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.Dp import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.foundation.gestures.detectDragGestures import androidx.compose.foundation.gestures.detectDragGesturesAfterLongPress import androidx.compose.foundation.layout.offset import androidx.compose.ui.input.pointer.PointerInputChange import kotlin.math.abs import androidx.compose.ui.geometry.Size import androidx.compose.ui.graphics.drawscope.Stroke import androidx.compose.ui.unit.IntOffset import kotlin.math.roundToInt import androidx.compose.foundation.layout.Column import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateSetOf import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.toMutableStateList import androidx.compose.ui.graphics.Brush import kotlinx.coroutines.delay import kotlinx.coroutines.launch import android.media.SoundPool import android.media.AudioAttributes import androidx.compose.runtime.DisposableEffect import androidx.compose.ui.platform.LocalContext import androidx.compose.foundation.layout.Row import androidx.compose.material3.Button // 棋盘参数 const val BOARD_SIZE = 8 // 8x8棋盘 const val CELL_SIZE_DP = 40 // 每个格子大小 class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) enableEdgeToEdge() // 启用边缘到边缘的布局,即全屏显示 setContent { val scope = rememberCoroutineScope() // 新增协程作用域声明 fun generateNewChesses() = mutableStateListOf<Chess>().apply { repeat(BOARD_SIZE * BOARD_SIZE) { index -> add( Chess( id = index + 1, col = index % BOARD_SIZE, row = index / BOARD_SIZE ) ) } } var restartKey by remember { mutableStateOf(0) } // 添加重启触发器 var chesses by remember(restartKey) { mutableStateOf(generateNewChesses()) } var score by remember { mutableStateOf(0) } // 添加分数状态 var comboCount by remember { mutableStateOf(0) } // 连击计数器 val prefs = remember { getPreferences(Context.MODE_PRIVATE) } var highScore by remember { mutableStateOf(prefs.getInt("high_score", 0)) } MyXiaoXiaoLeTheme { Box( modifier = Modifier .fillMaxSize() .background(Color.Black), contentAlignment = Alignment.Center ) { Column { ChessBoard( chesses = chesses, onElimination = { eliminatedCount -> // 计算分数:基础分 + 连击加成 val baseScore = eliminatedCount * 100 val comboBonus = comboCount * 50 score += baseScore + comboBonus // 新增分数比较逻辑 if (score > highScore) { highScore = score prefs.edit().putInt("high_score", highScore).apply() } comboCount++ // 增加连击 }, onComboEnd = { comboCount = 0 } , comboCount = comboCount, restartKey = restartKey ) Button( onClick = { chesses.clear() // 先清空现有数据 chesses.addAll(generateNewChesses()) // 添加全新数据 restartKey++ // 触发棋盘刷新 score = 0 comboCount = 0 }, modifier = Modifier .align(Alignment.CenterHorizontally) .padding(top = 64.dp) // 调整顶部间距 .padding(16.dp) ) { Text("重新开始", color = Color.White) } } Text( text = "最高记录: $highScore\n当前分数: $score\n连击: x${comboCount + 1}", color = Color.White, // 新增颜色设置 modifier = Modifier .align(Alignment.TopCenter) .padding(top = 64.dp) // 新增顶部间距 ) } } } } } // 棋子数据类 data class Chess( val id: Int, // 棋子的唯一标识符 var col: Int, // 棋子所在的列 var row: Int, // 棋子所在的行 val color: Color = colors.random(), // 新增颜色属性 var isSelected: Boolean = false ) { companion object { // 定义7种常用颜色 val colors = listOf( Color.Red, Color.Green, Color.Blue, Color.Yellow, Color.Magenta, Color.Cyan, Color.DarkGray ) } } @Composable fun ChessBoard( chesses: MutableList<Chess>, onElimination: (Int) -> Unit, // 添加消除回调 onComboEnd: () -> Unit, // 添加连击结束回调 comboCount: Int, restartKey: Int ) { val context = LocalContext.current // 添加音效池 val soundPool = remember { SoundPool.Builder().setAudioAttributes( AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_GAME) .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) .build() ).setMaxStreams(3).build() } val clearSoundId = remember { soundPool.load(context, R.raw.clear, 1) } // 清理音效资源 DisposableEffect(Unit) { onDispose { soundPool.release() } } val density = LocalDensity.current val cellSize = remember { with(density) { CELL_SIZE_DP.dp.toPx() } } val recycledIds = remember { mutableStateSetOf<Int>() } val scope = rememberCoroutineScope() // 处理棋子下落 fun processFalling(recycledIds: MutableSet<Int>) { repeat(BOARD_SIZE) { col -> // 获取当前列所有棋子并按原始行号降序排列(从下到上处理) val columnChess = chesses.filter { it.col == col } .sortedByDescending { it.row } .toMutableList() // 清空当前列的临时存储 val tempColumn = mutableListOf<Chess>() var targetRow = BOARD_SIZE - 1 // 第一步:收集现有棋子 columnChess.forEach { chess -> tempColumn.add(chess.copy()) // 创建临时副本 chesses.remove(chess) // 从主列表移除 } // 第二步:重新排列棋子 tempColumn.forEach { chess -> chesses.add(chess.apply { row = targetRow-- // 从底部开始填充新位置 }) } var newTargetRow = targetRow // 使用最终下落后的targetRow值 while (newTargetRow >= 0) { val newId = recycledIds.firstOrNull() ?: break recycledIds.remove(newId) //Thread.sleep(100) chesses.add(Chess( id = newId, col = col, row = newTargetRow--, // 从最后空位开始向上填充 color = Chess.colors.random() )) } } } // 检查并消除同色棋子 fun checkAndEliminate(): Boolean { var eliminationCount = 0 var hasEliminated = false do{ val toEliminate = mutableSetOf<Chess>() // 横向检测 repeat(BOARD_SIZE) { row -> var currentColor: Color? = null var startCol = 0 for (col in 0 until BOARD_SIZE) { val chess = chesses.firstOrNull { it.row == row && it.col == col } if (chess?.color == currentColor) { if (col == BOARD_SIZE - 1 && (col - startCol + 1) >= 3) { (startCol..col).forEach { c -> chesses.firstOrNull { it.row == row && it.col == c }?.let(toEliminate::add) } } } else { if (currentColor != null && (col - startCol) >= 3) { (startCol until col).forEach { c -> chesses.firstOrNull { it.row == row && it.col == c }?.let(toEliminate::add) } } currentColor = chess?.color startCol = col } } } // 纵向检测 repeat(BOARD_SIZE) { col -> var currentColor: Color? = null var startRow = 0 for (row in 0 until BOARD_SIZE) { val chess = chesses.firstOrNull { it.col == col && it.row == row } if (chess?.color == currentColor) { if (row == BOARD_SIZE - 1 && (row - startRow + 1) >= 3) { (startRow..row).forEach { r -> chesses.firstOrNull { it.col == col && it.row == r }?.let(toEliminate::add) } } } else { if (currentColor != null && (row - startRow) >= 3) { (startRow until row).forEach { r -> chesses.firstOrNull { it.col == col && it.row == r }?.let(toEliminate::add) } } currentColor = chess?.color startRow = row } } } // ===== 新增:检测相邻同色棋子 ===== val queue = mutableListOf<Chess>().apply { addAll(toEliminate) } val processed = mutableSetOf<Chess>().apply { addAll(toEliminate) } while (queue.isNotEmpty()) { val current = queue.removeAt(0) listOf( -1 to 0, // 左 1 to 0, // 右 0 to -1, // 上 0 to 1 // 下 ).forEach { (dx, dy) -> val newCol = current.col + dx val newRow = current.row + dy if (newCol in 0 until BOARD_SIZE && newRow in 0 until BOARD_SIZE) { val adjacent = chesses.firstOrNull { it.col == newCol && it.row == newRow && it.color == current.color } if (adjacent != null && processed.add(adjacent)) { queue.add(adjacent) toEliminate.add(adjacent) } } } } // 处理消除 val currentElimination = toEliminate.size toEliminate.forEach { recycledIds.add(it.id) chesses.remove(it) } if (currentElimination > 0) { eliminationCount++ hasEliminated = true processFalling(recycledIds) // 播放消除音效 soundPool.play(clearSoundId, 1f, 1f, 0, 0, 1f) // 触发消除回调 onElimination(currentElimination) } }while(currentElimination > 0) if (eliminationCount > 0) { // 最终检查一次可能的新组合 checkAndEliminate() } // 连击检测逻辑 if (!hasEliminated && eliminationCount > 0) { onComboEnd() } return hasEliminated } // 添加连击超时检测 LaunchedEffect(comboCount) { if (comboCount > 0) { delay(1000) // 1秒内没有新消除则重置连击 onComboEnd() } } // 添加初始消除检测 LaunchedEffect(restartKey) { var eliminationHappened: Boolean do { checkAndEliminate() eliminationHappened = recycledIds.isNotEmpty() recycledIds.clear() } while (eliminationHappened) } Box(modifier = Modifier.size(CELL_SIZE_DP.dp * BOARD_SIZE)) { Canvas(modifier = Modifier .size(CELL_SIZE_DP.dp * BOARD_SIZE) .background(Color.Gray) ) { // 绘制棋盘格子 repeat(BOARD_SIZE + 1) { i -> val pos = i * cellSize // 水平线 drawLine( color = Color.Black, start = Offset(0f, pos), end = Offset(size.width, pos) ) // 垂直线 drawLine( color = Color.Black, start = Offset(pos, 0f), end = Offset(pos, size.height) ) } } chesses.forEach { chess -> ChessPiece( chess = chess, cellSize = cellSize, onDragEnd = { offsetX, offsetY -> scope.launch { val currentChess = chesses.first { it.id == chess.id } val isHorizontal = abs(offsetX) > abs(offsetY) val (targetCol, targetRow) = when { isHorizontal && offsetX > 0 -> currentChess.col + 1 to currentChess.row isHorizontal && offsetX < 0 -> currentChess.col - 1 to currentChess.row !isHorizontal && offsetY > 0 -> currentChess.col to currentChess.row + 1 !isHorizontal && offsetY < 0 -> currentChess.col to currentChess.row - 1 else -> currentChess.col to currentChess.row } if (targetCol in 0 until BOARD_SIZE && targetRow in 0 until BOARD_SIZE) { chesses.firstOrNull { it.col == targetCol && it.row == targetRow }?.let { target -> // 记录原始位置 val originalPositions = mapOf( currentChess.id to (currentChess.col to currentChess.row), target.id to (target.col to target.row) ) // 第一步:执行交换动画 val swapped = listOf( currentChess.copy(col = target.col, row = target.row), target.copy(col = currentChess.col, row = currentChess.row) ) chesses.replaceAll { c -> swapped.find { it.id == c.id } ?: c } delay(300) // 等待交换动画完成 // 第二步:检查消除结果 val hadElimination = checkAndEliminate() // 第三步:根据结果处理 if (!hadElimination) { // 无效交换:还原位置 delay(100) chesses.replaceAll { c -> c.copy( col = originalPositions[c.id]?.first ?: c.col, row = originalPositions[c.id]?.second ?: c.row ) } delay(300) // 等待还原动画 } else { // 有效交换:保持交换后的状态,由消除动画自动处理 delay(100) // 给消除动画留出启动时间 } } } } } ) } } } @Composable fun ChessPiece(chess: Chess, cellSize: Float, onDragEnd: (Float, Float) -> Unit) { // 记录棋子的偏移量 var offsetX by remember { mutableStateOf(0f) } var offsetY by remember { mutableStateOf(0f) } // 将单元格大小转换为dp单位 val cellSizeDp = with(LocalDensity.current) { cellSize.toDp() } // 修改动画参数部分 val animatedRow by animateDpAsState( targetValue = with(LocalDensity.current) { (chess.row * cellSize).toDp() }, animationSpec = spring( dampingRatio = 0.4f, stiffness = 300f ) ) val animatedCol by animateDpAsState( targetValue = with(LocalDensity.current) { (chess.col * cellSize).toDp() }, animationSpec = spring( dampingRatio = 0.4f, stiffness = 300f ) ) var visible by remember { mutableStateOf(true) } LaunchedEffect(chess) { visible = true } AnimatedVisibility( visible = visible, enter = fadeIn(animationSpec = tween(500)) + expandIn( animationSpec = tween(500), expandFrom = Alignment.TopCenter ), exit = fadeOut(animationSpec = tween(500)) + shrinkOut( animationSpec = tween(500), shrinkTowards = Alignment.BottomCenter ) ) { Box(modifier = Modifier .offset { IntOffset( //(chess.col * cellSize + offsetX).roundToInt(), //(chess.row * cellSize + offsetY).roundToInt() animatedCol.roundToPx(), animatedRow.roundToPx() ) } .size(cellSizeDp) .background(Color.Transparent) .pointerInput(chess.id) { detectDragGestures( onDragStart = { // 添加空参数 offsetX = 0f offsetY = 0f }, onDragEnd = { onDragEnd(offsetX, offsetY) // 传递总偏移量 offsetX = 0f offsetY = 0f } ) { _, dragAmount -> offsetX += dragAmount.x offsetY += dragAmount.y } } ) { Canvas(modifier = Modifier.fillMaxSize()) { drawCircle( color = chess.color, radius = cellSize / 2 - 10, center = center ) } } } }