1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > 虽然我不是做游戏的 闲的没事 emm 写了个扫雷小游戏(Android)

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

时间:2020-02-05 18:38:00

相关推荐

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

文章目录

🔥老规矩,先上效果图🔥需求分析🔥实现分析🔥代码实现MinefieldUtilRcAdapterMainActivity🔥源码下载

🔥老规矩,先上效果图

🔥需求分析

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

操作逻辑:

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

🔥实现分析

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

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

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

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

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

🔥代码实现

代码实现开始了!

MinefieldUtil

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

package com.cyn.trapsimport android.util.Logimport 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 = 99val 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 / 30val k = i - 30 * dgameMap[d][k] = -1}//计算周围地雷数量createTrapsNumber()//====logLog.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 = falseisOpen = falseturnedOnNum = 0for (d in gameMap.indices) {for (k in gameMap[d].indices) {gameMap[d][k] = 0operationMap[d][k] = 0}}flagNum = 0}}

RcAdapter

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

package com.cyn.trapsimport android.content.Contextimport android.util.Logimport android.view.LayoutInflaterimport android.view.Viewimport android.view.ViewGroupimport android.widget.TextViewimport androidx.core.content.ContextCompatimport androidx.recyclerview.widget.RecyclerViewclass RcAdapter(var context: Context) : RecyclerView.Adapter<RcAdapter.Holder>() {//当游戏失败后,失败处的坐标,此处要着重显示var overPosition = 0override 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 / 30val 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 = trueoverPosition = positionnotifyDataSetChanged()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] = 3dataCallBack?.cancelFlag()notifyDataSetChanged()return@setOnLongClickListener true}}3 -> {holder.itemText.setBackgroundResource(R.mipmap.icon_doubt)holder.itemText.text = ""holder.itemText.setOnLongClickListener {MinefieldUtil.operationMap[d][k] = 0notifyDataSetChanged()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] = 1if (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] = 1MinefieldUtil.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] = 1MinefieldUtil.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] = 1MinefieldUtil.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] = 1MinefieldUtil.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] = 1MinefieldUtil.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] = 1MinefieldUtil.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] = 1MinefieldUtil.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] = 1MinefieldUtil.turnedOnNum++if (MinefieldUtil.gameMap[d + 1][k - 1] == 0) {exploitation(d + 1, k - 1)}}}}//==============================================================================================//相关事件回调private var dataCallBack: DataCallBack? = nullfun setDataCallBack(dataCallBack: DataCallBack) {this.dataCallBack = dataCallBack}interface DataCallBack {fun gameStart()//游戏结束fun gameOver()//使用小红旗fun useFlag()//取消使用小红旗fun cancelFlag()//游戏胜利fun gameWins()}}

MainActivity

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

package com.cyn.trapsimport android.annotation.SuppressLintimport android.content.Contextimport android.os.Bundleimport android.os.Handlerimport android.view.LayoutInflaterimport android.view.Viewimport android.widget.LinearLayoutimport android.widget.TextViewimport androidx.appcompat.app.AppCompatActivityimport androidx.recyclerview.widget.GridLayoutManagerimport androidx.recyclerview.widget.RecyclerViewclass MainActivity : AppCompatActivity() {lateinit var rcAdapter: RcAdapterlateinit var flagNum: TextViewlateinit var time: TextViewlateinit var reset: TextViewprivate var time1 = 0Lprivate var time2 = 0Lprivate 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.layoutParamsval pxValue = dip2px(this, 38F)layoutParams.width = pxValue * 30layoutParams.height = pxValue * 16rc.layoutManager = GridLayoutManager(this, 30)rcAdapter = RcAdapter(this)rc.adapter = rcAdapterrcAdapter.setDataCallBack(object : RcAdapter.DataCallBack {//游戏开始override fun gameStart() {time1 = 0time2 = 0handler.post(mCounter)}//游戏结束override fun gameOver() {MinefieldUtil.isEstablish = falseflagNum.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 = 0time2 = 0MinefieldUtil.reset()rcAdapter.notifyDataSetChanged()boxDialog.dismiss()}boxDialog = BoxDialog(this@MainActivity, inflate, BoxDialog.LocationView.CENTER)boxDialog.setCancelable(false)//是否可以点击DialogView外关闭DialogboxDialog.setCanceledOnTouchOutside(false)//是否可以按返回按钮关闭DialogboxDialog.show()}})}/*** dp转px*/private fun dip2px(context: Context, dpValue: Float): Int {val scale: Float = context.resources.displayMetrics.densityreturn (dpValue * scale + 0.5f).toInt()}}

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

🔥源码下载

CodeChina(推荐):/qq_40881680/trap

源码下载:/download/bytedemo/19752276

apk安装包下载:/download/bytedemo/19752267

Github: /ThirdGoddess/trap

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。