我是靠谱客的博主 妩媚水池,最近开发中收集的这篇文章主要介绍虽然我不是做游戏的,闲的没事,emm,写了个扫雷小游戏(Android),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

文章目录

      • ????老规矩,先上效果图
      • ????需求分析
      • ????实现分析
      • ????代码实现
        • MinefieldUtil
        • RcAdapter
        • MainActivity
      • ????源码下载


????老规矩,先上效果图

在这里插入图片描述


????需求分析

实现扫雷高级版,高级版有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,
            "ttt0t1t2t3t4t5t6t7t8t9t10t11t12t13t14t15t16t17t18t19t20t21t22t23t24t25t26t27t28t29"
        )
        Log.d(
            TAG,
            "tt--------------------------------------------------------------------------------------------------------------------------"
        )
        for ((c, i) in gameMap.withIndex()) {
            var str = "$c->tt"
            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.toString()
                        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 -> {
                    holder.itemText.setBackgroundResource(R.mipmap.icon_flag)
                    holder.itemText.text = ""
                    holder.itemText.setOnLongClickListener {

                        MinefieldUtil.operationMap[d][k] = 3

                        dataCallBack?.cancelFlag()

                        notifyDataSetChanged()



                        return@setOnLongClickListener true
                    }
                }

                3 -> {
                    holder.itemText.setBackgroundResource(R.mipmap.icon_doubt)
                    holder.itemText.text = ""
                    holder.itemText.setOnLongClickListener {
                        MinefieldUtil.operationMap[d][k] = 0
                        notifyDataSetChanged()
                        return@setOnLongClickListener true
                    }
                }
            }
        }
    }

    class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        val itemText: TextView = itemView.findViewById(R.id.itemText)
    }


    //==============================================================================================
    /**
     * 开采领域,递归调用
     */
    private fun exploitation(d: Int, k: Int) {
        if (MinefieldUtil.gameMap[d][k] >= 0) {
            MinefieldUtil.operationMap[d][k] = 1


            if (0 != MinefieldUtil.gameMap[d][k]) {
                return
            }

            //判断左侧是否开采
            if (k >= 1 && MinefieldUtil.gameMap[d][k - 1] >= 0 && MinefieldUtil.operationMap[d][k - 1] != 1) {
                MinefieldUtil.operationMap[d][k - 1] = 1
                MinefieldUtil.turnedOnNum++
                if (MinefieldUtil.gameMap[d][k - 1] == 0) {
                    exploitation(d, k - 1)
                }
            }

            //判断上侧是否开采
            if (d >= 1 && MinefieldUtil.gameMap[d - 1][k] >= 0 && MinefieldUtil.operationMap[d - 1][k] != 1) {
                MinefieldUtil.operationMap[d - 1][k] = 1
                MinefieldUtil.turnedOnNum++
                if (MinefieldUtil.gameMap[d - 1][k] == 0) {
                    exploitation(d - 1, k)
                }
            }

            //判断右侧是否开采
            if (k <= 28 && MinefieldUtil.gameMap[d][k + 1] >= 0 && MinefieldUtil.operationMap[d][k + 1] != 1) {
                MinefieldUtil.operationMap[d][k + 1] = 1
                MinefieldUtil.turnedOnNum++
                if (MinefieldUtil.gameMap[d][k + 1] == 0) {
                    exploitation(d, k + 1)
                }
            }

            //判断下侧是否开采
            if (d <= 14 && MinefieldUtil.gameMap[d + 1][k] >= 0 && MinefieldUtil.operationMap[d + 1][k] != 1) {
                MinefieldUtil.operationMap[d + 1][k] = 1
                MinefieldUtil.turnedOnNum++
                if (MinefieldUtil.gameMap[d + 1][k] == 0) {
                    exploitation(d + 1, k)
                }
            }

            //判断左上是否开采
            if (d >= 1 && k >= 1 && MinefieldUtil.gameMap[d - 1][k - 1] >= 0 && MinefieldUtil.operationMap[d - 1][k - 1] != 1) {
                MinefieldUtil.operationMap[d - 1][k - 1] = 1
                MinefieldUtil.turnedOnNum++
                if (MinefieldUtil.gameMap[d - 1][k - 1] == 0) {
                    exploitation(d - 1, k - 1)
                }
            }

            //判断右上是否开采
            if (d >= 1 && k <= 28 && MinefieldUtil.gameMap[d - 1][k + 1] >= 0 && MinefieldUtil.operationMap[d - 1][k + 1] != 1) {
                MinefieldUtil.operationMap[d - 1][k + 1] = 1
                MinefieldUtil.turnedOnNum++
                if (MinefieldUtil.gameMap[d - 1][k + 1] == 0) {
                    exploitation(d - 1, k + 1)
                }
            }

            //判断右下是否开采
            if (d <= 14 && k <= 28 && MinefieldUtil.gameMap[d + 1][k + 1] >= 0 && MinefieldUtil.operationMap[d + 1][k + 1] != 1) {
                MinefieldUtil.operationMap[d + 1][k + 1] = 1
                MinefieldUtil.turnedOnNum++
                if (MinefieldUtil.gameMap[d + 1][k + 1] == 0) {
                    exploitation(d + 1, k + 1)
                }
            }

            //判断左下是否开采
            if (d <= 14 && k >= 1 && MinefieldUtil.gameMap[d + 1][k - 1] >= 0 && MinefieldUtil.operationMap[d + 1][k - 1] != 1) {
                MinefieldUtil.operationMap[d + 1][k - 1] = 1
                MinefieldUtil.turnedOnNum++
                if (MinefieldUtil.gameMap[d + 1][k - 1] == 0) {
                    exploitation(d + 1, k - 1)
                }
            }

        }
    }

    //==============================================================================================
    //相关事件回调
    private var dataCallBack: DataCallBack? = null

    fun setDataCallBack(dataCallBack: DataCallBack) {
        this.dataCallBack = dataCallBack
    }

    interface DataCallBack {

        fun gameStart()

        //游戏结束
        fun gameOver()

        //使用小红旗
        fun useFlag()

        //取消使用小红旗
        fun cancelFlag()

        //游戏胜利
        fun gameWins()
    }


}

