JavaScript简单贪吃蛇,基本面向对象

高洛峰
高洛峰 原创
2016-11-25 11:46:40 869浏览

没有写博客的习惯,这篇算心血来潮,算篇近几天编写的小程序纪实.

以编写此程序的方式结束Javascript的本阶段的学习.编写的目的在于熟悉javascript的编程方式,包括代码风格,面向对象的运用等.

回到程序,说说Snake的移动的实现方法.其实很简单,向头部添加Unit,然后删除尾部.其他,参见注释.

程序包括一个html文件:snake.html和一个js文件:snake.js

snake.html:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<html xmlns="http://www.w3.org/1999/xhtml">

<head>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />

<title>JavaScript简单贪吃蛇</title>

<script type="text/javascript" src="//m.sbmmt.com/m/faq/snake.js"></script>

<script type="text/javascript" >

$s(function(){

$s.SnakeContext.init();

});

</script>

</head>

<body>

<div id="headLocation"></div>

<div id="keyup"></div>

</body>

</html>

snake.js:

/*

* JavaScript简单贪吃蛇.基本面向对象.

* 规则:

* 1.没有墙,左与右连接,上与下连接.

* 2.当蛇头碰撞到自身时死亡.

* 兼容性:

* 完全支持Firefox,Chrome

* 基本支持IE(除了调试部分)

*

* 作者:pcenshao

* 转载请注明来自:

* http://blog.csdn.net/pywepe

* http://pcenshao.taobao.com

*/

