h5别踩白块游戏

本文最后更新于:November 28, 2021 am

内容介绍

1.简介

别踩白块这个游戏相信很多人都在手机上玩过,今天我们就来做一个网页版的,先上一张游戏效果图:

图片描述

属于简化版别踩白块,代码相对较为简单易学,主要涉及通过 javascript 操作元素节点的增删以及属性节点(class)的操作。

2.知识点

  • HTML/CSS
  • JavaScript
  • 元素节点增删
  • 属性节点操作

3.项目架构

1
2
3
4
puzzle
|index.html
|css/index.css
|js/index.js

项目原理

在开始编程之前,让我们先来分析下整个游戏的流程:一定的速度下移,点击黑块,黑块消失,新的黑块出现在普通游戏玩家眼中,应该是游戏开始,黑块不断向下移动,若黑块触底则游戏结束;

而以开发者来说,应将每一个黑块和白块抽象成一个个的数据结构,黑块的消失和出现其实就是数据结构的创造和销毁,我们来看一张游戏的流程图,对于要编写的功能有一个大概的了解:

image-20201105153016423

实现步骤

页面布局

可以用 div+css 布局来实现别踩白块的静态效果展示,直接上 HTML 代码,我来简要说下 HTML 思路,将主界面分解成一个 4x4 的大矩形格子,每一个方块代表其中一个小的矩形格,其中每一行的四个白块中有一个黑块,每一行中黑块位于哪一列是随机生成的,但是我们这里现在是静态页面就自己确定了,然后通过 css 控制样式。

创建 index.html 文件,并输入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>别踩白块</title>
<link rel="stylesheet" href="css/index.css" />
</head>
<body>
<div id="main">
<div id="con">
<div class="row">
<div class="cell"></div>
/*白块*/
<div class="cell black"></div>
/*黑块*/
<div class="cell"></div>
<div class="cell"></div>
</div>
<div class="row">
<div class="cell"></div>
<div class="cell black"></div>
<div class="cell"></div>
<div class="cell"></div>
</div>
<div class="row">
<div class="cell"></div>
<div class="cell"></div>
<div class="cell black"></div>
<div class="cell"></div>
</div>
<div class="row">
<div class="cell black"></div>
<div class="cell"></div>
<div class="cell"></div>
<div class="cell"></div>
</div>
</div>
</div>
</body>
<script src="js/index.js"></script>
</html>

添加样式

下面是 css 代码,这里有一个要注意的地方,我将 div#con 块级元素向上提了 100 px,这样在游戏的开始就出现了最底一行的空白,隐藏最上面那行,为什么要这样呢,继续往下看就知道了。设置黑白块的样式,为 js 部分动态插入黑白块做准备。

创建 css/index.css 文件,并输入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
#main {
width: 408px;
height: 408px;
background: white;
border: 2px solid gray;
margin: 0 auto;
overflow: hidden;
}

h2 {
text-align: center;
}

#con {
width: 100%;
height: 400px;
position: relative;
top: -408px; /*隐藏所有格子*/
border-collapse: collapse;
}
/* 行 */
.row {
height: 100px;
width: 100%;
}
/* 一个块 */
.cell {
height: 100px;
width: 100px;
float: left;
border: rgb(54, 74, 129) 1px solid;
}

.black {
background: black;
}

.btn {
width: 100%;
text-align: center;
}

.start {
margin: 20px auto;
width: 150px;
height: 50px;
border-radius: 10px;
background: yellowgreen;
line-height: 50px;
color: #fff;
}

如果以上部分你都能够理解并且对应着代码实现的话,那么我们看到的应该是一个空格子,我们设置 id 为 con 的 div 的 top 属性为 0 px: #con {top: 0px;},这样我们就可以看见:

图片描述

是不是很像别踩白块的界面了呢,我们已经成功了一大步,然后就是通过 js 来实现动态的插入黑块或白块,以及操作。

游戏初始化

根据前面的 HTML 部分我们可以知道,每个 <div class="cell"> 就代表一个白块,<div class="cell black"> 就代表一个黑块,每点击一个黑块消失其实是删除了一个 <div class="row"> 然后从上面添加一个新的 <div class="row"> 所以我们首先要通过 js 来控制 <div class="row"> 的创造和生成(记得删除在编写静态页面时候指定生成的 4 个 div.row)。具体方法如下:

