虽然我不是做游戏的,闲的没事,emm,写了个扫雷小游戏(Android)

Posted 第三女神程忆难

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了虽然我不是做游戏的,闲的没事,emm,写了个扫雷小游戏(Android)相关的知识,希望对你有一定的参考价值。


🔥老规矩,先上效果图


🔥需求分析

实现扫雷高级版,高级版有30*16的网格,480个格子,99个地雷,381个安全区,通过以下操作逻辑完全避开99个地雷视为通关,可使用小红旗最大数量为99个!

操作逻辑:

  1. 单次长按插小红旗,第二次长按填问号,再次长按恢复正常状态
  2. 单次点击进行开疆扩土,踩到地雷本局游戏结束,并显示所有地雷
  3. 当所有雷排干净时,游戏胜利!

🔥实现分析

分配2个地图二维数组,一个是雷区,一个是用户操作图

当用户操作是,判断点击位置,上下左右,以及周围的角,进行扩展

当用户第一次点击时,才是创建雷区的时候,防止用户点击上来就炸!!!

通过递归的方式查询,判断周围雷区,开疆扩土!

话不多说,源码开始!!!


🔥代码实现

代码实现开始了!

MinefieldUtil

公共地图类,这个类承担的作用,如下注释,包含创建,分配地图等,注释写的很详细了,可以直接看:

package com.cyn.traps

import android.util.Log
import java.util.*

/**
 * 雷区工具类
 */
object MinefieldUtil {

    private const val TAG = "log-trap"

    //是否已经建立了游戏
    var isEstablish = false

    //剩余小红旗数量
    var flagNum = 0

    //已排除的格子数量
    var turnedOnNum = 0

    //是否公开雷区,当公开雷区也就意味着游戏结束,不能再点击
    var isOpen = false

    //创建一张二维数组代表地雷布置
    //-1:地雷区域
    //0-8:周围地雷数量
    val gameMap = Array(16) { Array(30) { 0 } }

    //用户操作图记录,与地图大小相等
    //0:未开采
    //1:已开踩
    //2:标记小红旗
    //3:问号
    val operationMap = Array(16) { Array(30) { 0 } }

    //特殊坐标,该坐标不允许创建雷区
    private lateinit var specialCoordinate: MutableList<Int>

    /**
     * 创建雷区,当开采第一个方块后,才会开始布置雷区,防止用户上来就炸,并且用户点击处和周围1格不再布置雷区
     */
    fun establish(dTemp: Int, kTemp: Int) {

        if (!isEstablish) {
            isEstablish = true
        } else {
            return
        }

        //创建特殊坐标
        createSpecialCoordinates(dTemp, kTemp)

        //重置用户操作图
        for (d in operationMap.indices) {
            for (k in operationMap[d].indices) {
                operationMap[d][k] = 0
            }
        }

        //剩余小红旗数量重置
        flagNum = 99

        val random = Random()
        val temp = mutableSetOf<Int>()

        //生成要埋地雷的下标
        while (true) {
            val nextInt = random.nextInt(479)

            dTemp * 30 + kTemp


            //如果不是用户点击处以及周围1格,才会采取该坐标
            if (!specialCoordinate.contains(nextInt)) {
                temp.add(nextInt)
            }

            if (99 == temp.size) {
                break
            }
        }

        //埋下地雷
        for (i in temp) {
            val d = i / 30
            val k = i - 30 * d
            gameMap[d][k] = -1
        }

        //计算周围地雷数量
        createTrapsNumber()

        //====log
        Log.d(
            TAG,
            "\\t\\t\\t0\\t1\\t2\\t3\\t4\\t5\\t6\\t7\\t8\\t9\\t10\\t11\\t12\\t13\\t14\\t15\\t16\\t17\\t18\\t19\\t20\\t21\\t22\\t23\\t24\\t25\\t26\\t27\\t28\\t29"
        )
        Log.d(
            TAG,
            "\\t\\t--------------------------------------------------------------------------------------------------------------------------"
        )
        for ((c, i) in gameMap.withIndex()) {
            var str = "$c->\\t\\t"
            for (k in i) {
                str += k.toString() + "\\t"
            }
            Log.d(TAG, str)
        }

    }