(function(){

$s = function(){

if(arguments.length == 1){

if(typeof arguments[0] == "string"){

return document.getElementById(arguments[0]);

}else if(typeof arguments[0] == "function"){

window.onload = arguments[0];

}

}

};

$s.UNIT_WIDTH = 10; // 单元的宽度

$s.UNIT_HEIGHT = 10;

$s.PANEL_WIDTH = 30; // 逻辑宽度

$s.PANEL_HEIGHT = 20; // 逻辑高度

$s.STEP = 250 ; // 每一步的时间

$s.HEAD_COLOR = "red"; // 蛇头颜色

$s.BODY_COLOR = "black"; // 蛇体颜色

/*

* 食物的颜色

*/

$s.COLORS = ["blue","green","#494e8f","#905d1d","#845538","#77ac98","#8552a1"];

/*

* 调试相关

* $s.DEBUG 调试信息显示开关

* $s.KEY_UP_DIR_ID 监视方向键的结点id,若不存在,则不显示

* $s.HEAD_LOCATION_ID 监视蛇头位置的结点id,若不存在,则不显示

*/

$s.DEBUG = false;

$s.KEY_UP_DIR_ID = "keyup";

$s.HEAD_LOCATION_ID = "headLocation";

$s.Dir = { // 代表方向,强制以$s.Dir.UP方法调用,避免参数错误

UP : {},

DOWN : {},

LEFT : {},

RIGHT : {},

NONE : {}

};

$s.State = { // 代表状态

STOP : {},

RUNNGIN : {},

PAUSE : {}

};

$s.Unit = function(){ // 一个单元格,用MVC的眼光看,Unit是模型,UnitView是视图

this.x = 0;

this.y = 0;

this.view = new $s.UnitView();

this.view.unit = this;

this.color = $s.BODY_COLOR;

};

$s.Unit.prototype.repaint = function(){

if(this.view != null){

this.view.repaint(); // 通知重绘

}

};

$s.Snake = function(){

this.units = [];

};

$s.Snake.prototype.init = function(dir,count){

var x = 5;

var y = 5;

for(var i = 0 ; i < count ; i ++){

var u = new $s.Unit();

u.x = x ;

u.y = y ++;

this.units.push(u);

if(i == (count - 1 )){

u.color = $s.HEAD_COLOR;

}

u.repaint();

}

};

$s.Snake.prototype.crash = function(x,y){ // 传入头部的位置,返回true表示碰撞自身

for(var i = this.units.length - 2 ; i >= 0 ; i --){ // 不包括头自身

var u = this.units[i];

if(u.x == x && u.y == y){

return true;

}

}

return false;

};

$s.Snake.prototype.go = function(){

// 判断前方是否有食物

// 是否撞墙

var _x = 0 , _y = 0;

var head = this.units[this.units.length - 1];

_x = head.x;

_y = head.y;

var dir = $s.SnakeContext.dir;

if(this.crash(_x,_y)){ // 判断是否碰撞到自身

$s.SnakeContext.stop();

$s.SnakeContext.ondead(); // 触发dead事件

return;

}

if(dir == $s.Dir.LEFT){

_x --;

}else if(dir == $s.Dir.RIGHT){

_x ++;

}else if(dir == $s.Dir.UP){

_y --;

}else if(dir == $s.Dir.DOWN){

_y ++;

}

// 实现左右连接,上下连接

if(_x >= $s.PANEL_WIDTH){

_x = 0;

}

if(_x < 0){

_x = $s.PANEL_WIDTH - 1;

}

if(_y >= $s.PANEL_HEIGHT){

_y = 0;

}

if(_y < 0){

_y = $s.PANEL_HEIGHT - 1;

}

var h = new $s.Unit(); // 新头

if($s.SnakeContext.hasFood(_x,_y)){ // 下一步碰到食物

this.eat(_x,_y);

head = this.units[this.units.length - 1]; // 因为eat方法可以改变头部,所以重新获取

_x = head.x;

_y = head.y;

if(dir == $s.Dir.LEFT){

_x --;

}else if(dir == $s.Dir.RIGHT){

_x ++;

}else if(dir == $s.Dir.UP){

_y --;

}else if(dir == $s.Dir.DOWN){

_y ++;

}

head.color = $s.HEAD_COLOR;

head.repaint();

var oldHead = this.units[this.units.length - 2];

oldHead.color = $s.BODY_COLOR;

oldHead.repaint();

return;

}

var tail = this.units.shift();

$s.NodePool.releaseNode(tail);

h.x = _x;

h.y = _y;

this.units.push(h);

for(var i = this.units.length - 1; i >= 0; i --){

var u = this.units[i];

if(i == (this.units.length - 1)){ // 头

u.color = $s.HEAD_COLOR;

}else{

u.color = $s.BODY_COLOR;

}

u.repaint();

}

};

$s.Snake.prototype.eat = function(x,y){

var food = $s.SnakeContext.food;

if(food != null){

food.alive = false;

this.units.push(food.unit);

$s.SnakeContext.oneat();

}else{

alert("error:no food on (" + x + "," + y + ")");

}

}

/*

* 随机数产生器,提供简便的方法

*/

$s.Random = {

randomNumber : function(lower,upper){ // 返回区间[lower,upper]的整数

var choices = upper - lower + 1;

return Math.floor(Math.random() * choices + lower); // value = Math.floor(Math.random() * 可能值的个数+ 第一个可能的值)

},

randomLocation : function(maxX,maxY){

var x = $s.Random.randomNumber(0,maxX);

var y = $s.Random.randomNumber(0,maxY);

return {x:x,y:y};

}

};

$s.Food = function(x,y){ // 代表食物,由一个Unit表示

this.unit = new $s.Unit();

this.unit.x = x;

this.unit.y = y;

var color = $s.COLORS[$s.Random.randomNumber(0,$s.COLORS.length - 1)];

this.unit.color = color;

this.alive = true;

this.unit.repaint();

};

$s.Food.prototype.locateOn = function(x,y){

return this.unit.x == x && this.unit.y == y;

};

/*

* HTML结点池,主要目的是提高效率

* 因为snake的移动是通过删除尾部结点并向头部添加结点实现的,

* 在这个过程中会有大量的结点创建操作,为了操作效率,所以对结点进行池化管理.

* 尾部的结点不删除,而是隐藏,需要结点时可以重用之

*/

$s.NodePool = {

nodes : []

};

$s.NodePool._findHideNode = function(){ // 查找隐藏的div结点

for(var i = 0 ; i < this.nodes.length ; i ++){

var n = this.nodes[i];

if(n.style.display == "none"){

return n;

}

}

return null;

};

$s.NodePool.createNode = function(){

var pooledNode = this._findHideNode();

if(pooledNode != null){

return pooledNode;

}else{

var newNode = document.createElement("div");

this.nodes.push(newNode);

return newNode;

}

};

$s.NodePool.releaseNode = function(node){

if(node != undefined && node != null){

if(node instanceof $s.Unit){

var view = node.view;

if(view != null){

var div = view.node;

div.style.display = "none";

}

}

}

}

$s.UnitView = function(){ // Unit的视图

this.unit = null;

this.node = null;

};

$s.UnitView.prototype.repaint = function(){

if(this.node == null){ // 初始化

var tag = $s.NodePool.createNode();

tag.style.width = $s.UNIT_WIDTH + "px";

tag.style.height = $s.UNIT_HEIGHT + "px";

tag.style.borderStyle = "dotted";

tag.style.borderWidth = "1px";

tag.style.borderColor = "white";

tag.style.margintLeft = "1px";

tag.style.marginRight = "1px";

tag.style.marginTop = "1px";

tag.style.marginBottom = "1px";

tag.style.backgroundColor = this.unit.color; // 颜色由模型Unit指定

tag.style.position = "absolute"; //容器的position指定为relative,孩子的position指定为absolute时,表示孩子相对容器绝对定位.

tag.style.display = "block"; // 重要,因为从NodePool取现的结点是隐藏状态的

var x = this.unit.x * $s.UNIT_WIDTH;

var y = this.unit.y * $s.UNIT_HEIGHT;

tag.style.top = y + "px";

tag.style.left = x + "px";

this.node = tag;

$s.SnakeContext.panelView.append(this);

}else{

var tag = this.node;

var x = this.unit.x * $s.UNIT_WIDTH;

var y = this.unit.y * $s.UNIT_HEIGHT;

tag.style.top = y + "px";

tag.style.left = x + "px";

tag.style.backgroundColor = this.unit.color;

}

};

$s.PanelView = function(){ // 整个游戏区域,包括按钮区

var panel = document.createElement("div");

panel.style.width = ($s.PANEL_WIDTH * $s.UNIT_WIDTH ) + "px";

panel.style.height = ($s.PANEL_HEIGHT * $s.UNIT_HEIGHT ) + "px";

panel.style.borderStyle = "dotted";

panel.style.borderColor = "red";

panel.style.borderWidth = "1px";

panel.style.marginLeft = "auto";

panel.style.marginRight = "auto";

panel.style.marginTop = "50px";

panel.style.position = "relative"; // 容器的position指定为relative,孩子的position指定为absolute时,表示孩子相对容器绝对定位.

panel.style.marginBottom = "auto";

this.node = panel;

document.body.appendChild(panel);

var len = document.createElement("div");

len.style.marginLeft = "auto";

len.style.marginRight = "auto";

len.style.marginBottom = "20px";

len.style.color = "gray";

len.style.fontSize = "12px";

len.innerHTML = "长度:";

document.body.appendChild(len);

$s.SnakeContext._len = len;

var startBn = document.createElement("button");

startBn.innerHTML = "开始";

startBn.style.marginLeft = "10px";

startBn.onclick = function(){

$s.SnakeContext.run();

};

$s.SnakeContext._startBn = startBn;

document.body.appendChild(startBn);

var pauseBn = document.createElement("button");

pauseBn.innerHTML = "暂停";

pauseBn.style.marginLeft = "10px";

pauseBn.onclick = function(){

$s.SnakeContext.pause();

};

$s.SnakeContext._pauseBn = pauseBn;

document.body.appendChild(pauseBn);

/*

var stopBn = document.createElement("button");

stopBn.innerHTML = "停止";

stopBn.style.marginLeft = "10px";

stopBn.onclick = function(){

$s.SnakeContext.stop();

};

$s.SnakeContext._stopBn = stopBn;

document.body.appendChild(stopBn);

*/

var restartBn = document.createElement("button");

restartBn.innerHTML = "重新开始";

restartBn.style.marginLeft = "10px";

restartBn.onclick = function(){

window.location.href = window.location.href;

};

$s.SnakeContext._restartBn = restartBn;

document.body.appendChild(restartBn);

var line = document.createElement("div");

line.style.height = "10px";

document.body.appendChild(line);

var span = document.createElement("span");

span.style.color = "gray";

span.style.fontSize = "12px";

span.innerHTML = "调试";

document.body.appendChild(span);

var debug = document.createElement("input");

debug.type = "checkbox";

debug.checked = $s.DEBUG;

debug.onchange = function(){

$s.SnakeContext.setDebug(debug.checked);

};

document.body.appendChild(debug);

};

$s.PanelView.prototype.append = function(unitView){

try{

this.node.appendChild(unitView.node);

}catch(e){

alert(e);

}

};

/*

* 全局环境类,代表应用

* 约定以_开头的成员为私有成员

* 启动程序的方法:

* window.onload = function(){

* $s.SnakeContext.init();

* }

*/

$s.SnakeContext = {

dir : $s.Dir.NONE,

state : $s.State.STOP,

goTimer : null,

run : function(){

if(this.state != $s.State.RUNNGIN){

this.state = $s.State.RUNNGIN;

this.goTimer = window.setInterval(function(){

$s.SnakeContext.updateFood();

$s.SnakeContext.snake.go();

},$s.STEP);

}

},

stop : function(){

this._setState($s.State.STOP);

},

pause : function(){

this._setState($s.State.PAUSE);

},

_setState : function(s){

if(this.state != s && this.goTimer != null){

window.clearInterval(this.goTimer);

this.goTimer = null;

this.state = s;

}

},

getFood : function(x,y){

for(var f in this.foods){

if(f.x == x && f.y == y){

return f;

}

}

return null;

},

init : function(){

this.panelView = new $s.PanelView();

this.snake = new $s.Snake();

this.dir = $s.Dir.DOWN;

this.snake.init($s.Dir.UP,3);

this._len.innerHTML = "长度:" + 3;

document.body.onkeyup = function(e){

var code = null;

if(window.event){ // fuck的IE

code = window.event.keyCode;

}else{

code = e.keyCode;

}

var str = "";

var oldDir = $s.SnakeContext.dir;

switch(code){

case 37: // left

if($s.SnakeContext.dir != $s.Dir.RIGHT){

$s.SnakeContext.dir = $s.Dir.LEFT;

}

str = "left";

break;

case 38 : // up

if($s.SnakeContext.dir != $s.Dir.DOWN){

$s.SnakeContext.dir = $s.Dir.UP;

}

str = "up";

break;

case 39: // right

if($s.SnakeContext.dir != $s.Dir.LEFT){

$s.SnakeContext.dir = $s.Dir.RIGHT;

}

str = "right";

break;

case 40: // down

if($s.SnakeContext.dir != $s.Dir.UP){

$s.SnakeContext.dir = $s.Dir.DOWN;

}

str = "down";

break;

}

if($s.SnakeContext.dir != oldDir){

if($s.DEBUG){

var v = $s($s.KEY_UP_DIR_ID);

if(v){

v.innerHTML = "方向键:" + str;

}

}

if($s.SnakeContext.goTimer != null){

window.clearInterval($s.SnakeContext.goTimer);

$s.SnakeContext.goTimer = null;

}

$s.SnakeContext.snake.go();

$s.SnakeContext.goTimer = window.setInterval(function(){

$s.SnakeContext.updateFood();

$s.SnakeContext.snake.go();

},$s.STEP);

}

};

var loc = $s.Random.randomLocation($s.PANEL_WIDTH - 1, $s.PANEL_HEIGHT - 1);

this.food = new $s.Food(loc.x,loc.y);

},

snake : null,

foods : [],

panelView : null,

food : null,

updateFood : function(){

if(this.food.alive){ // 当前Food还存活

return;

}

var loc = null;

do{

// 随机产生一个点,直到不Snake重叠

loc = $s.Random.randomLocation($s.PANEL_WIDTH - 1,$s.PANEL_HEIGHT - 1);

}while(this.overlap(loc));

this.food = new $s.Food(loc.x,loc.y);

},

overlap : function(loc){ // 检查是否与Snake重叠,当重叠时返回true

var x = loc.x;

var y = loc.y;

for(var i = 0 ; i < this.snake.units.length ; i ++ ){

var u = this.snake.units[i];

if(u.x == x && u.y == y){

return true;

}

}

return false;

},

hasFood : function(x,y){

if($s.DEBUG){

var xt = $s($s.HEAD_LOCATION_ID);

if(xt){

xt.innerHTML = "头部位置:(" + x + "," + y + ")";

}

}

return this.food.locateOn(x,y);

},

setDebug : function(enable){

if(enable != $s.DEBUG){

$s.DEBUG = enable;

if($s.DEBUG){ // 显示

var i = $s($s.KEY_UP_DIR_ID);

$s.SnakeContext._show(i);

i = $s($s.HEAD_LOCATION_ID);

$s.SnakeContext._show(i);

}else{ // 隐藏

var i = $s($s.KEY_UP_DIR_ID);

$s.SnakeContext._hide(i);

i = $s($s.HEAD_LOCATION_ID);

$s.SnakeContext._hide(i);

}

}

},

_show : function(tag){

if(tag){

tag.style.display = "block";

}

},

_hide : function(tag){

if(tag){

tag.style.display = "none";

}

},

ondead : function(){ // Snake死亡时回调

if(this._startBn){

this._startBn.disabled = true;

}

if(this._pauseBn){

this._pauseBn.disabled = true;

}

if(this._stopBn){

this._stopBn.disabled = true;

}

alert("挂了");

},

oneat : function(){ // Snake长度增加时回调

this._len.innerHTML = "长度:" + this.snake.units.length;

},

_startBn : null,

_pauseBn : null,

_stopBn : null,

_restartBn : null,

_len : null

};

})();

QQ图片20161125114704.png

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系admin@php.cn核实处理。