删除之前写的 4 个 div.row,将 index.html 文件改为以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>别踩白块</title>
<link rel="stylesheet" href="css/index.css" />
</head>
<body>
<h2>score</h2>
<h2 id="score">0</h2>
<div id="main">
<div id="con"></div>
</div>
<div class="btn">
<button class="start" onclick="start()">
开始游戏
</button>
</div>
</body>
<script src="js/index.js"></script>
</html>

对游戏进行初始化,在 js/index.js 文件中写入以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// 创建div, className是其类名
function creatediv(className) {
var div = document.createElement('div');
div.className = className;
return div;
}

// 创造一个<div class="row">并且有四个子节点<div class="cell">
function createrow() {
var con = $('con');
var row = creatediv('row'); //创建div className=row
var arr = creatcell(); //定义div cell的类名,其中一个为cell black

con.appendChild(row); // 添加row为con的子节点

for (var i = 0; i < 4; i++) {
row.appendChild(creatediv(arr[i])); //添加row的子节点 cell
}

if (con.firstChild == null) {
con.appendChild(row);
} else {
con.insertBefore(row, con.firstChild);
}
}

//删除div#con的子节点中最后那个<div class="row">
function delrow() {
var con = $('con');
if (con.childNodes.length == 6) {
con.removeChild(con.lastChild);
}
}

// 创建一个类名的数组,其中一个为cell black, 其余为cell
function creatcell() {
var temp = ['cell', 'cell', 'cell', 'cell'];
var i = Math.floor(Math.random() * 4); //随机产生黑块的位置 Math.random()函数参数 0~1的随机数
temp[i] = 'cell black';
return temp;
}

让黑块动起来

在可以通过 js 来创造和销毁 div 后,我们就要让黑块动起来,这个时候我们就用到了之前 css 提到的设定 <div id="con"> 隐藏了一行的 <div id="row">,我们通过 js 的 DOM 操作使其向下方移动,并设置定时器每 30 毫秒移动一次,这样就实现了黑块的平滑移动,在黑块移动的同时,我们要判断黑块是否已经触底,触底则游戏失败,停止调用 move(),触底后调用函数 fail() 游戏失败,具体方法如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//让黑块动起来
function move() {
var con = $('con');
var top = parseInt(window.getComputedStyle(con, null)['top']);

if (speed + top > 0) {
top = 0;
} else {
top += speed;
}
con.style.top = top + 'px'; //不断移动top值,使它动起来
over();
if (top == 0) {
createrow();
con.style.top = '-102px';
delrow();
}
}

// 加速函数
function speedup() {
speed += 2;
if (speed == 20) {
alert('你超神了');
}
}

// 判断游戏是否结束

function over() {
var rows = con.childNodes;
if (rows.length == 5 && rows[rows.length - 1].pass !== 1) {
fail();
}
for (let i = 0; i < rows.length; i++) {
if (rows[i].pass1 == 1) {
fail();
}
}
}

// 游戏结束
function fail() {
clearInterval(clock);
flag = false;
confirm('你的最终得分为 ' + parseInt($('score').innerHTML));
var con = $('con');
con.innerHTML = '';
$('score').innerHTML = 0;
con.style.top = '-408px';
}

点击黑块事件

让黑块动起来之后呢,我们就来考虑怎么判断用户有没有点击到黑块呢,同时用户若点击到黑块,我们要让所在那一行消失,那么我们需要一个 judge 方法,具体如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 判断是否点击黑块、白块
function judge(ev) {
if (
ev.target.className.indexOf('black') == -1 &&
ev.target.className.indexOf('cell') !== -1
) {
ev.target.parentNode.pass1 = 1; //定义属性pass,表示此行row的白块已经被点击
}

if (ev.target.className.indexOf('black') !== -1) {
//点击目标元素 类名中包含 black 说明是黑块
ev.target.className = 'cell';
ev.target.parentNode.pass = 1; //定义属性pass,表明此行row的黑块已经被点击
score();
}
}

