虽然我不是做游戏的,闲的没事,emm,写了个扫雷小游戏(Android)
Posted 第三女神程忆难
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了虽然我不是做游戏的,闲的没事,emm,写了个扫雷小游戏(Android)相关的知识,希望对你有一定的参考价值。
🔥老规矩,先上效果图
🔥需求分析
实现扫雷高级版,高级版有30*16的网格,480个格子,99个地雷,381个安全区,通过以下操作逻辑完全避开99个地雷视为通关,可使用小红旗最大数量为99个!
操作逻辑:
- 单次长按插小红旗,第二次长按填问号,再次长按恢复正常状态
- 单次点击进行开疆扩土,踩到地雷本局游戏结束,并显示所有地雷
- 当所有雷排干净时,游戏胜利!
🔥实现分析
分配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)