一个基于Fyne的扫雷小游戏

简介

扫雷这个游戏没有什么好说的,就是排布雷场场加判定,很经典的游戏,于是我尝试用golang自己写一个,选用了fyne框架作为UI。个人认为fyne灵活程度不是很高,自定义有些难度,拿来做一些简单的ui可以,但是想要深度定制需要下一番功夫去学习。

效果展示

  1. 游戏菜单——选择游戏难度
    在这里插入图片描述
  2. 点击进入游戏主界面
    在这里插入图片描述
  3. 游戏过程
    请添加图片描述

两个难点

懂得使用fyne进行简单的元素排布其实整个界面就能写出来了,无非是根据数组循环生成按钮和标签。不过生成雷场是一个难点,另一个我认为是做到点击空地后一点打开一大片的效果

一、生成雷场

这个项目中我使用了自定义的Vector3结构去记录每个单元的X,Y位置信息,以及单元格代表的属性(0表示空地,-1表示地雷,其余数字表示该方格周围存在的地雷数)

type Vector3 struct {
	X   int
	Y   int
	Num int
}

这样,如果我们想要布雷,只需要根据两个随机数生成器生成x,y上的随机数就可以了,然后准备一个Vector3的数组来装载这些单元。不过记得除重以及判断是否超界,否则就会出现报错和莫名其妙的BUG。
那么,如何去求那些烦人的数字呢?现在我的手里有一个数组的代表单元格的结构体,但是我怎么去求这些单元格之间互相纠葛的数字呢?这个事情让我纠结了好久。后来我想到了一个方法:既然这个数字代表的是附近地雷的数量,那么反过来想,当你凝视深渊时,深渊也在凝视着你。每个雷单元都会处于这个数字的单元格周围范围内,我只需要去遍历数组中存在的雷单元格,然后再遍历每个雷单元格周围的单元格,将遍历到的代表数字的单元格数字+1就可以了。为什么要说“代表数字”的单元格,因为有些雷单元可能相邻,千万别忘了处理这些格子,不然你的雷单元就会消失。另外也不要忘了判断是否出界,不然会出错的。
以下是处理这个功能的代码:

func (mf *MineField) getAroundMinesNum() {
	//这里拿到了Vector3数组
	mineList := mf.mapData
	for i := 0; i < len(mineList); i++ {
		//遍历周边单元
		for x := -1; x <= 1; x++ {
			for y := -1; y <= 1; y++ {
				aroundPoint := &Vector3{
					X: mineList[i].X + x,
					Y: mineList[i].Y + y,
				}
				//如果是本身,则跳过
				if aroundPoint.isEqual(mineList[i]) {
					continue
				}
				//如果数组中存在
				//1. 判断是否是雷,如果是则跳过
				//2. 如果不是雷,则Num加一
				index := mf.getPointIndex(*aroundPoint)
				if index != -1 {
					if mf.mapData[index].Num == -1 {
						continue
					} else {
						mf.mapData[index].Num++
					}
				} else {
					//如果不存在,则添加;如果位置非法则放弃添加
					mf.addNums(*aroundPoint)
				}
			}
		}
	}
}

二、点击空地开一片的效果

到这里得说明一下用户界面的结构。大家都知道,每一个元素的单元格其实都是被按钮遮盖的,点击之后让单元格上的按钮消失就会显示你扫到的到底是个啥,那么底下的元素怎么显示呢?我的方案是使用Label作为底层显示的元素,去表示空地,数字,或者地雷——这里我偷了个懒取了个巧 ,所有图标都是依靠emoji显示的,包括地雷和旗子,省去了很多麻烦。
为了让每个单元格由两个组件表示,我选择了StackContainer,它的特性是只会显示最上层组件,因此只要上层按钮隐藏,自然会露出下面藏着的label。
在这里插入图片描述

根据Vector3数组转换的整型二维数组表示的地图数据,只需要遍历各单元,label根据每个单元的类型表示相应元素就成了。
不过如何做到点击空地时隐藏一片按钮直到一圈数字呢?这个问题好比于在一片空间内找边界,也许可以用到一个广度优先算法。如果知道了我们点击的表示空地的按钮位置,就寻找它周围的数字、空地元素并隐藏他们的按钮,如果是数字就停止延申,如果是空地就继续这个过程。 进行一个递归调用,最终掀开所有合法单元的脑壳。

func (gv *GameViewer) openField(positon []int) {
	//获得本按钮的位置
	posX := positon[1]
	posY := positon[0]
	//遍历周围单元
	for x := -1; x <= 1; x++ {
		for y := -1; y <= 1; y++ {
			if x == 0 && y == 0 {
				continue
			}
			pointX := posX + x
			pointY := posY + y
			//地址合法
			if pointX >= 0 && pointX < gv.mf.length && pointY >= 0 && pointY < gv.mf.width {
				//获得该点元素类型
				pointNum := gv.mapData[pointY][pointX]
				//获得按钮,以进行操作
				btn := gv.btnArray[pointY*gv.mf.length+pointX]
				//确保朝一个方向蔓延,防止重复访问导致死循环
				if btn.Hidden || btn.Text == "🚩" {
					continue
				}
				btn.Hide()
				//是空地,则递归调用
				if pointNum == 0 {
					newPos := make([]int, 0)
					newPos = append(newPos, pointY)
					newPos = append(newPos, pointX)
					gv.openField(newPos)
				}

			}
		}
	}
}

结语

对于Fyne我还是不熟练,有很多界面设置都是直接摆烂的,实在是还没学会怎么调整它的界面,现在觉得前端html组合还是灵活的多。不过这个小项目倒是让我对golang和fyne更了解了一些,希望能给大家带来一些思考启发和学习。
源码在此:https://github.com/OneWalkerCN/Minesweeper

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值