    /**
     * 获取陷阱数量
     */
    private fun createTrapsNumber() {
        for (i in gameMap.indices) {
            for (j in gameMap[i].indices) {
                //当此时坐标不是炸弹时候开始计算
                if (-1 != gameMap[i][j]) {
                    var trapNum = 0

                    //查询目标点左侧
                    if (j - 1 >= 0 && -1 == gameMap[i][j - 1]) {
                        trapNum++
                    }

                    //查询目标上侧
                    if (i - 1 >= 0 && -1 == gameMap[i - 1][j]) {
                        trapNum++
                    }

                    //查询目标右侧
                    if (j + 1 <= 29 && -1 == gameMap[i][j + 1]) {
                        trapNum++
                    }

                    //查询目标下侧
                    if (i + 1 <= 15 && -1 == gameMap[i + 1][j]) {
                        trapNum++
                    }

                    //查询左上角
                    if (j - 1 >= 0 && i - 1 >= 0 && -1 == gameMap[i - 1][j - 1]) {
                        trapNum++
                    }

                    //查询右上角
                    if (j + 1 <= 29 && i - 1 >= 0 && -1 == gameMap[i - 1][j + 1]) {
                        trapNum++
                    }

                    //查询右下角
                    if (j + 1 <= 29 && i + 1 <= 15 && -1 == gameMap[i + 1][j + 1]) {
                        trapNum++
                    }

                    //查询左下角
                    if (j - 1 >= 0 && i + 1 <= 15 && -1 == gameMap[i + 1][j - 1]) {
                        trapNum++
                    }

                    //赋值地雷个数
                    gameMap[i][j] = trapNum

                }
            }
        }
    }

    /**
     * 创建特殊坐标
     */
    private fun createSpecialCoordinates(dTemp: Int, kTemp: Int) {
        specialCoordinate = mutableListOf()

        //点击位置
        specialCoordinate.add(dTemp * 30 + kTemp)

        //点击坐标左侧
        if (kTemp >= 1) {
            specialCoordinate.add(dTemp * 30 + kTemp - 1)
        }

        //点击坐标上侧
        if (dTemp >= 1) {
            specialCoordinate.add((dTemp - 1) * 30 + kTemp)
        }

        //点击坐标右侧
        if (kTemp <= 28) {
            specialCoordinate.add(dTemp * 30 + kTemp + 1)
        }

        //点击坐标下侧
        if (dTemp <= 14) {
            specialCoordinate.add((dTemp + 1) * 30 + kTemp)
        }

        //点击坐标的左上
        if (dTemp >= 1 && kTemp >= 1) {
            specialCoordinate.add((dTemp - 1) * 30 + kTemp - 1)
        }

        //点击坐标的右上
        if (dTemp >= 1 && kTemp <= 28) {
            specialCoordinate.add((dTemp - 1) * 30 + kTemp + 1)
        }

        //点击坐标的右下
        if (dTemp <= 14 && kTemp <= 28) {
            specialCoordinate.add((dTemp + 1) * 30 + kTemp + 1)
        }

        //点击坐标的左下
        if (dTemp <= 14 && kTemp >= 1) {
            specialCoordinate.add((dTemp + 1) * 30 + kTemp - 1)
        }

        for (i in specialCoordinate) {
            Log.d(TAG, i.toString())
        }

    }

    /**
     * 重置
     */
    fun reset() {
        isEstablish = false

        isOpen = false

        turnedOnNum = 0

        for (d in gameMap.indices) {
            for (k in gameMap[d].indices) {
                gameMap[d][k] = 0
                operationMap[d][k] = 0
            }
        }

        flagNum = 0
    }


}

RcAdapter

地图适配器,这是一个RecyclerView的适配器,直接用RecyclerView写的,作用是显示地图以及赋予点击事件:

package com.cyn.traps

import android.content.Context
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView

class RcAdapter(var context: Context) : RecyclerView.Adapter<RcAdapter.Holder>() {

    //当游戏失败后,失败处的坐标,此处要着重显示
    var overPosition = 0

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {

        return Holder(LayoutInflater.from(context).inflate(R.layout.item_lattice, parent, false))
    }

    override fun getItemCount(): Int {
        return 480
    }

