kotlin、jetpack compose、Android消消乐游戏

发布于:2025-05-07 ⋅ 阅读:(10) ⋅ 点赞:(0)

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
                )

            }
        }
    }
}

网站公告

今日签到

点亮在社区的每一天
去签到