其实程序写到这里,几个核心的功能点都已经实现了,是不是感觉很简单呢。剩下来的就是将这些方法组合起来,组成完整的逻辑关系,在我给出的源码里添加有一个记分器记录用户分数的功能,同时设置加速方法,使黑块的移动越来越快等等。有兴趣的的同学可以尝试着添加事件按钮,使这个游戏更接近 APP 版本。

js 完整代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
// 工具封装
// 根据id来获取元素
function $(id) {
return document.getElementById(id);
}

// 创建div, className是其类名
function creatediv(className) {
var div = document.createElement('div');
div.className = className;
return div;
}
var clock = null;
var state = 0;
var speed = 6;
var flag = false;

//点击开始游戏按钮 开始游戏
function start() {
if (!flag) {
init();
} else {
alert('游戏已经开始,无须再次点击!');
}
}

/*
* 初始化 init
*/
function init() {
flag = true;
for (var i = 0; i < 4; i++) {
createrow();
}

// 添加onclick事件
$('main').onclick = function (ev) {
ev = ev || event;
judge(ev);
};

// 定时器 每30毫秒调用一次move()
clock = window.setInterval('move()', 30);
}

// 判断是否点击黑块、白块
function judge(ev) {
if (
ev.target.className.indexOf('black') == -1 &&
ev.target.className.indexOf('cell') !== -1
) {
ev.target.parentNode.pass1 = 1; //定义属性pass,表示此行row的白块已经被点击
}

if (ev.target.className.indexOf('black') !== -1) {
//点击目标元素 类名中包含 black 说明是黑块
ev.target.className = 'cell';
ev.target.parentNode.pass = 1; //定义属性pass,表明此行row的黑块已经被点击
score();
}
}

// 判断游戏是否结束

function over() {
var rows = con.childNodes;
if (rows.length == 5 && rows[rows.length - 1].pass !== 1) {
fail();
}
for (let i = 0; i < rows.length; i++) {
if (rows[i].pass1 == 1) {
fail();
}
}
}

// 游戏结束
function fail() {
clearInterval(clock);
flag = false;
confirm('你的最终得分为 ' + parseInt($('score').innerHTML));
var con = $('con');
con.innerHTML = '';
$('score').innerHTML = 0;
con.style.top = '-408px';
}

// 创造一个<div class="row">并且有四个子节点<div class="cell">
function createrow() {
var con = $('con');
var row = creatediv('row'); //创建div className=row
var arr = creatcell(); //定义div cell的类名,其中一个为cell black

con.appendChild(row); // 添加row为con的子节点

for (var i = 0; i < 4; i++) {
row.appendChild(creatediv(arr[i])); //添加row的子节点 cell
}

if (con.firstChild == null) {
con.appendChild(row);
} else {
con.insertBefore(row, con.firstChild);
}
}

// 创建一个类名的数组,其中一个为cell black, 其余为cell
function creatcell() {
var temp = ['cell', 'cell', 'cell', 'cell'];
var i = Math.floor(Math.random() * 4); //随机产生黑块的位置 Math.random()函数参数 0~1的随机数
temp[i] = 'cell black';
return temp;
}

//让黑块动起来
function move() {
var con = $('con');
var top = parseInt(window.getComputedStyle(con, null)['top']);

if (speed + top > 0) {
top = 0;
} else {
top += speed;
}
con.style.top = top + 'px'; //不断移动top值,使它动起来
over();
if (top == 0) {
createrow();
con.style.top = '-102px';
delrow();
}
}

// 加速函数
function speedup() {
speed += 2;
if (speed == 20) {
alert('你超神了');
}
}

//删除某行
function delrow() {
var con = $('con');
if (con.childNodes.length == 6) {
con.removeChild(con.lastChild);
}
}

// 记分
function score() {
var newscore = parseInt($('score').innerHTML) + 1; //分数加一
$('score').innerHTML = newscore; //修改分数
if (newscore % 10 == 0) {
//当分数是10 的倍数时使用加速函数,越来越快
speedup();
}
}

总结

仅仅一百多行代码我们就实现了一个简单有趣的 Web 小游戏。如果你刚入门前端,相信本项目可以很好的让你理解,练习如何使用 JavaScript 操作 DOM.