MainActivity

第一个启动的页面,作用于创建实例!通过它来调用上述两个类来实现的,其功能也写了很详细的注释!可以直接看下面:

package com.cyn.traps

import android.annotation.SuppressLint
import android.content.Context
import android.os.Bundle
import android.os.Handler
import android.view.LayoutInflater
import android.view.View
import android.widget.LinearLayout
import android.widget.TextView
import androidx.appcompat.app.AppCompatActivity
import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.RecyclerView


class MainActivity : AppCompatActivity() {

    lateinit var rcAdapter: RcAdapter
    lateinit var flagNum: TextView
    lateinit var time: TextView
    lateinit var reset: TextView

    private var time1 = 0L
    private var time2 = 0L

    private val handler: Handler = Handler()

    //胜利弹框
    private lateinit var boxDialog: BoxDialog

    //计时器
    private val mCounter: Runnable = object : Runnable {
        @SuppressLint("SetTextI18n")
        override fun run() {
            handler.postDelayed(this, 1000)
            time2++
            if (60L == time2) {
                time1++
                time2 = 0
            }
            time.text =
                (if (time1 < 10) "0$time1" else time1.toString()) + ":" + if (time2 < 10) "0$time2" else time2.toString()
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //状态栏设置
        val titleBar = findViewById<LinearLayout>(R.id.titleBar)
        StatusBarUtil.immersive(this)
        StatusBarUtil.darkMode(this)
        StatusBarUtil.setPaddingSmart(this, titleBar)

        //控件实例化
        flagNum = findViewById(R.id.flagNum)
        time = findViewById(R.id.time)
        reset = findViewById(R.id.reset)

        //加载游戏布局
        initView()

        //重置点击
        reset.setOnClickListener {

            //重新创建游戏
            MinefieldUtil.reset()

            //计时器重置
            handler.removeCallbacks(mCounter)
            time.text = "00:00"

            //小红旗重置
            flagNum.text = "--"

            //列表刷新
            rcAdapter.notifyDataSetChanged()

        }


    }

    @SuppressLint("ClickableViewAccessibility")
    fun initView() {
        val rc = findViewById<RecyclerView>(R.id.rc)
        val layoutParams = rc.layoutParams
        val pxValue = dip2px(this, 38F)
        layoutParams.width = pxValue * 30
        layoutParams.height = pxValue * 16

        rc.layoutManager = GridLayoutManager(this, 30)
        rcAdapter = RcAdapter(this)
        rc.adapter = rcAdapter

        rcAdapter.setDataCallBack(object : RcAdapter.DataCallBack {

            //游戏开始
            override fun gameStart() {
                time1 = 0
                time2 = 0
                handler.post(mCounter)
            }

            //游戏结束
            override fun gameOver() {
                MinefieldUtil.isEstablish = false
                flagNum.text = "--"

                //停止计时
                handler.removeCallbacks(mCounter)
            }

            //使用小红旗
            override fun useFlag() {
                if (MinefieldUtil.isEstablish) {
                    MinefieldUtil.flagNum--
                    flagNum.text = MinefieldUtil.flagNum.toString()
                }
            }

            //取消使用小红旗
            override fun cancelFlag() {
                if (MinefieldUtil.isEstablish) {
                    MinefieldUtil.flagNum++
                    flagNum.text = MinefieldUtil.flagNum.toString()
                }
            }

            //游戏胜利
            @SuppressLint("SetTextI18n")
            override fun gameWins() {
                //停止计时
                handler.removeCallbacks(mCounter)

                //弹出游戏胜利
                val inflate: View = LayoutInflater.from(this@MainActivity)
                    .inflate(R.layout.dialog_win, null, false)

                val consume = inflate.findViewById<TextView>(R.id.consume)

                consume.text =
                    "用时:" + (if (time1 < 10) "0$time1" else time1.toString()) + ":" + if (time2 < 10) "0$time2" else time2.toString()

                val again = inflate.findViewById<TextView>(R.id.again)
                again.setOnClickListener {
                    //重新创建游戏
                    time1 = 0
                    time2 = 0
                    MinefieldUtil.reset()
                    rcAdapter.notifyDataSetChanged()
                    boxDialog.dismiss()
                }

                boxDialog = BoxDialog(this@MainActivity, inflate, BoxDialog.LocationView.CENTER)
                boxDialog.setCancelable(false)//是否可以点击DialogView外关闭Dialog
                boxDialog.setCanceledOnTouchOutside(false)//是否可以按返回按钮关闭Dialog
                boxDialog.show()

            }

        })

    }

    /**
     * dp转px
     */
    private fun dip2px(context: Context, dpValue: Float): Int {
        val scale: Float = context.resources.displayMetrics.density
        return (dpValue * scale + 0.5f).toInt()
    }
}

综上所述,完成基本靠以上3个类,这是主要逻辑代码!


????源码下载

CodeChina(推荐):https://codechina.csdn.net/qq_40881680/trap

源码下载:https://download.csdn.net/download/bytedemo/19752276

apk安装包下载:https://download.csdn.net/download/bytedemo/19752267

Github: https://github.com/ThirdGoddess/trap


最后

以上就是妩媚水池为你收集整理的虽然我不是做游戏的,闲的没事,emm,写了个扫雷小游戏(Android)的全部内容,希望文章能够帮你解决虽然我不是做游戏的,闲的没事,emm,写了个扫雷小游戏(Android)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(48)

评论列表共有 0 条评论

立即
投稿
返回
顶部