摸鱼写游戏 之 《扫个锤子雷》

205 阅读4分钟

前言

本次做的小游戏是经典的扫雷游戏,以前的老旧电脑上面会自带扫雷游戏,应该都玩过。以前玩这个的时候,基本上都是乱蒙,也不知道格子里出现的数字是啥意思,也没想着去查。自从后来入了这一行,才慢慢去了解一下,原来每个数字的意思,是数字周围一圈其他8个格子中所包含的雷数,知道这个才慢慢喜欢并上手这个小游戏。

这次我们就来手把手做一个H5版本的《扫雷》。因为用的是uniapp,所以可在小程序或者H5移动版使用,用uniapp主要也是布局样式啥的比较简单快速。后面再做小游戏,会用vue去写。

一、基本布局

这里我使用的布局样式,是参考微信小游戏里面的扫雷,我感觉画面很舒服,就直接拿来参考了。后续我会把做的所有游戏都按照这个样式风格重新修改调整统一。

在这里插入图片描述

  1. 首先是头部的时间与雷数展示
<!-- 时间、雷数 -->
<view class="bomb-head flexItems boxtb">
	<view class="bomb-head-time flexCenter flex1">
		<view class="bomb-head-icon flexCenter">
			<u-icon name="clock" size="80" color="#999"></u-icon>
		</view>
		<view class="bomb-head-box">{{time}}</view>
	</view>
	<view class="bomb-head-number flexCenter flex1">
		<view class="bomb-head-icon flexCenter">
			<u-icon name="/static/image/home/index-bomb.png" size="70"></u-icon>
		</view>
		<view class="bomb-head-box">{{showBomb}}</view>
	</view>
</view>
  1. 其次是中间战场展示
<!-- 游戏区 -->
<view class="bomb-main flex1 flexCenter">
	<scroll-view scroll-y="true" style="width: 100%;height: 100%;">
		<view class="bomb-main-box">
			<view class="bomb-tr flex" v-for="(item1, index1) in bombList" :key="index1">
				<view class="bomb-td" v-for="(item2, index2) in item1" :key="index2" @click="bindBlock(index1,index2)">
					<view class="bomb-td-box bomb-td-close" v-if="!item2.is_open">
						<u-icon name="/static/image/bomb/flag.png" size="44" v-show="item2.is_flag" />
					</view>
					<view class="bomb-td-box bomb-td-open" :class="{'bomb-td-boom' : item2.is_bomb}">
						<u-icon name="/static/image/home/index-bomb.png" size="50" v-if="item2.is_bomb" />
						<text v-else>{{item2.num > 0 ? item2.num : ''}}</text>
					</view>
				</view>
			</view>
		</view>
	</scroll-view>
</view>
  1. 最后是底部操作功能展示
<!-- 插旗、翻开、等级 -->
<view class="bomb-foot" v-if="!is_end">
	<view class="bomb-foot-btn" :class="{'bomb-foot-btn-hover': bind == 0}" @click="bindBottom(0)">翻 开</view>
	<view class="bomb-foot-btn" :class="{'bomb-foot-btn-hover': bind == 1}" @click="bindBottom(1)">插 旗</view>
	<view class="bomb-foot-btn" :class="{'bomb-foot-btn-hover': bind == 2}" @click="bindBottom(2)">等 级</view>
</view>
  1. 当我们点击等级的时候,会有一个等级弹框 在这里插入图片描述
<!-- 等级选择 -->
<u-popup v-model="showLevel" mode="center" borderRadius="10">
	<view class="bomb-level">
		<view class="bomb-level-item fontBold flexColumn flexCenter" @click="bindLevel(0)">
			<view class="bomb-level-item-title u-font-40">入 门</view>
			<view class="flexItems u-m-t-10"><u-icon name="/static/image/home/index-bomb.png" size="40"></u-icon>10个<text class="u-m-l-20">10*10</text></view>
		</view>
		<view class="bomb-level-item fontBold flexColumn flexCenter" @click="bindLevel(1)">
			<view class="bomb-level-item-title u-font-40">普 通</view>
			<view class="flexItems u-m-t-10"><u-icon name="/static/image/home/index-bomb.png" size="40"></u-icon>30个<text class="u-m-l-20">15*10</text></view>
		</view>
		<view class="bomb-level-item fontBold flexColumn flexCenter" @click="bindLevel(2)">
			<view class="bomb-level-item-title u-font-40">地 狱</view>
			<view class="flexItems u-m-t-10"><u-icon name="/static/image/home/index-bomb.png" size="40"></u-icon>50个<text class="u-m-l-20">20*10</text></view>
		</view>
	</view>
