Demo介绍
纯原生js 实现 且用ES6语法class写的扫雷小游戏:布局为10*10的网格,随机生成10-20个雷。
左键点击扫雷。右键标记该地方有雷。该demo下载下来复制到html文件中直接可以使用。不需要下载任何插件和引入任何框架。
小游戏源代码
Document* {
padding: 0;
margin: 0;
}
.container {
width: 500px;
height: 500px;
background: rgb(150, 130, 130);
position: relative;
border: 5px solid #000;
margin: 0 auto;
}
class mineSweeping {
constructor() {
let container = this.$$(".container")
this.container = container;
this.mineWidth = 50;
this.mineHeight = 50;
this.mineNum =
(this.container.clientWidth * this.container.clientHeight) /
(this.mineWidth * this.mineHeight);
this.brr = [-11, -10, -9, -1, 1, 9, 10, 11];//
this.mineColNum = this.container.clientWidth / this.mineWidth;
this.createDiv()
this.createMine()
this.setNum()
}
// 创建div;
createDiv() {
for (let i = 0; i < this.mineNum; i++) {
let div = document.createElement("div");
// 添加样式
Object.assign(div.style, {
width: this.mineWidth - 2 + "px",
height: this.mineHeight - 2 + "px",
backgroundColor: "#ccc",
border: "1px solid #fff",
position: "absolute",
left: (i % this.mineColNum) * this.mineWidth + "px",
top: parseInt(i / this.mineColNum) * this.mineHeight + "px",
textAlign: "center",
lineHeight: "50px",
userSelect: "none"
});
// 添加自定义属性
div.mine = false // 是否为雷
div.show = false // 是否被点开
// 添加索引
div.index = i
// 添加坐标
div.pos = {
x: parseInt(i / this.mineColNum) + 1,
y: i % this.mineColNum + 1,
}
// 绑定事件
div.onclick = this.clickFn.bind(this)//左键事件
div.oncontextmenu = this.contextmenuFn//右键事件
this.container.appendChild(div);//追加小盒子
}
}
// 随机生成几个地雷
createMine() {
this.divs = this.container.children
let arr = []
for (let i = 0; i < this.getRandom(10, 20); i++) {
let x = this.getRandom(1, 11)
let y = this.getRandom(1, 11)
//随机获得10-20个得并随机放置,将左边存进数组
arr.push({
x, y
})
}
for (let j = 0; j < arr.length; j++) {
Array.from(this.divs).forEach(v => {
// console.log(v.pos);
if (arr[j].x === v.pos.x && arr[j].y === v.pos.y) {
v.mine = true
}
})
}
}
// 计算数字
setNum() {
for (let i = 0; i < this.divs.length; i++) {
if (this.divs[i].mine) {
continue;
}
let num = 0;
for (var j = 0; j < this.brr.length; j++) {
// 考虑最上面一行 - 不能-11 不能-10 不能-9
if (i < this.mineColNum && (this.brr[j] === -11 || this.brr[j] === -10 || this.brr[j] === -9)) {
continue;
}
// // 最左边一行过滤掉
if (i % this.mineColNum === 0 && (this.brr[j] === -11 || this.brr[j] === -1 || this.brr[j] === 9)) {
continue;
}
// 最下面一行
if (parseInt(i / this.mineColNum) === this.mineColNum - 1 && (this.brr[j] === 9 || this.brr[j] === 10 || this.brr[j] === 11)) {
continue;
}
// 最右边一行
if (i % this.mineColNum === this.mineColNum - 1 && (this.brr[j] === -9 || this.brr[j] === 1 || this.brr[j] === 11)) {
continue;
}
// console.log(this.divs[i + this.brr[j]].mine);
if (this.divs[i + this.brr[j]].mine) {
num++
// console.log(num);
}
}
// 把数字存起来
this.divs[i].num = num
}
}
//左键点击事件
clickFn(e) {
e = e || window.event;
let target = e.target || e.srcElement;
let divs = target.parentNode.children
if (target.mine) {//这里注意 要加target
for (let i = 0; i < divs.length; i++) {
divs[i].innerText = '';
divs[i].innerText = divs[i].num ? divs[i].num : ''
divs[i].style.backgroundColor = '#0f0'
}
for (let i = 0; i < divs.length; i++) {
if (divs[i].mine) {
divs[i].style.backgroundColor = 'red';
divs[i].innerText = '雷';
}
}
} else {
this.openAround(target.index)
}
}
//右键点击事件标记雷
contextmenuFn() {
this.style.backgroundColor = 'blue'
return false // 阻止默认事件
}
// 自动递归打开周围空格子
openAround(index) {
this.container.children[index].innerText = this.container.children[index].num
this.container.children[index].style.backgroundColor = '#0f0'
this.container.children[index].show = true
if (this.container.children[index].num === 0) {
this.container.children[index].style.backgroundColor = 'pink';
for (let j = 0; j < this.brr.length; j++) {
if (index < this.mineColNum && (this.brr[j] === -11 || this.brr[j] === -10 || this.brr[j] === -9)) {
continue;
}
// // 最左边一行过滤掉
if (index % this.mineColNum === 0 && (this.brr[j] === -11 || this.brr[j] === -1 || this.brr[j] === 9)) {
continue;
}
// 最下面一行
if (parseInt(index / this.mineColNum) === this.mineColNum - 1 && (this.brr[j] === 9 || this.brr[j] === 10 || this.brr[j] === 11)) {
continue;
}
// 最右边一行
if (index % this.mineColNum === this.mineColNum - 1 && (this.brr[j] === -9 || this.brr[j] === 1 || this.brr[j] === 11)) {
continue;
}
if (this.container.children[index + this.brr[j]].show) {
continue
}
this.openAround(index + this.brr[j])
}
}
}
// 获取随机数
getRandom(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}
//获取节点
$$(tag, all = false) {
return all ? document.querySelectorAll(tag) : document.querySelector(tag);
}
};
new mineSweeping;
3.Demo代码分析
1.定义位置。动态创建div小格子。将格子动态设置样式。给盒子设置自定义属性mine属性判断是否是地雷属性。show则判断是否被打开
2. 给盒子坐标编号。同时根据坐标值随机生成10-20个地雷在随机位置。
3. 添加点击事件。左键点击扫雷。右键点击标记。同时追加盒子在画布container上
4. 给每个盒子计算数字。并且设置边缘检测。每个盒子8个位置 上 上右,上左,下,下右,下左的位置设置成一个brr[I]数组。周围有地雷。则数值加加。
5. 点击左键事件:点击盒子,点击后显示数字且变色。吧show属性改为true。如果是雷有mine属性其为true.直接打开所有盒子并游戏结束。否则调用展开方法openaround()
6. 展开方法其实是一个递归函数。通过检查是否是空是则继续检查四周几个位置是否为空。直到碰到周边有雷的位置。递归终止。这里要注意。展开的递归函数是要排除自己的,如果比如你向右递归。但改盒子的左边又是上一个递归的本身。那就会出现死循环。报错。
4.运行截图
5.还需要改进或者可与完善的点
没有游戏结束中止和计分设置。可以在循环判断是否触雷那里改写成promise。then,catch方法则判定游戏获胜或结束。
可与将展开函数进行改造。改成一种DFC(图中的深度优先算法)
边界检测调用了几次,可以将其封装起来
周围值数组值可以未来根据盒子实际大小动态生成。
6.总结
写这种demo虽然工作中完全用不了,但对于js原生写法的和逻辑的锻炼以及js初学者有所帮助。