    override fun onBindViewHolder(holder: Holder, position: Int) {
        val d = position / 30
        val k = position - 30 * d

        //-1:地雷区域
        //0-8:周围地雷数量
        val indexGame = MinefieldUtil.gameMap[d][k]

        //0:未开采
        //1:已开踩
        //2:标记小红旗
        //3:问号
        val indexOperation = MinefieldUtil.operationMap[d][k]

        //判断是否公开雷区
        if (MinefieldUtil.isOpen) {
            //公开雷区,游戏结束

            when (indexOperation) {
                0, 3 -> {
                    if (indexGame == -1) {
                        holder.itemText.setBackgroundResource(R.mipmap.icon_trap_open)
                        holder.itemText.text = ""
                    } else {
                        holder.itemText.setBackgroundResource(R.mipmap.icon_lattice)
                        holder.itemText.text = ""
                    }
                }
                1 -> {
                    holder.itemText.setBackgroundResource(R.mipmap.icon_empty)
                    holder.itemText.text = indexGame.toString()

                    if (0 != indexGame) {
                        holder.itemText.text = indexGame.toString()
                    } else {
                        holder.itemText.text = ""
                    }

                    holder.itemText.setTextColor(
                        when (indexGame) {
                            1 -> ContextCompat.getColor(context, R.color.index1)
                            2 -> ContextCompat.getColor(context, R.color.index2)
                            3 -> ContextCompat.getColor(context, R.color.index3)
                            4 -> ContextCompat.getColor(context, R.color.index4)
                            5 -> ContextCompat.getColor(context, R.color.index5)
                            6 -> ContextCompat.getColor(context, R.color.index6)
                            7 -> ContextCompat.getColor(context, R.color.index7)
                            else -> ContextCompat.getColor(context, R.color.index8)
                        }
                    )
                }
                2 -> {
                    if (indexGame == -1) {
                        holder.itemText.setBackgroundResource(R.mipmap.icon_flag)
                        holder.itemText.text = ""
                    } else {
                        holder.itemText.setBackgroundResource(R.mipmap.icon_flag_error)
                        holder.itemText.text = ""
                    }
                }
            }

            if (indexOperation == 0 && -1 == indexGame) {
                holder.itemText.setBackgroundResource(R.mipmap.icon_trap_open)
                holder.itemText.text = ""
            }

            if (overPosition == position) {
                holder.itemText.setBackgroundResource(R.mipmap.icon_trap)
                holder.itemText.text = ""
            }

        } else {
            //隐藏雷区
            when (indexOperation) {
                0 -> {
                    holder.itemText.setBackgroundResource(R.mipmap.icon_lattice)
                    holder.itemText.text = ""
                    holder.itemText.setOnClickListener {
                        //开采区域
                        if (-1 == indexGame) {
                            //踩到地雷,游戏结束
                            MinefieldUtil.isOpen = true
                            overPosition = position
                            notifyDataSetChanged()
                            dataCallBack?.gameOver()
                        } else {

//                            dataCallBack?.gameWins()

                            //回调游戏开始
                            if (!MinefieldUtil.isEstablish) {
                                dataCallBack?.gameStart()
                            }

                            //本次点击排除一个格子
                            MinefieldUtil.turnedOnNum++

                            //创建地雷,本局游戏只会执行一次,内部已封装好方法
                            MinefieldUtil.establish(d, k)

                            //递归开采其他模块
                            exploitation(d, k)


                            //判断是否已经排除完地雷
                            if (381 == MinefieldUtil.turnedOnNum) {
                                dataCallBack?.gameWins()
                            }

                            //刷新
                            notifyDataSetChanged()

                        }
                    }
                    holder.itemText.setOnLongClickListener {
                        //在该区域插上小红旗

                        //判断小红旗是否用完了
                        if (MinefieldUtil.flagNum <= 0) {
                            return@setOnLongClickListener true
                        }

                        MinefieldUtil.operationMap[d][k] = 2

                        //回调使用了小红旗
                        dataCallBack?.useFlag()

                        notifyDataSetChanged()
                        return@setOnLongClickListener true
                    }
                }

                1 -> {
                    if (0 == indexGame) {
                        //已开采周围没有地雷的方块
                        holder.itemText.setBackgroundResource(R.mipmap.icon_empty)
                        holder.itemText.text = ""
                    } else {
                        //已开采周围有地雷的方块
                        holder.itemText.setBackgroundResource(R.mipmap.icon_empty)
                        holder.itemText.text = indexGame<

以上是关于虽然我不是做游戏的,闲的没事,emm,写了个扫雷小游戏(Android)的主要内容,如果未能解决你的问题,请参考以下文章

虽然我不是做游戏的,闲的没事,emm,写了个扫雷小游戏(Android)

闲的无聊写了个很(wu)有(liao)意(dao)思(bao)的程序

JavaScript 做的网页版扫雷小游戏

java小项目之:扫雷,这游戏其实没有那么简单!

第一次写小小小小小游戏, 扫雷

源码项目+解析C语言/C++开发,打造一个小项目扫雷小游戏!