HTML+CSS+JS实现的贪吃蛇游戏<一>

  最近都在学习js,学习间隙总想拿点项目练习,而贪吃蛇作为一个简单的小游戏正适合。相信大家都玩过贪吃蛇,我记得我第一次看到这个游戏是在小学的时候。当时家里买了个新手机,黑白的屏幕,点开游戏映入眼帘的就是贪吃蛇。虽然当时手机屏幕还是那么的小,分辨率还是那么低,但是这个游戏还是让我不亦乐乎,好了扯远了,下面我们就用js实现一下这个游戏吧!

  首先,作为一名初学者,我搜索了一下网络上现有的资料,然后看到了一段相关的代码,虽然时间已经很久了,但是我试玩了一下,觉得界面很清新,很喜欢,所以就决定按此来借鉴学习了。
  他的代码是没有分开的,所有的代码都写在了一个文件里。我在后面会把它分开来,然后,他的代码是没有按面向对象来写的,我也会把它改进。好了下面开始正式讲解。
  我们先理一理游戏的逻辑:

  1. 建立一个游戏的地图
  2. 绘出蛇和它的食物
  3. 让蛇动起来
  4. 增加一些判定机制
  5. 增加一些其他娱乐元素

第一步

  要建立一个游戏的地图。此处的前提是有一个html页面,我们的重点不是如何编写html和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
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>贪吃蛇</title>
<link rel="stylesheet" href="snake.css" /><!--此处为css文件,使用时注意文件名和位置,下面的是js文件,同样要注意-->
<script type="text/javascript" src="snake_class.js" ></script>
</head>
<body>
<div id="say">贪吃蛇</div>
<span class="best">最高分:<strong id="best_score">0</strong></span><br />
<span style="float: right;">方向键:控制方向,空格:暂停/开始</span>
<div id="snakeWrap"></div>
<div id="help">
<span class="box food"></span><span>绿色加分</span>
<span class="box block"></span><span>灰色路障</span>
<span class="box skate"></span><span>蓝色加速</span>
<span class="box brake"></span><span>红色减速</span>
<span style="float:right">目前得分:<strong id="score">0</strong></span>
<input type="button" id="btnStart" value="开始游戏" />
<div id="size">
<p style="width: 150px;color: white;float: left;">请选择游戏地图大小:</p><br />
<form style="width: 200px;float: left;">
<input type="radio" name="size" value="20" checked="true"><span style="float: none;">20*20</span></input>
<input type="radio" name="size" value="40"><span style="float: none;">40*20</span></input>
<input type="radio" name="size" value="60"><span style="float: none;">60*20</span></input>
</form>
</div>
</div>
</body>
</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
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
* {
padding: 0px;
margin: 0px;
}
body {
background: #333;
-moz-user-select: none;
font-size: 12px;
text-align: center;
}
table {
margin: 20px auto 10px auto;
overflow: hidden;
border-collapse: collapse;
}
td {
width: 20px;
height: 20px;
border: 1px solid #ddd;
background: #f4f4f4;
}
.cover {
background: #39c;
}
.food {
background: #093;
}
.block {
background: #333;
}
.brake {
background: #f00;
}
.skate {
background: #00f;
}
#help span {
float: left;
margin-right: 10px;
}
#help .box {
width: 15px;
height: 15px;
margin-right: 5px;
border: 1px solid white;
}
#help {
width: 420px;
margin: 0 auto;
line-height: 17px;
}
span{
color: white;
}
#say {
margin-top: 20px;
margin-bottom: 20px;
color: white;
font-size: 14px;
}
#snakeWrap {
margin-top: 0px;
}
#btnStart {
clear: both;
width: 100px;
height: 30px;
margin-top: 10px;
margin-right: 50px;
padding: 0;
background: #bbb;
color: #222;
border: 1px solid #fff;
/*边界亮化*/
border-bottom-color: #000;
border-right-color: #000;
cursor: pointer;
float: right;
}
.best{
font-size: 20px;
margin: 40px auto 10px auto;
}
#size{
margin-top: 10px;
width: 200px;
height: 50px;
float: left;
margin-left: 20px;
}

  上面的代码分别复制、黏贴后就可以生成一个基本的网页了(注意我在html中的注释,当心css文件的文件名和位置),但是,我们可以发现里面没有我们上面提到的任何一个逻辑,那就对了,剩下的所有逻辑我们都要通过js文件来完成,从此处我们也可以看到js在前端技术中是多么的强大。
  现在我们还在讨论第一步,好的我们继续。游戏的地图也就是一些大小相同的方格,一个个的生成肯定是不实际的,因为这样很难管理,如此规则的矩形阵列怎么规模生成呢?表格对不对!?是的,表格除了一般用来放文字之外其他特征都很像,那么我们只要不放入文字并设置好大小不就行了?好的,接下来就可以做了。还有我们要注意一点,我们表格的位置也选好了,就是html中id是snakeWrap的模块。   

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
this.initGrid = function(target){
if (this.table) {
$(target).removeChild(this.table);
}
this.table = document.createElement("table");
var tbody = document.createElement("tbody");
for (var i = 0; i < this.HEIGHT; i++) {
var row = document.createElement("tr");
for (var j = 0; j < this.WIDTH; j++) {
var col = document.createElement("td");
row.appendChild(col);
this.gridElems[i][j] = col;
}
tbody.appendChild(row);
}
this.table.appendChild(tbody);
$(target).appendChild(this.table);
}

  上述就是具体代码,其中的this是最后类封装是加上的,现在你可以忽略,把他们都理解为全局的函数或者变量。其中的主要思想就是:利用DOM技术,按表格标签的结构生成表格结点,再绑定到id为target的元素上,也就是目标位置。其中开头的代码是在已有表格时限删除现有表格的,此时不用太注意。我们需要注意一下gridElems[i][j],这是后续控制的关键,这是一个二维数组,下面会给出代码,还有一个$运算符,是一个简化代码的函数,一起给出,看一下就明白了。

