javascript 贪吃蛇(三)【附带源码】

      在javascript 贪吃蛇(一)及javascript 贪吃蛇(二)中介绍了关于贪吃蛇的实现。其实整个编写的过程并非一开始就是像前面展示的代码那样具有面向对象的特征。在完成这个小游戏的过程中还是遇到了一些问题,在这里就谈谈自己的一些编程体会。

编程思想:

      编写最开始的想法其实很简单就是能在页面上显示一个canvas和一条自动游走的snake。因此最开始的时候其实是花了一部分时间在如何构建一个canvas上。在网上查询了下,有生成canvas相关的js包,但是没决定用,因为写这个游戏的目的是为了把自己刚刚学到的一部分js付诸实践的,而不是拿别人的源码来模仿,因为很可能最后的模仿就变成copy了。当然也google到了一个用html5实现的,但是就目前来说html5只有在chrome和firefox上才能运转,因此也放弃了使用html5的canvas元素实现snake运动的想法。

      在用tabel还是div+css实现canvas的选择上,最后选择了div+css,原因很简单div+css也需要加强熟悉!

      snake的实现可以说是一步一步堆积起来。一开始在实现sanke的时候并未想到要去设置蛇头有蛇尾,只是一个很单纯的想法:先让canvas上能出现一条snake。那么很自然的想到通过区分snake的背景与canvas背景色的方式区分snake。因此用到的代码就只有document.getElementById('id').style.background=oColor;接下来想到的才是如何标记snake的整个身体。有时将canvas分成相同的方格,因此在生成这些有span组成的方格时,每个方格是具有不同的ID唯一标识的。那么snake所占的格子就可以使用这些span的唯一标识ID来确定。那么snake其实就是一个数组,而数组的每个元素其实就是canvas上span的ID编号。这样一来,那么snake的head跟tail就很容易定义了。head是数组的最后一个元素;tail是数组的第一元素。

      如何定义snake的移动呢?刚才不是说了snake其实就是一个数组。那么当snake移动时,snake的整体长度是不变的,也就是数组的长度不变。唯一改变的是tail跟head值的变化。根据canvas格子的定义,那么水平移动的snake,下一次head的位置肯定是当前head加一或是减一;而上下移动时,由于canvas是20×20那么head的位置应该是加减二十;tail就很好定义了,由于snake的总长度不变,那么只需要把表示snake身体的数组的首个元素从数组中移除即可。移动算法如下:

  • 当前snake's body = Array[1,...,n]   head = Array[n]   tail = Array[1] (n代表数组元素位置)
  • 水平移动后的 body = Array[2,...,n+1]  head = Array[n]+/-1  tail = Array[2] (这里的n=2是相对于移动前的n=1来说的)
  • 上下移动后的 body = Array[2,...,n+1]  head = Array[n]+/-20 tail = Array[2](这里的n=2是相对于移动前的n=1来说的)

这样一来就可以表示snake的移动了。

      实现了上述的算法后,使用setTimeout不停的去调用实现该算法的方法(方法参数为snake的运动方向)。但这个时候会发现snake会穿透canvas的边缘,那么这个是不符合游戏规则的。这个时候开始考虑snake的撞墙事件。通过观察生成的canvas的span的编号可以容易发现canvas的边界,其实canvas四个边界也就是四个保存了span id的数组。因此撞墙的条件就是:

  • 上下运动时:snake'head的值不能小于1或是大于400
  • 水平运动时:如果snake向左运动,那么运动后的head的位置不应该属于canvas右边的墙,并且snake身体的第二节(数组的倒数第二个元素)不应该属于cavans的左墙;向右运动的话刚好相反,head不属于左墙并且第二节不属于右墙

至此snake的基本算法就算已经完成。

      接下来就是增加键盘方向键的监听事件了,否则snake一碰到canvas的边缘游戏就结束了。在这个过程中其实有两种选择,一种就是直接为button元素添加onkeypress事件,另一种就是使用DOM的addEventListener;最后选择直接onkeypress的方式处理,也就前面代码window.onload中的代码。

      为了增加点趣味性,开始为snake生成food。开始做的时候food很简单,就是单一的。food生成规则:

  • 随机生成1-400的一个整数
  • 当这个数属于snake'body时重新生成

这样的话snake的移动也就要改变了。游戏规则是当snake遇到food时,身体需要加长,那么我们只需要判断当snake'head的下一个位置等于food的位置时,就把该food添加到数组中,而snake'tail不用从body中移除即可。这样就模拟实现了snake eat food的行为。后面在添加了一些彩头,实现食物的等级,使得snake在吃掉不同的food后有不同的积分。

      最后就是增加一些记分的历史记录的装饰了。(这部分没有重点做,有兴趣可以改造下)

一些有趣的地方:

首先是js中的null和undefined有如下测试:

function test1(){
var param;// 定义但未赋值
alert(param == undefined);//back true
alert(typeof param);//back "undefined"
alert(typeof param == "undefined");//back true
}
function test2(){
alert(param == undefined);// erro
alert(typeof param == "undefined");//true
}
function test3(){
var param = null;
alert(param == undefined) ;// back true
alert(typeof param);//object
alert(typeof param =="undefined");//false
}
function test4(){
var param = undefined;
alert(param == undefined) ;// back true
alert(typeof param);//back "undefined"
alert(typeof param =="undefined");//false
}
从中可以得出:

  1. typeof null 是对象,也就是null对象
  2. null == underfined返回真,但 null是对象
  3. 声明但未初始的变量为underfined,typeof 是"underfined"
  4. 未声明的变量不是underfined,但typeof 是"underfined"
  5. 初始为underfined的变量,==underfined,typeof 为"underfined"

js中this与全局变量及对象冒充。先看如下代码:

SnakeGame.prototype.__delay = function(obj, fn, time) {
	fnGameDelay = function() {
		fn.call(obj);// call方法会把fn方法中的this关键字替换成obj对象
		// apply方法同call,但参数要用数组形式。IE中参数为空时不能用apply(obj,null),但FF中是可以的
		// fn.apply(obj, new Array());
	};
	return setTimeout('fnGameDelay()', time);
};
大家可能看了觉得复杂,干嘛不在SnakeGame的start方法中直接调用setTimeout("this.start()",oTime)呢?这里涉及到一个问题就是this是对象内部的而不是全局的,而setTimeout调用的方法却必须是全局属性的。因此直接用"this.start()"时会提示找不到this.start()方法。因此这里需要使用对象冒充( 参看:ECMAScript继承机制中的对象冒充及call()方法)

还需要优化的地方

  • 游戏开始时的snake的随机生成不好。目前初次运动方向没有随机,snake的初始身体长度没有随机
  • 得分算法不够好
  • 排行榜很简陋。目前只是用cookie很简单的实现。后续应该改成持久化文件存储
  • 可以考虑添加音乐效果及其他效果

源代码代码可以点击这里下载snake.zip(解压密码:blog.csdn.net/oxcow)

《完》

你可能感兴趣的:(javascript 贪吃蛇(三)【附带源码】)