概述
文章目录
- ????老规矩,先上效果图
- ????需求分析
- ????实现分析
- ????代码实现
- MinefieldUtil
- RcAdapter
- MainActivity
- ????源码下载
????老规矩,先上效果图
????需求分析
实现扫雷高级版,高级版有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,
"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)所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复