</u-popup>

二、实现

1. 定义我们需要的数据

is_pause: false,	// 是否暂停
is_end: false,		// 是否结束
row: 10,			// 行
col: 10,			// 列
bombList: [],		// 战场二维数组
bomb: 10,			// 地雷数量
showBomb: 10,		// 地雷数字展示
bind: 0,			// 0=翻开 1=插旗 2=暂停
time: 0,			// 需要展示的时间
timer: null,		// 定时器
level: 0,			// 等级
showLevel: false,	// 等级选择弹框

2. 初始化战场

首先我们要知道每一块地,展示的样式,默认是砖块,要么是空地、要么数字、要么插旗子、要么有雷。 基本数据初始化好了,我们需要拿出响应的雷数,也就是提前随机埋地雷。 埋完地雷我们还要去计算雷周围每块地的数字。

(1)布置战场

for(let i = 0; i < this.row; i++){
	this.bombList[i] = [];
	for(let j = 0; j < this.col; j++){
		this.bombList[i][j] = {
			is_open: false,		// 是否翻开
			is_bomb: false,		// 是否有雷
			is_flag: false,		// 是否插旗
			num: 0,				// 提示数字
		};
	}
}

(2)埋地雷

let num = 0;
for(let a = 1; a > 0; a++){
	// 随机选取纵横坐标随机值
	let bomb_x = Math.floor(Math.random() * this.row);
	let bomb_y = Math.floor(Math.random() * this.col);
	// 如果随机的地方没有雷,则可以埋雷
	if(!this.bombList[bomb_x][bomb_y].is_bomb){
		this.bombList[bomb_x][bomb_y].is_bomb = true;
		num++;
	}
	if(num == this.bomb) break;
}

(3)计算每个雷周围的数字

这里也可以去循环地雷,然后给每个雷周围的格子中的数字+1,也能达到效果

this.bombList.forEach((item1, index1) => {
	item1.forEach((item2, index2) => {
		// 计算每个格子周围8个方向上格子,是否有雷
		if((index1 - 1 >= 0 && index2 - 1 >= 0) && this.bombList[index1 - 1][index2 - 1].is_bomb){
			item2.num++;
		}
		if((index1 - 1 >= 0) && this.bombList[index1 - 1][index2].is_bomb){
			item2.num++;
		}
		if((index1 - 1 >= 0 && index2 + 1 < this.col) && this.bombList[index1 - 1][index2 + 1].is_bomb){
			item2.num++;
		}
		if((index2 - 1 >= 0) && this.bombList[index1][index2 - 1].is_bomb){
			item2.num++;
		}
		if((index2 + 1 < this.col) && this.bombList[index1][index2 + 1].is_bomb){
			item2.num++;
		}
		if((index1 + 1 < this.row && index2-1 >= 0) && this.bombList[index1 + 1][index2 - 1].is_bomb){
			item2.num++;
		}
		if((index1 + 1 < this.row) && this.bombList[index1 + 1][index2].is_bomb){
			item2.num++;
		}
		if((index1 + 1 < this.row && index2 + 1 < this.col) && this.bombList[index1 + 1][index2 + 1].is_bomb){
			item2.num++;
		}
	});
});

(4)游戏开始时,计时器也会开始

// 计时器
clearInterval(this.timer)
this.timer = setInterval(() => {
	this.time++;
}, 1000)

3. 点击方块

要注意,我们点击扫雷和插旗,需要跟下面的按钮切换配合 这里有一点就是,当我们点击到空地,这里的空地是没有数字的空地,需要注意,空地会向四面八方延伸,会自动打开周围所有的空地。

bindBlock(x, y){
	// 是否结束
	if(this.is_end){
		return;
	} 
	// 翻开 bind == 0
	// 当前是翻开操作,并且没有插旗子、没有打开
	if(this.bind == 0 && !this.bombList[x][y].is_flag && !this.bombList[x][y].is_open){
		this.bombList[x][y].is_open = true;
		this.$forceUpdate();
		// 判断是否有雷
		if(this.bombList[x][y].is_bomb){
			// 如果点到雷了,就把所有雷都展示出来
			this.bombList.forEach(item1 => {
				item1.forEach(item2 => {
					if(item2.is_bomb) item2.is_open = true;
				})
			});
			uni.showModal({showCancel: false, content: "你输了"});
			this.is_end = true;
			clearInterval(this.timer);
			return;
		}
		// 判断是否是空白,如果是空白,则需要延伸打开空地周围的方块
		if(this.bombList[x][y].num == 0){
			this.blockExtend(x, y);
		}
		// 判断是否赢了
		if(this.getWin()){
			uni.showModal({showCancel: false, content: "你!终!于!赢!了!"});
			this.is_end = true;
			clearInterval(this.timer);
			return;
		}
	}
	// 插旗子 bind == 1
	// 只有没翻开的地,才能插旗子,每插一个棋子,或者去掉一个棋子,都会进行响应雷数+1或-1
	if(this.bind == 1 && !this.bombList[x][y].is_open){
		this.bombList[x][y].is_flag = !this.bombList[x][y].is_flag;
		this.bombList[x][y].is_flag ? this.showBomb-- : this.showBomb++;
		this.$forceUpdate();
	}
}