1
2
3
4
5
6
7
8
9
10
11
function multiArray(m, n) { //创建m*n的二维数组,我在此理解为矩阵
var arr = new Array(m);
for (var i = 0; i < m; i++) {
arr[i] = new Array(n);
}
return arr;
}
function $(id){
return document.getElementById(id);
}

第二步

  我们需要在创建好的地图上绘出基本的游戏元素。玩过的都知道,蛇我们可以简化为一系列连续的色块,而食物我们可以用一个其他颜色的色块代替。我们这里有一个问题,如何控制颜色?首先,有一个基本的思想,就是我们通过设置上述表格的className属性来简化问题,这样只要我们在css中事先设计好颜色就好了。还有一个关键点,就是上面我们刚提到的gridElems,它连接到每个表格元素,所有的设置都要靠它。

1
2
3
4
5
6
7
8
9
10
11
12
13
this.initSnake = function (){
this.talk("贪吃蛇游戏开始啦~");
$("score").innerHTML = this.score;
this.snake_pos = new Array();
var snake_head_dot = randdot(this.carrier, 0, this.len - 1, this.HEIGHT, Math.floor(WIDTH/2));
for (var i = 0; i < this.len; i++) {
var x = snake_head_dot[0];
var y = snake_head_dot[1] - i;
this.snake_pos.push([x, y]);
this.carrier[x][y] = "cover";
this.gridElems[x][y].className = "cover";
}
}

  上述代码也经过了封装,有关this的都是类的属性或者方法,现在只要简单理解为一些函数或者变量就可以了。我们的重点从创建一个Array开始,其用来存蛇的身体每点的坐标,randdot是一个用来生产随机点的函数,下面会给出。carrier也是二维数组,来记录地图的使用情况,其一般与gridElems联动。最后一行就是对表格的控制。最前面的两行是一些交互,现在不用理解。

1
2
3
4
5
6
7
8
9
10
11
12
13
function randdot(carrier, startX, startY, endX, endY){
startX = startX || 0;
startY = startY || 0;
endX = endX || this.HEIGHT;
endY = endY || this.WIDTH;
var x = Math.floor(Math.random() * (endX - startX)) + startX;
var y = Math.floor(Math.random() * (endY - startY)) + startY;
var dot = [x, y];
if (carrier[x][y]) {
return randdot(carrier, startX, startY, endX, endY);
}
return dot;
}

  这个函数需要传入carrier来保证不会重复利用,而其他的参数就是起始、结束位置。代码的前几行类似默认参数设置,我试过了直接写在形参的位置,但是chrome好像不认,所以还是按照原代码写了。添加食物我们可以结合后续要添加的各种要素统一创建一个函数,但是基本思想和上面是一样的。

1
2
3
4
5
this.addObject = function(type){
var pos = randdot(this.carrier);
this.carrier[pos[0]][pos[1]] = type;
this.gridElems[pos[0]][pos[1]].className = type;
}

  可见我们用了type来指代要添加的名称,此名称和css相关控制显示,然后其他的就和上面的一样了,联动的修改carrier和gridElems。到这里第二点的逻辑就实现了。
  好了,今天先讲到这里。

版权:本文采用以下协议进行授权,自由转载 - 非商用 - 非衍生 - 保持署名 | Creative Commons BY-NC-ND 3.0,转载请注明作者及出处。