持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第2天,点击查看活动详情
1.绘制游戏区域
16*16的二维数组,双层遍历之后,第一层创建ul标签,第二层创建button标签。为什么用button标签,因为button标签自带点击效果,不需要再额外设置。渲染完之后加上CSS样式,游戏区域就写好了。利用数组来渲染游戏区域,识别查找还有修改起来会很方便。
let buttonArr = [
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
[{}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}, {}],
]
//渲染游戏区域函数
const renderDiv = () => {
document.querySelector('div').innerHTML = ''
buttonArr.forEach((item, index) => {
let ul = document.createElement('ul')
//给ul标签添加自定义属性y
ul.dataset.y = index
item.forEach((item2, index2) => {
let button = document.createElement('button')//遍历数组,绘制棋盘
//给button标签添加自定义属性x,用来作为坐标使用
button.dataset.x = index2
if (item2.num === 10) {
//给地雷元素添加一个自定义属性,便于识别
button.dataset.z = 10
//写的时候可以把地雷先渲染出来,写完了再注释掉
// button.classList.add('active')
} else {
item2.num = 0
}
ul.appendChild(button)
})
document.querySelector('div').appendChild(ul)
})
}
renderDiv()
复制代码
CSS
* {
margin: 0;
padding: 0;
box-sizing: border-box;
list-style: none;
}
div {
width: 500px;
margin: 50px auto;
padding: 5px;
border: 5px solid black;
}
ul {
display: flex;
height: 30px;
}
button {
width: 30px;
height: 30px;
background-color: #c0c0c0;
}
.bgc1 {
background-color: white;
}
.bgc2 {
background-color: black;
}
.bgc3 {
background: url(./pngsucai_1307487_8c9867.png)no-repeat;
background-color: #c0c0c0;
background-size: 100%;
}
.active {
background: url(./Snipaste_2022-06-12_16-24-48.jpg) no-repeat;
background-size: 100%;
}
p {
position: absolute;
top: 200px;
right: 200px;
font-size: 20px;
}
复制代码
<body>
<div></div>
<p>鼠标左键点击<br>
鼠标右键标记<br>
点击空白格子可快速扫雷
</p>
<script src="./扫雷.js"></script>
</body>
复制代码
2.生成地雷
一共40个地雷,通过Math方法获取随机数X和Y,把这两个数作为坐标存入数组中,数组长度为40时就去重,然后接着获取坐标,直到40个坐标没有重复,就跳出循环。地雷的数量也可以随意调整,要记得给有地雷的格子添加一种自定义属性,这样数组里面元素对应的页面标签就可以很方便的联系起来。
//生成地雷
const lei = () => {
let leiArr = []
function fn() {
//获取随机坐标
const x = Math.floor(Math.random() * 16)
const y = Math.floor(Math.random() * 16)
leiArr.push([y, x])
if (leiArr.length == 40) {
//数组去重
let obj = {}
leiArr.forEach(item => obj[item] = item)
leiArr = Object.values(obj)
}
if (leiArr.length == 40) {
return
}
fn()
}
fn()
return leiArr
}
//渲染地雷
const renderLei = (arr) => {
//把地雷对应的对象里面添加一个数据,便于识别
arr.forEach(item => {
buttonArr[item[0]][item[1]].num = 10
})
}
renderLei(lei())
renderDiv()
复制代码
3.给每一个格子添加数字,周围有几个地雷就是几,没有地雷就是空
把所有不是地雷的格子都遍历一遍,然后把每个格子周围一圈的格子都获取到,接着判断这一圈格子里面有几个地雷,当前格子的数字就是几。
获取周围一圈格子有点点复杂,获取格子的逻辑就是当前格子上下1行内,X坐标相差为1或者0的格子。中间区域的格子都是从上中下3行内的格子进行获取。第一行和最后一行的格子就只用获取两行。
因为每一个格子的默认num值都是0,格子内数字可以通过遍历周围一圈的格子,然后有地雷就给num值+1,利用遍历累加的方法,这样num值就是对应的地雷的数量了。渲染的时候就把大于0的num值显示出来就可以了。
//渲染格子数字
const renderNum = () => {
buttonArr.forEach((item, i) => {
item.forEach((item02, i02) => {
if (item02.num == 0) {
//获取不是地雷的标签
let ul = document.querySelectorAll('ul')[i]
let btn = ul.querySelectorAll('button')[i02]
//调用获取格子数字的函数,传入3个参数,当前格子对应的数组元素,当前行,和当前格子
getNum(item02, ul, btn)
//判断这个格子是否带有数字
if (item02.num < 10 && item02.num > 0) {
//给有数字的格子添加一个自定义属性,便于识别
btn.dataset.z = 1
const span = document.createElement('span')
//将数字渲染到格子中
span.innerText = item02.num
span.style.display = 'none'
btn.appendChild(span)
}
}
})
})
}
//获取格子数字
const allUl = document.querySelectorAll('ul')
let getNumArr = []
////获取格子数字函数
const getNum = (item02, ul, btn) => {
//建立一个存放目标格子周围一圈格子的数组
getNumArr = []
const y = ul.dataset.y
const x = btn.dataset.x
//调用获取格子周围一圈格子的函数,将格子的坐标传入参数
getBox(x, y)
//遍历这个格子周围一圈的格子,有地雷的话num就+1
getNumArr.forEach(item1 => {
if (item1.dataset.z == 10) {
item02.num++
}
})
}
//获取格子周围一圈格子的函数
function getBox(x, y) {
//第一排的格子只需要选中前两排
if (y == 0) {
for (let i = 0; i < 2; i++) {
//选中前两排的格子
const allButton01 = allUl[i].querySelectorAll('button')
//如果两个格子x坐标相减的绝对值小于或等于1,那么这两个格子就是相邻的
let getNumArr02 = Array.from(allButton01).filter(item => Math.abs(item.dataset.x - x) <= 1)
//加入数组
getNumArr.push(...getNumArr02)
}
}
//第二排至倒数第二排,需要选中自身上中下三排的格子
else if (y >= 1 && y < 15) {
for (let i = +y - 1; i < +y + 2; i++) {
const allButton02 = allUl[i].querySelectorAll('button')
let getNumArr02 = Array.from(allButton02).filter(item => Math.abs(item.dataset.x - x) <= 1)
getNumArr.push(...getNumArr02)
}
} else { //最后一排,选中两排的格子遍历
for (let i = 14; i < 16; i++) {
const allButton03 = allUl[i].querySelectorAll('button')
let getNumArr02 = Array.from(allButton03).filter(item => Math.abs(item.dataset.x - x) <= 1)
getNumArr.push(...getNumArr02)
}
}
}
renderNum()
复制代码
4.鼠标点击事件
数字渲染出来之后,接下来就是点击事件了。点击事件分两个,鼠标左键点击和鼠标右键插旗。
首先把地雷和数字的样式都隐藏,鼠标点击实际上就是一个添加样式的过程。
左键点击的时候,要先判断点击的是否是地雷。是地雷的话就游戏结束。不是地雷就给它添加一个CSS样式,并且让数字显示出来。
如果点击的是空白格子,就需要把这个空白格子所连接的所有非地雷格子都显示出来,就是那种点击一个显示一大片的效果。
点击的如果是数字,就是显示这一个格子。
鼠标右键就很简单了,直接添加CSS样式,起到一个插旗子的效果。但是要判断一下,以经点过的格子就不能插旗子了,只能给没点过的格子添加红旗。
//点击事件
let allArr = [] //声明一个用来判断获胜的数组
allUl.forEach(buttons => {
buttons.querySelectorAll('button').forEach(item => {
item.addEventListener('click', function () {
//点击效果
this.classList.add('bgc1')
//如果这个格子有数字,就显示
if (this.querySelector('span')) {
this.querySelector('span').style.display = 'block'
}
allArr.push(this)
//判断,如果点击的是地雷,游戏结束
if (item.dataset.z == 10) {
item.classList.add('active')
setTimeout(function () {
alert('游戏失败')
location.reload()
}, 200)
}
//只有空白的格子没有自定义的z属性,判断如果点击的是空白格子
if (!item.dataset.z) {
const num0Arr = []
num0(item)
//空白格子的周围一圈一定没有地雷,直接让这些格子显示类容
function num0(item) {
getNumArr = []
const buttons = item.parentNode
const y = buttons.dataset.y
const x = item.dataset.x
//再次调用获取周围一圈格子的函数
getBox(x, y)
getNumArr.forEach(itemBtn => {
//点击空白格,就会自动把周围一圈的格子都显示
if (itemBtn.dataset.z != 10) {
itemBtn.classList.add('bgc1')
}
if (itemBtn.querySelector('span')) {
itemBtn.querySelector('span').style.display = 'block'
}
//将这些格子都加入总数组中
allArr.push(itemBtn)
if (!itemBtn.dataset.z) {
//如果空白格周围一圈格子里面还有空白格,就将他们加入这个num0数组中,稍后再次循环一次这个点击事件
num0Arr.push(itemBtn)
}
})
}
//给空白格周围的空白格也添加一个显示类容的函数
function clickNum0() {
//num0Arr包含了点击的空白格周围9个格子内的所有空白格子,newNum0Arr就是除掉自身的所有空白格子
const newNum0Arr = num0Arr.filter(item2 => item2 != item)
newNum0Arr.forEach(item02 => {
//再次在其他空白格身上调用显示周围一圈内容的函数,这样就形成点击一个空白格子,
//如果这个格子周围空白区域很多,能显示一大片区域的效果。
num0(item02)
})
}
clickNum0()
}
//给总数组去重
const newAllArr = [...new Set(allArr)]
//筛选,排除是地雷的格子
const newAllArr02 = newAllArr.filter(item => item.dataset.z != 10)
//一共256个格子,40个地雷,如果数组长度达到216,就可以判定胜利了。
if (newAllArr02.length == 216) {
alert('游戏胜利!')
location.reload()
}
})
//鼠标右键点击事件,用来插棋子
item.addEventListener('contextmenu', function () {
if (!this.classList.contains('bgc3') && !this.classList.contains('bgc1')) {
this.classList.add('bgc3')
} else {
this.classList.remove('bgc3')
}
})
})
})
复制代码
空白格子的点击事件是这个游戏麻烦的地方。但是只要清楚一点,空白格子周围一圈是一定没有地雷的。点击空白的格子,就相当于把周围的9个格子全部都点了一遍。在这个逻辑上再去写代码,就不会很难了。
- 浏览器里面点鼠标右键,页面会弹出来菜单,我们需要把这个屏蔽掉。
//鼠标右键屏蔽菜单
document.oncontextmenu = function (event) {
if (window.event) {
event = window.event;
}
try {
var the = event.srcElement;
if (!((the.tagName == "INPUT" && the.type.toLowerCase() == "text") || the.tagName == "TEXTAREA")) {
return false;
}
return true;
} catch (e) {
return false;
}
}
复制代码