在这里插入图片描述

4. 空地延伸

这块也跟简单,就是判断空地方块周围8个方位是否有空地,利用递归可完成。 [x-1, y], [x+1, y], [x, y-1], [x, y+1], [x-1, y-1], [x+1, y+1], [x-1, y+1], [x+1, y-1]

blockExtend(x, y){
	if(x - 1 >= 0 && !this.bombList[x - 1][y].is_bomb && !this.bombList[x - 1][y].is_open && !this.bombList[x - 1][y].is_flag){
		this.bombList[x - 1][y].is_open = true;
		if(this.bombList[x - 1][y].num == 0){
			this.blockExtend(x - 1, y);
		}
	}
	if(x + 1 < this.row && !this.bombList[x + 1][y].is_bomb && !this.bombList[x + 1][y].is_open && !this.bombList[x + 1][y].is_flag){
		this.bombList[x + 1][y].is_open = true;
		if(this.bombList[x + 1][y].num == 0){
			this.blockExtend(x + 1, y);
		}
	}
	if(y - 1 >= 0 && !this.bombList[x][y - 1].is_bomb && !this.bombList[x][y - 1].is_open && !this.bombList[x][y - 1].is_flag){
		this.bombList[x][y - 1].is_open = true;
		if(this.bombList[x][y - 1].num == 0){
			this.blockExtend(x, y - 1);
		}
	}
	if((y + 1 < this.col) && !this.bombList[x][y + 1].is_bomb && !this.bombList[x][y + 1].is_open && !this.bombList[x][y + 1].is_flag){
		this.bombList[x][y + 1].is_open = true;
		if(this.bombList[x][y + 1].num == 0){
			this.blockExtend(x, y + 1);
		}
	}
	if((x - 1 >=0 && y - 1 >= 0) && !this.bombList[x - 1][y - 1].is_bomb && !this.bombList[x - 1][y - 1].is_open && !this.bombList[x - 1][y - 1].is_flag){
		this.bombList[x - 1][y - 1].is_open = true;
		if(this.bombList[x - 1][y - 1].num == 0){
			this.blockExtend(x - 1, y - 1);
		}
	}
	if((x + 1 < this.row && y + 1 < this.col) && !this.bombList[x + 1][y + 1].is_bomb && !this.bombList[x + 1][y + 1].is_open && !this.bombList[x + 1][y + 1].is_flag){
		this.bombList[x + 1][y + 1].is_open = true;
		if(this.bombList[x + 1][y + 1].num == 0){
			this.blockExtend(x + 1, y + 1);
		}
	}
	if((x - 1 >= 0 && y + 1 < this.col) && !this.bombList[x - 1][y + 1].is_bomb && !this.bombList[x - 1][y + 1].is_open && !this.bombList[x - 1][y + 1].is_flag){
		this.bombList[x - 1][y + 1].is_open = true;
		if(this.bombList[x - 1][y + 1].num == 0){
			this.blockExtend(x - 1, y + 1);
		}
	}
	if((x + 1 < this.row && y - 1 >= 0) && !this.bombList[x + 1][y - 1].is_bomb && !this.bombList[x + 1][y - 1].is_open && !this.bombList[x + 1][y - 1].is_flag){
		this.bombList[x + 1][y - 1].is_open = true;
		if(this.bombList[x + 1][y - 1].num == 0){
			this.blockExtend(x + 1, y - 1);
		}
	}
}

5. 判断输赢

每点开一个方块,就判断剩余方块数是否等于雷数(前提是点开的那个方块不是雷)

getWin(){
	let arr = [];
	this.bombList.forEach(item1 => {
		item1.forEach(item2 => {
			if(!item2.is_open){
				arr.push(item2);
			}
		});
	});
	return arr.length == this.bomb ? true : false;
}

三、总结

这个游戏总体下来比较简单的,不算难,比较适合新手来锻炼代码和逻辑思维能力。

以上就是全部内容,下面是代码地址。 游戏完整代码地址 gitee