Js贪吃蛇开发过程详细
2013-08-16 17:20
274 查看
最近看百度知道时有人提问js实现贪吃蛇,突然来了兴致想自己写一个。从来没写过贪吃蛇,也不知道应该怎么实现,后来自己琢磨了一个方法,不知道会不会有更好的,直接贴代码的话怕有人看不懂,于是想把整个开发过程详细写出来和大家交流学习,如下:
开发需求:
需求大家玩过贪吃蛇应该都了解,用上下左右控制蛇的方向移动,吃到闪动的小虫就增长一段并加分。直到撞到四壁或吃到自己游戏结束。
设计思路:
首先要有一个地图供蛇爬行。由于蛇的形状长度均会改变,如果用图片表示的话肯定不行,其实蛇的移动可以看做是背景色的黑白变化,蛇头的前一个html元素变黑,蛇为变白,则可看成是向前移动了一步。你整个地图的移动则需将整个地图分为多个小的html元素,这里我们选择table做地图框架,里面包含的td做蛇的位移就可以了。我们可以做一个30*30的表格做地图。然后通过改变900个td的背景色的变化来控制蛇的位移。
开发详细:
初始构思是这样画出方格阵做地图,然后3个方格是黑色背景表示的蛇。第四个变黑,第一个变白则可看做是向前移动了一步。怎样控制前后变色呢?开始我想到的是用js获取当前元素的前后兄弟元素的方法控制首尾的td背景颜色改变,但这种方法后来失败,只能适用于直线前行的移动,如果向下掉头的户js获取下方的td就难了。其实等把整个地图画出来就想到了,应该用二维数组。二维数组的一维表示tr行,二维表示每行的td,这样把地图的每个元素都存入二维数组,可以按a[x][y]的x,y算法很快的调用到要移动的位置。
其它的蛇怎么动怎么吃虫子先不考虑,先做出这个table和数组。
css
body页面为
js
这样就画出了一个ck*ck的表格,此处用的30,即30*30;生成表格的同时生成了数组。
然后我们初始化蛇的位置在前k个,k为蛇的初始长度可自行设定。这个没啥难的,循环allTds[0][0]到allTds[0][k-1]将其className设为css的“hei”即可。
然后的要点是我们要考虑这条蛇应该怎么移动,实质是蛇头的相邻td和蛇尾的td的背景变化。蛇头好说如果当前蛇头是allTds[x][y],则移动一步后向前则变为allTds[x][y+1],向下舌头变为allTds[x+1][y]。但如何知道当前哪个是蛇头?经过很多次走动拐弯后如何确定,还有蛇尾,蛇尾的td变白色后,哪个td是下一个蛇尾,是左侧还是右侧还是上下都有可能。
这时我想到一个办法是用一个一维数组var she = new Array();表示蛇身,将表示蛇神的td元素依次从尾到头存入she[0]....she
.这样移动一步后新蛇头可以根据移动方向确定下一个蛇头是谁,将she
替换为新的蛇头,前面的she[n-]的值替换为she
....一次替换到she[0]是蛇尾变为了she[1].she[0]原有值被挤掉。这样就移动了一步!每移动一步前先这个数组中所有元素变白,即蛇消除掉,变化数组后此数组的所有元素再变黑,蛇再显示出来,就移动了,这样蛇头蛇尾的变化都解决
,不用考虑蛇身。
思路有了接下来就做。
首先蛇头是根据方向来移动的,需要一个控制方向的函数,蛇的消除和显示可以单独做两个个带参函数供调用如下
开始的初始化改为:
原来的地图table下再画个table放开始结束等按钮:
这时已经是一个粗糙的可以通过控制4向箭头控制蛇走动的游戏了。然后要解决的就是撞墙,还有吃虫,还有变长,还有吃到自己
撞墙这个必须控制,否则移动到table边缘时取不到下一个元素js本身就会报错。控制撞墙前提示也很简单,其实就是先看新舌头的坐标是否超出了0-ck的范围,再决定是结束游戏还是走一步,此例是0-30;
然后是吃虫,这个也很简单,用js取随机数的方法随机取0-ck的两个数做为虫子的坐标,如果新蛇头的坐标刚好等于虫子的坐标则表明吃到了虫子,则将虫子坐标做为新蛇头,为了增长蛇长度,后面不再挤掉蛇尾,数组不变,只是在数组尾部再加一个td,注意此处我们是依照蛇尾到蛇头顺序存入的数组,所以是数组尾部加一个新蛇头。吃掉后同时做加分操作。重新抛出一个虫子坐标。
然后是吃到自己,同样简单了,只有判断新蛇头是否包含在蛇身数组中就知道是否要吃到自己了。
然后是game over后如何结束游戏,其实就是关闭setTimeout事件,用var ji指向每一次的setTimeout结束游戏调用clearTimeout(ji)即可。
所以此时再执行移动就需要判断了,是否撞墙了,是否吃虫了,所以sheyin,shexian需要判断后再执行需要拿到if里面来:
这时基本完成了,不过还有点小问题,方向我们可以随意控制,但蛇是不能逆行反方向走的,所有控制方向这要改。开始想到的是改变方向前先判断是否逆行再去改变,后来实施后有问题,因为比如向左走时,按向下箭头,并不是蛇头立刻向下移动,而是等到这个setTimeout的0.5秒到时间后才去走,就算你设的不是0.5秒,这也是有时间间隔的,这段时间间隔内我们按了向下,非逆行改变了dir的值为下,立刻再按向右箭头,判断与原来的值下非逆行,也可改变dir为向右,但真正移动时。。。逆行了。。
解决办法是我们设一个临时的方向dirLin,无论怎么改变方向都是改变这个dirLin,真正执行移动这个操作时再去判断dirLin和正在走的方向dir是否逆行,如果不逆行,则dir=drLin,否则dir保持原方向。
最终代码如下:
欢迎交流学习
Java学习交流群: 2177712
开发需求:
需求大家玩过贪吃蛇应该都了解,用上下左右控制蛇的方向移动,吃到闪动的小虫就增长一段并加分。直到撞到四壁或吃到自己游戏结束。
设计思路:
首先要有一个地图供蛇爬行。由于蛇的形状长度均会改变,如果用图片表示的话肯定不行,其实蛇的移动可以看做是背景色的黑白变化,蛇头的前一个html元素变黑,蛇为变白,则可看成是向前移动了一步。你整个地图的移动则需将整个地图分为多个小的html元素,这里我们选择table做地图框架,里面包含的td做蛇的位移就可以了。我们可以做一个30*30的表格做地图。然后通过改变900个td的背景色的变化来控制蛇的位移。
开发详细:
初始构思是这样画出方格阵做地图,然后3个方格是黑色背景表示的蛇。第四个变黑,第一个变白则可看做是向前移动了一步。怎样控制前后变色呢?开始我想到的是用js获取当前元素的前后兄弟元素的方法控制首尾的td背景颜色改变,但这种方法后来失败,只能适用于直线前行的移动,如果向下掉头的户js获取下方的td就难了。其实等把整个地图画出来就想到了,应该用二维数组。二维数组的一维表示tr行,二维表示每行的td,这样把地图的每个元素都存入二维数组,可以按a[x][y]的x,y算法很快的调用到要移动的位置。
其它的蛇怎么动怎么吃虫子先不考虑,先做出这个table和数组。
css
#she{border-collapse:collapse;} #she td{ width:15px; height:15px; border:1px solid grey; } .hei{ background:#000;}/*黑色背景*/ .bai{ background:#ffff;}/*白色背景*/
body页面为
<table cellpadding="0" cellspacing="0" id="she" align="center"></table>
js
var allTds = new Array();//用于保存所有的td的二维数组 var ck = 30;//地图长宽 window.onload=function(){//网页加载时执行 for(var i = 0;i<ck;i++){//控制ck个行即tr var tr = document.createElement("tr"); var trs = new Array();//声明一个数组用于保存此行的ck个td. for(var j=0;j<ck;j++){//控制每个行里的元素即tr里的ck个td,如果不想画正方形ck可分开写两个参数,一个高一个宽 var td=document.createElement("td"); trs[j] = td; tr.appendChild(td); } document.getElementById("she").appendChild(tr); allTds[i] = trs;//保存到二维数组 }
这样就画出了一个ck*ck的表格,此处用的30,即30*30;生成表格的同时生成了数组。
然后我们初始化蛇的位置在前k个,k为蛇的初始长度可自行设定。这个没啥难的,循环allTds[0][0]到allTds[0][k-1]将其className设为css的“hei”即可。
然后的要点是我们要考虑这条蛇应该怎么移动,实质是蛇头的相邻td和蛇尾的td的背景变化。蛇头好说如果当前蛇头是allTds[x][y],则移动一步后向前则变为allTds[x][y+1],向下舌头变为allTds[x+1][y]。但如何知道当前哪个是蛇头?经过很多次走动拐弯后如何确定,还有蛇尾,蛇尾的td变白色后,哪个td是下一个蛇尾,是左侧还是右侧还是上下都有可能。
这时我想到一个办法是用一个一维数组var she = new Array();表示蛇身,将表示蛇神的td元素依次从尾到头存入she[0]....she
.这样移动一步后新蛇头可以根据移动方向确定下一个蛇头是谁,将she
替换为新的蛇头,前面的she[n-]的值替换为she
....一次替换到she[0]是蛇尾变为了she[1].she[0]原有值被挤掉。这样就移动了一步!每移动一步前先这个数组中所有元素变白,即蛇消除掉,变化数组后此数组的所有元素再变黑,蛇再显示出来,就移动了,这样蛇头蛇尾的变化都解决
,不用考虑蛇身。
思路有了接下来就做。
首先蛇头是根据方向来移动的,需要一个控制方向的函数,蛇的消除和显示可以单独做两个个带参函数供调用如下
var dir = 1;//方向,1前,2后,3上,4下 function keyDown(event){//接受键盘改变方向 if(event.keyCode==39){//左箭头 dir =1; } if(event.keyCode==37){//右箭头 dir =2; } if(event.keyCode==38){//上箭头 dir =3; } if(event.keyCode==40){//下箭头 dir =4; } }
function sheYin(obj){//蛇隐 其实不用全部消除,只消除蛇身数组的第一个,因为我们是按蛇尾到蛇头排的数组 obj[0].className = "bai"; } function sheXian(obj){//蛇现同理 obj[obj.length-1].className = "hei"; }
开始的初始化改为:
var allTds = new Array();//用于保存所有的td的二维数组 var ck = 30;//地图长宽 var k=3;//初始长度 var she = new Array();//初始化蛇 var x = 0;y = k-1;//蛇头坐标 var dir = 1;//方向,1前,2后,3上,4下 window.onload=function(){//网页加载时执行 for(var i = 0;i<ck;i++){//控制ck个行即tr var tr = document.getElementById("she").insertRow(-1); var trs = new Array();//声明一个数组用于保存此行的ck个td. for(var j=0;j<ck;j++){//控制每个行里的元素即tr里的ck个td,如果不想画正方形ck可分开写两个参数,一个高一个宽 var td=document.createElement("td"); trs[j] = td; tr.appendChild(td); } allTds[i] = trs;//保存到二维数组 } for(var g = 0;g< k;g++){//初始化蛇身数组 she[g] = allTds[0][g]; allTds[0][g].className = "hei"; } }
function weStart(){ sheYin(she);//先消除蛇尾 for(var o=0;o<she.length-1;o++){//将蛇身数组依次前移,挤掉蛇尾 she[o] = she[o+1]; } if(dir==1){//如果是横向前行 she[k-1] = allTds[x][y+1];//任命新蛇头 y+=1;//改变蛇头坐标 } if(dir==2){//同理 she[k-1] = allTds[x][y-1]; y-=1;//改变蛇头坐标 } if(dir==3){//同理 she[k-1] = allTds[x-1][y]; x-=1;//改变蛇头坐标 } if(dir==4){//同理 she[k-1] = allTds[x+1][y]; x+=1;//改变蛇头坐标 } sheXian(she);//将新蛇头点黑 setTimeout("weStart()",500);//隔半秒后再执行一次,就是半秒走一步的意思 }
原来的地图table下再画个table放开始结束等按钮:
<table cellpadding="0" cellspacing="0" id="she" align="center"></table> <table cellpadding="0" cellspacing="0" width="300px;" align="center"> <tr> <td><input type="button" value="Start" onclick="weStart()"/></td> <td>得分:<input id="mark" type="text" value="0" readonly size=3/></td> <td><input type="button" value="Stop" onclick="weStop()"/></td> </tr> </table>
这时已经是一个粗糙的可以通过控制4向箭头控制蛇走动的游戏了。然后要解决的就是撞墙,还有吃虫,还有变长,还有吃到自己
撞墙这个必须控制,否则移动到table边缘时取不到下一个元素js本身就会报错。控制撞墙前提示也很简单,其实就是先看新舌头的坐标是否超出了0-ck的范围,再决定是结束游戏还是走一步,此例是0-30;
然后是吃虫,这个也很简单,用js取随机数的方法随机取0-ck的两个数做为虫子的坐标,如果新蛇头的坐标刚好等于虫子的坐标则表明吃到了虫子,则将虫子坐标做为新蛇头,为了增长蛇长度,后面不再挤掉蛇尾,数组不变,只是在数组尾部再加一个td,注意此处我们是依照蛇尾到蛇头顺序存入的数组,所以是数组尾部加一个新蛇头。吃掉后同时做加分操作。重新抛出一个虫子坐标。
然后是吃到自己,同样简单了,只有判断新蛇头是否包含在蛇身数组中就知道是否要吃到自己了。
然后是game over后如何结束游戏,其实就是关闭setTimeout事件,用var ji指向每一次的setTimeout结束游戏调用clearTimeout(ji)即可。
所以此时再执行移动就需要判断了,是否撞墙了,是否吃虫了,所以sheyin,shexian需要判断后再执行需要拿到if里面来:
var ji = null;//控制游戏进程 var mark = 0;//分数 function westop(){ if(ji!=null){ clearTimeout(ji) } } function overItC(){//结束游戏 westop(); alert("吃自己了!最终得分:"+mark) } function overItZ(){//结束游戏 westop(); alert("撞墙了!最终得分:"+mark) } function zengIt(){//吃到虫子增长长分 she[she.length] = allTds[m] ; k+=1; mark+=1; document.getElementById("mark").value = mark;//得分 shiwu(); } function in_array(ch){//验证是否吃到自己 var inflag = true; for (var i = 0; i < she.length; i++) { if (she[i] == ch) { inflag = false; } } return inflag } function weStart(){ if(dir==1){ if(y+1<ck){//如果这个方向没撞墙 if(in_array(allTds[x][y+1])){//如果没吃到自己 sheYin(she); if(x==m&&y+1==n){//如果吃到了虫子 zengIt(); }else{ for(var o=0;o<she.length-1;o++){ she[o] = she[o+1]; } she[k-1] = allTds[x][y+1]; } sheXian(she); y+=1; ji = setTimeout("weStart()",sudu); }else{ overItC(); } }else{ overItZ(); } } if(dir==2){ if(y-1>=0){ if(in_array(allTds[x][y-1])){ sheYin(she); if(x==m&&y-1==n){//如果吃到了虫子 zengIt(); }else{ for(var o=0;o<she.length-1;o++){ she[o] = she[o+1]; } she[k-1] = allTds[x][y-1]; } sheXian(she); y-=1; ji = setTimeout("weStart()",sudu); }else{ overItC(); } }else{ overItZ(); } } if(dir==3){ if(x-1>=0){ if(in_array(allTds[x-1][y])){ sheYin(she); if(x-1==m&&y==n){//如果吃到了虫子 zengIt(); }else{ for(var o=0;o<she.length-1;o++){ she[o] = she[o+1]; } she[k-1] = allTds[x-1][y]; } sheXian(she); x-=1; ji = setTimeout("weStart()",sudu); }else{ overItC(); } }else{ overItZ(); } } if(dir==4){ if(x+1<30){ if(in_array(allTds[x+1][y])){ sheYin(she); if(x+1==m&&y==n){//如果吃到了虫子 zengIt(); }else{ for(var o=0;o<she.length-1;o++){ she[o] = she[o+1]; } she[k-1] = allTds[x+1][y]; } sheXian(she); x+=1; ji = setTimeout("weStart()",sudu); }else{ overItC(); } }else{ overItZ(); } } }
var shan = null;//控制食物闪烁进程 var m;var n//食物坐标 function shiwu(){//抛出食物 if(shan!=null){ clearTimeout(shan) } m = Math.floor(Math.random()*ck);//取的是0到ck-1 n = Math.floor(Math.random()*ck);//取的是0到ck-1 if(allTds[m] .className =="hei"){//如果取到了黑色背景的说明是蛇身重新取 shiwu(); }else{allTds[m] .className ="hei";} shanshuo(); } function shanshuo(){//让食物闪烁 if(allTds[m] .className =="hei"){ allTds[m] .className ="bai"; }else{ allTds[m] .className ="hei"; } shan = setTimeout("shanshuo()",500); }
这时基本完成了,不过还有点小问题,方向我们可以随意控制,但蛇是不能逆行反方向走的,所有控制方向这要改。开始想到的是改变方向前先判断是否逆行再去改变,后来实施后有问题,因为比如向左走时,按向下箭头,并不是蛇头立刻向下移动,而是等到这个setTimeout的0.5秒到时间后才去走,就算你设的不是0.5秒,这也是有时间间隔的,这段时间间隔内我们按了向下,非逆行改变了dir的值为下,立刻再按向右箭头,判断与原来的值下非逆行,也可改变dir为向右,但真正移动时。。。逆行了。。
解决办法是我们设一个临时的方向dirLin,无论怎么改变方向都是改变这个dirLin,真正执行移动这个操作时再去判断dirLin和正在走的方向dir是否逆行,如果不逆行,则dir=drLin,否则dir保持原方向。
最终代码如下:
<!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> <title>qqqun21/777/12</title> <meta http-equiv="Content-Type" content="text/html; charset=gb2312"/> <style> #she{border-collapse:collapse;} #she td{ width:15px; height:15px; border:1px solid grey; } .hei{ background:#000;} .bai{ background:#ffff;} </style> <script> var allTds = new Array(); var ck = 30;//地图长宽 var k=3;//初始长度 var she = new Array();//初始化蛇 var x = 0;y = k-1;//蛇头坐标 var m;var n//食物坐标 var dir = 1;//方向,1前,2后,3上,4下 var dirLin = 1;//临时方向,防止转圈按上左右导致逆行,1前,2后,3上,4下 var ji = null;//控制游戏进程 var shan = null;//控制闪烁进程 var mark = 0;//分数 var sudu = 500; window.onload=function(){ for(var i = 0;i<ck;i++){ var tr = document.createElement("tr"); var trs = new Array(); for(var j=0;j<ck;j++){ var td=document.createElement("td"); trs[j] = td; tr.appendChild(td); } document.getElementById("she").appendChild(tr); allTds[i] = trs; } for(var g = 0;g< k;g++){ she[g] = allTds[0][g]; allTds[0][g].className = "hei"; } shiwu(); } function sheYin(obj){//蛇隐 obj[0].className = "bai"; } function sheXian(obj){//蛇现 obj[obj.length-1].className = "hei"; } function shiwu(){ if(shan!=null){ clearTimeout(shan) } m = Math.floor(Math.random()*ck); n = Math.floor(Math.random()*ck); if(allTds[m] .className =="hei"){ shiwu(); }else{allTds[m] .className ="hei";} shanshuo(); } function shanshuo(){ if(allTds[m] .className =="hei"){ allTds[m] .className ="bai"; }else{ allTds[m] .className ="hei"; } shan = setTimeout("shanshuo()",500); } function overItC(){//结束游戏 westop(); alert("吃自己了!最终得分:"+mark) } function overItZ(){//结束游戏 westop(); alert("撞墙了!最终得分:"+mark) } function zengIt(){//吃到虫子增长长分 she[she.length] = allTds[m] ; k+=1; mark+=1;//每次得1分,可自己改 document.getElementById("mark").value = mark; shiwu(); } function westop(){ if(ji!=null){ clearTimeout(ji) } if(shan!=null){ clearTimeout(shan) } } function in_array(ch){//验证是否吃到自己 var inflag = true; for (var i = 0; i < she.length; i++) { if (she[i] == ch) { inflag = false; } } return inflag } function westart(){ if(dir==1&&dirLin!=2){ dir=dirLin; } if(dir==2&&dirLin!=1){ dir=dirLin; } if(dir==3&&dirLin!=4){ dir=dirLin; } if(dir==4&&dirLin!=3){ dir=dirLin; } if(dir==1){ if(y+1<ck){ if(in_array(allTds[x][y+1])){ sheYin(she); if(x==m&&y+1==n){//如果吃到了虫子 zengIt(); }else{ for(var o=0;o<she.length-1;o++){ she[o] = she[o+1]; } she[k-1] = allTds[x][y+1]; } sheXian(she); y+=1; ji = setTimeout("westart()",sudu); }else{ overItC(); } }else{ overItZ(); } } if(dir==2){ if(y-1>=0){ if(in_array(allTds[x][y-1])){ sheYin(she); if(x==m&&y-1==n){//如果吃到了虫子 zengIt(); }else{ for(var o=0;o<she.length-1;o++){ she[o] = she[o+1]; } she[k-1] = allTds[x][y-1]; } sheXian(she); y-=1; ji = setTimeout("westart()",sudu); }else{ overItC(); } }else{ overItZ(); } } if(dir==3){ if(x-1>=0){ if(in_array(allTds[x-1][y])){ sheYin(she); if(x-1==m&&y==n){//如果吃到了虫子 zengIt(); }else{ for(var o=0;o<she.length-1;o++){ she[o] = she[o+1]; } she[k-1] = allTds[x-1][y]; } sheXian(she); x-=1; ji = setTimeout("westart()",sudu); }else{ overItC(); } }else{ overItZ(); } } if(dir==4){ if(x+1<30){ if(in_array(allTds[x+1][y])){ sheYin(she); if(x+1==m&&y==n){//如果吃到了虫子 zengIt(); }else{ for(var o=0;o<she.length-1;o++){ she[o] = she[o+1]; } she[k-1] = allTds[x+1][y]; } sheXian(she); x+=1; ji = setTimeout("westart()",sudu); }else{ overItC(); } }else{ overItZ(); } } } function keyDown(event){//接受键盘改变方向 if(event.keyCode==39){//左箭头 dirLin =1; } if(event.keyCode==37){//右箭头 dirLin =2; } if(event.keyCode==38){//上箭头 dirLin =3; } if(event.keyCode==40){//下箭头 dirLin =4; } } </script> </head> <body onkeydown="keyDown(event)"> <table cellpadding="0" cellspacing="0" id="she" align="center"></table> <table cellpadding="0" cellspacing="0" width="300px;" align="center"> <tr> <td><input type="button" value="start" onclick="westart()"/></td> <td>得分:<input id="mark" type="text" value="0" readonly size=3/></td> <td><input type="button" value="stop" onclick="westop()"/></td> </tr> </table> </body>
欢迎交流学习
Java学习交流群: 2177712
相关文章推荐
- LINUX开发环境构建过程(很详细)
- Android开发进阶(七)-- Android客户端访问PHP服务器的详细过程
- 2017年,软件开发全过程,描述得不能再详细了
- MyEclipse + Maven开发Web工程的详细配置过程
- 用c#进行移动设备开发时rda同步数据时的设置详细过程
- MyEclipse + Maven开发Web工程的详细配置过程
- 2017年,软件开发全过程,描述得不能再详细了
- Maven开发Web工程的详细配置过程
- Maven命令详解 模块导入 MyEclipse + Maven开发Web工程的详细配置过程
- windows下用ADT进行android NDK开发的详细教程(从环境搭建、配置到编译全过程)
- MyEclipse + Maven开发Web工程的详细配置过程
- MyEclipse + Maven开发Web工程的详细配置过程
- MyEclipse + Maven开发Web工程的详细配置过程 .
- Python开发工具:Wing IDE (含详细注册过程)
- MyEclipse + Maven开发Web工程的详细配置过程
- 软件开发过程中的浪费——详细设计
- MyEclipse导入Maven项目开发Web工程的详细配置过程
- MyEclipse + Maven开发Web工程的详细配置过程
- MyEclipse + Maven开发Web工程的详细配置过程
- Js版游戏打砖块开发过程详细