JavaScript简单贪吃蛇,基本面向对象
没有写博客的习惯,这篇算心血来潮,算篇近几天编写的小程序纪实.
以编写此程序的方式结束Javascript的本阶段的学习.编写的目的在于熟悉javascript的编程方式,包括代码风格,面向对象的运用等.
回到程序,说说Snake的移动的实现方法.其实很简单,向头部添加Unit,然后删除尾部.其他,参见注释.
作者:pcenshao
转载请注明来自:
http://blog.csdn.net/pywepe
程序包括一个html文件:snake.html和一个js文件:snake.js
snake.html:
<!DOCTYPEhtmlPUBLIC"-//W3C//DTDXHTML1.0Transitional//EN""http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<htmlxmlns="http://www.w3.org/1999/xhtml">
<head>
<metahttp-equiv="Content-Type"content="text/html;charset=utf-8"/>
<title>JavaScript简单贪吃蛇</title>
<scripttype="text/javascript"src="snake.js"></script>
<scripttype="text/javascript">
$s(function(){
$s.SnakeContext.init();
});
</script>
</head>
<body>
<divid="headLocation"></div>
<divid="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(typeofarguments[0]=="string"){
returndocument.getElementById(arguments[0]);
}elseif(typeofarguments[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){
varx=5;
vary=5;
for(vari=0;i<count;i++){
varu=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(vari=this.units.length-2;i>=0;i--){//不包括头自身
varu=this.units[i];
if(u.x==x&&u.y==y){
returntrue;
}
}
returnfalse;
};
$s.Snake.prototype.go=function(){
//判断前方是否有食物
//是否撞墙
var_x=0,_y=0;
varhead=this.units[this.units.length-1];
_x=head.x;
_y=head.y;
vardir=$s.SnakeContext.dir;
if(this.crash(_x,_y)){//判断是否碰撞到自身
$s.SnakeContext.stop();
$s.SnakeContext.ondead();//触发dead事件
return;
}
if(dir==$s.Dir.LEFT){
_x--;
}elseif(dir==$s.Dir.RIGHT){
_x++;
}elseif(dir==$s.Dir.UP){
_y--;
}elseif(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;
}
varh=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--;
}elseif(dir==$s.Dir.RIGHT){
_x++;
}elseif(dir==$s.Dir.UP){
_y--;
}elseif(dir==$s.Dir.DOWN){
_y++;
}
head.color=$s.HEAD_COLOR;
head.repaint();
varoldHead=this.units[this.units.length-2];
oldHead.color=$s.BODY_COLOR;
oldHead.repaint();
return;
}
vartail=this.units.shift();
$s.NodePool.releaseNode(tail);
h.x=_x;
h.y=_y;
this.units.push(h);
for(vari=this.units.length-1;i>=0;i--){
varu=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){
varfood=$s.SnakeContext.food;
if(food!=null){
food.alive=false;
this.units.push(food.unit);
$s.SnakeContext.oneat();
}else{
alert("error:nofoodon("+x+","+y+")");
}
}
/*
*随机数产生器,提供简便的方法
*/
$s.Random={
randomNumber:function(lower,upper){//返回区间[lower,upper]的整数
varchoices=upper-lower+1;
returnMath.floor(Math.random()*choices+lower);//value=Math.floor(Math.random()*可能值的个数+第一个可能的值)
},
randomLocation:function(maxX,maxY){
varx=$s.Random.randomNumber(0,maxX);
vary=$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;
varcolor=$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){
returnthis.unit.x==x&&this.unit.y==y;
};
/*
*HTML结点池,主要目的是提高效率
*因为snake的移动是通过删除尾部结点并向头部添加结点实现的,
*在这个过程中会有大量的结点创建操作,为了操作效率,所以对结点进行池化管理.
*尾部的结点不删除,而是隐藏,需要结点时可以重用之
*/
$s.NodePool={
nodes:[]
};
$s.NodePool._findHideNode=function(){//查找隐藏的div结点
for(vari=0;i<this.nodes.length;i++){
varn=this.nodes[i];
if(n.style.display=="none"){
returnn;
}
}
returnnull;
};
$s.NodePool.createNode=function(){
varpooledNode=this._findHideNode();
if(pooledNode!=null){
returnpooledNode;
}else{
varnewNode=document.createElement("div");
this.nodes.push(newNode);
returnnewNode;
}
};
$s.NodePool.releaseNode=function(node){
if(node!=undefined&&node!=null){
if(nodeinstanceof$s.Unit){
varview=node.view;
if(view!=null){
vardiv=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){//初始化
vartag=$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取现的结点是隐藏状态的
varx=this.unit.x*$s.UNIT_WIDTH;
vary=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{
vartag=this.node;
varx=this.unit.x*$s.UNIT_WIDTH;
vary=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(){//整个游戏区域,包括按钮区
varpanel=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);
varlen=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;
varstartBn=document.createElement("button");
startBn.innerHTML="开始";
startBn.style.marginLeft="10px";
startBn.onclick=function(){
$s.SnakeContext.run();
};
$s.SnakeContext._startBn=startBn;
document.body.appendChild(startBn);
varpauseBn=document.createElement("button");
pauseBn.innerHTML="暂停";
pauseBn.style.marginLeft="10px";
pauseBn.onclick=function(){
$s.SnakeContext.pause();
};
$s.SnakeContext._pauseBn=pauseBn;
document.body.appendChild(pauseBn);
/*
varstopBn=document.createElement("button");
stopBn.innerHTML="停止";
stopBn.style.marginLeft="10px";
stopBn.onclick=function(){
$s.SnakeContext.stop();
};
$s.SnakeContext._stopBn=stopBn;
document.body.appendChild(stopBn);
*/
varrestartBn=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);
varline=document.createElement("div");
line.style.height="10px";
document.body.appendChild(line);
varspan=document.createElement("span");
span.style.color="gray";
span.style.fontSize="12px";
span.innerHTML="调试";
document.body.appendChild(span);
vardebug=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(varfinthis.foods){
if(f.x==x&&f.y==y){
returnf;
}
}
returnnull;
},
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){
varcode=null;
if(window.event){//fuck的IE
code=window.event.keyCode;
}else{
code=e.keyCode;
}
varstr="";
varoldDir=$s.SnakeContext.dir;
switch(code){
case37://left
if($s.SnakeContext.dir!=$s.Dir.RIGHT){
$s.SnakeContext.dir=$s.Dir.LEFT;
}
str="left";
break;
case38://up
if($s.SnakeContext.dir!=$s.Dir.DOWN){
$s.SnakeContext.dir=$s.Dir.UP;
}
str="up";
break;
case39://right
if($s.SnakeContext.dir!=$s.Dir.LEFT){
$s.SnakeContext.dir=$s.Dir.RIGHT;
}
str="right";
break;
case40://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){
varv=$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);
}
};
varloc=$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;
}
varloc=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
varx=loc.x;
vary=loc.y;
for(vari=0;i<this.snake.units.length;i++){
varu=this.snake.units[i];
if(u.x==x&&u.y==y){
returntrue;
}
}
returnfalse;
},
hasFood:function(x,y){
if($s.DEBUG){
varxt=$s($s.HEAD_LOCATION_ID);
if(xt){
xt.innerHTML="头部位置:("+x+","+y+")";
}
}
returnthis.food.locateOn(x,y);
},
setDebug:function(enable){
if(enable!=$s.DEBUG){
$s.DEBUG=enable;
if($s.DEBUG){//显示
vari=$s($s.KEY_UP_DIR_ID);
$s.SnakeContext._show(i);
i=$s($s.HEAD_LOCATION_ID);
$s.SnakeContext._show(i);
}else{//隐藏
vari=$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
};
})();
计算机二手书经典
http://pcenshao.taobao.com