2016-09-22更新:html
使用canvas實現:https://github.com/onlyfu/SnakeSir-Javascriptgit
如下爲HTML4實現:github
今天來個略有意思的,《貪食蛇》。這個估計沒有人沒玩過吧。它稍有點難度,不過仍然算是簡單的遊戲,實現代碼也很少,下面就開始canvas
這裏效果好點數組
貪食蛇主要幾個問題須要解決dom
基本上,《貪食蛇》就難在這三個地方,這個順序,難度從高到低,最簡單的莫過去撞牆判斷失敗。最難的是轉向,以後是吃。下面從最開始一步步的來解決這些問題。ide
var mapItemX=60; //遊戲地圖橫向點數 var mapItemY=31; //遊戲地圖縱向點數 var snakeLen=5; //蛇的初始長度 var snakeMoveDirection='E'; //蛇的移動方向 var snakeStartPoints={'x':5,'y':15}; //蛇的起始位置 var snake=new Array(); //用於存放蛇身點的座標 var corner=new Array(); //用於存放轉角點座標 var cornerNum=0; //轉角數 var timer,speed=100; //移動計時器和初始移動速度 var timeiner,timeSecond=0,timeMinute=0,timestr=0; //時間計時器,分,秒,總秒數 var mouseX,mouseY; //老鼠位置(吃的) var start=false; //是否開始
function init(){ var maps=''; for(var i=0;i<mapItemY;i++){ for(var j=0;j<mapItemX;j++){ maps=maps+'<div id="mapItem_'+j+'_'+i+'" class="mapItem"></div>'; } } $("#game_map").html(maps); //放地圖的容器 }
地圖很簡單,不過要注意,必須保證以第一行0,0開始,第二行0,1開始,以此類推,它是一個二維數組,這直接關係到定位,因此必須得保證這樣的結構。this
生成的每個點,都有一個根據縱橫座標組成的ID,它將是控制這些點的必要東西spa
function initSnake(){ if(snakeMoveDirection=='E'){ //用於判斷蛇的初始前進方向 var j=snakeLen+snakeStartPoints.x; var _snakeLen=snakeLen; for(var i=snakeStartPoints.x;i<j;i++){ if(i==snakeLen*2-1){ $("#mapItem_"+i+"_"+snakeStartPoints.y).addClass('snakehead'); }else{ $("#mapItem_"+i+"_"+snakeStartPoints.y).addClass('snakebody'); } _snakeLen--; snake[_snakeLen]=new Array(); snake[_snakeLen]['x']=i; snake[_snakeLen]['y']=snakeStartPoints.y; snake[_snakeLen]['d']=snakeMoveDirection; } } }
snakeMoveDirection,有個默認值,直接決定蛇的初始前進方向,這裏只使用了一個方向,因此只寫了一組,若是須要設定蛇的初始前方向,這裏得把4個方向都補齊。debug
以後根據初始的蛇身長度和初始位置進行循環,將以前的點增長CSS樣式,也就是變個顏色,蛇頭用另外一種顏色,這樣好區分那裏是頭。每一個點都要寫進snake數組裏,從大到小寫,好比初始蛇長爲5,那就是4到0,蛇頭永遠是0,這樣會更容易操做。
function initMouse(){ var _x,_y; var _this=this; this.randoms=function(){ _x=Math.floor(Math.random()*(mapItemX-1)); _y=Math.floor(Math.random()*(mapItemY-1)); _this.checkMouse(); } this.checkMouse=function(){ var _e=false; for(var i in snake){ if(snake[i]['x']==_x&&snake[i]['y']==_y){ _e=true; break; } } if(_e==true){ _this.randoms(); }else{ mouseX=_x; mouseY=_y; $("#mapItem_"+mouseX+'_'+mouseY).addClass('mouse'); } } this.randoms(); }
吃的,也就是在地圖上隨機出現一個點,因此直接使用Math.random()就能夠了,由於是橫縱座標,因此得生成兩個值,它的下限天然是0,上限天然是地圖的大小,因此使用:Math.floor(Math.random()*(mapItemX-1));;
不過這裏還有個問題,這樣隨機的點,有可能恰好在蛇身上,那樣可就太糟糕了,因此蛇身所在的點都得排除。this.checkMouse(),就是用來作這件事,遍歷蛇身的點,和生成的點座標進行比較,若是恰好在它身上,再生成一次隨機點。這裏是遞歸,this.randoms()裏調用this.checkMouse(),這樣直到生成的點不在蛇身上爲止。找到這樣的點後,就給這個點增長一個CSS樣式,改變它的顏色,這裏用紅色,代表是吃的。
好了,準備工做就緒了,下面就是移動
function move(){ var _d; $("#mapItem_"+snake[snakeLen-1]['x']+'_'+snake[snakeLen-1]['y']).removeClass('snakebody'); for(var i=0;i<snakeLen;i++){ if(i==0){ $("#mapItem_"+snake[i]['x']+'_'+snake[i]['y']).removeClass('snakehead'); $("#mapItem_"+snake[i]['x']+'_'+snake[i]['y']).addClass('snakebody'); } var _d=corners(snake[i]['x'],snake[i]['y']); //判斷轉角 if(_d){ snake[i]['d']=_d; } if(i==snakeLen-1&&corner.length>0){ clearCorners(snake[i]['x'],snake[i]['y']); //清除一個轉角點 } switch(snake[i]['d']){ case 'E': if(i==0){ eat(snake[i]['x']+1,snake[i]['y']); checkDie(snake[i]['x']+1,snake[i]['y']); } snake[i]['x']=snake[i]['x']+1; break; case 'S': if(i==0){ eat(snake[i]['x'],snake[i]['y']+1); checkDie(snake[i]['x'],snake[i]['y']+1); } snake[i]['y']=snake[i]['y']+1; break; case 'W': if(i==0){ eat(snake[i]['x']-1,snake[i]['y']); checkDie(snake[i]['x']-1,snake[i]['y']); } snake[i]['x']=snake[i]['x']-1; break; case 'N': if(i==0){ eat(snake[i]['x'],snake[i]['y']-1); checkDie(snake[i]['x'],snake[i]['y']-1); } snake[i]['y']=snake[i]['y']-1; break; } } $("#mapItem_"+snake[0]['x']+'_'+snake[0]['y']).addClass('snakehead'); }
移動的代碼稍多,主要是思路是:
function corners(x,y){ var _d=''; for(var i in corner){ if(corner[i]['x']==x&&corner[i]['y']==y){ _d=corner[i]['d']; } } return _d; }
主要是遍歷轉角數組,判斷蛇身點,是否是通過它,若是是就返回這個轉角點的方向,move()的時候,若是corners()有值,則更新通過它的蛇身點的方向。每一個點都會通過它,都會改變方向,整個蛇也就改變了方向。
function clearCorners(x,y){ if(corner[0]['x']==x&&corner[0]['y']==y){ corner.shift(); cornerNum=corner.length; } }
之因此要清除轉角,是由於若是你不清除它,那當蛇再次通過它的時候,會自動轉向的。這裏主要是先進先出的原則,直接使用shift()就能夠了,以後重計轉角數組的長度。
function checkDie(x,y){ if(x<0||x>mapItemX-1||y<0||y>mapItemY-1){ //撞牆 clearInterval(timer); clearInterval(timeiner); var _scores=scores(); alert('你死了!總分:'+_scores.toFixed(2)); location.reload(); return false; } for(var i in snake){ if(snake[i]['x']==x&&snake[i]['y']==y){ //撞到蛇身上 clearInterval(timer); clearInterval(timeiner); var _scores=scores(); alert('你死了!總分:'+_scores.toFixed(2)); location.reload(); return false; } } }
撞牆最好懂了,也就是超出了地圖的邊。撞蛇身上,則須要遍歷蛇身上的點,判斷移動到的點是否是在這些點裏,若是是,你死了。
死了,天然要中止計時,給出提示。這裏還計算了一下分數,不過度數的計算方法不好勁,算了,不說了。
function eat(x,y){ if(mouseX==x&&mouseY==y){ //判斷吃 var _snakeLast=snake[snakeLen-1]; snake[snakeLen]=new Array(); switch(_snakeLast['d']){ case 'E': snake[snakeLen]['x']=_snakeLast['x']-1; snake[snakeLen]['y']=_snakeLast['y']; break; case 'S': snake[snakeLen]['x']=_snakeLast['x']; snake[snakeLen]['y']=_snakeLast['y']-1; break; case 'W': snake[snakeLen]['x']=_snakeLast['x']+1; snake[snakeLen]['y']=_snakeLast['y']; break; case 'N': snake[snakeLen]['x']=_snakeLast['x']; snake[snakeLen]['y']=_snakeLast['y']+1; break; } snake[snakeLen]['d']=_snakeLast['d']; snakeLen=snake.length; $("#snake_len").html(snakeLen); $("#mapItem_"+mouseX+"_"+mouseY).removeClass('mouse'); initMouse(); changeSpeed(); } }
蛇頭碰到吃點時,最重的一點是在蛇尾增長一個點作蛇尾,但須要考慮的問題是,這個點應該在蛇尾的什麼位置上。這裏使用蛇尾點的方向,若是它的方向是E,那就在它的W上增長一個點,同理可得其它3個點時,增長點的位置。也就是代碼中switch部分所作的事情。
新的蛇尾添加好後,就能夠把吃的點移除,removeClass(),以後再生成一個新的吃點initMouse();
changeSpeed(),只是爲了提升一些可玩性,知足條件時加快一點蛇的移動速度。這裏就不提了。
function keyDirection(e){ var evt = e ||window.event; var key=evt.which||evt.keyCode; switch(key){ case 37: snakeMoveDirection='W'; break; case 38: snakeMoveDirection='N'; break; case 39: snakeMoveDirection='E'; break; case 40: snakeMoveDirection='S'; break; } corner[cornerNum]=new Array(); corner[cornerNum]['x']=snake[0]['x']; corner[cornerNum]['y']=snake[0]['y']; corner[cornerNum]['d']=snakeMoveDirection; cornerNum++; }
很簡單,獲取按鍵值,判斷方向,設定snakeMoveDirection,而後增長一個轉角點。但這裏會有一個問題,那就是,當連續兩次按鍵的速度快過蛇的移動速度時,兩個轉角點會重合,這就會讓整個轉角代碼失控,出現不能清除的狀況,那當蛇再次通過這些點的時候,它會自動轉向,因此,這裏還得增強一下
var _snakeMoveDirection=snakeMoveDirection; //先將方向附給_snakMoveDirection this.createCorner=function(){ corner[cornerNum]=new Array(); corner[cornerNum]['x']=snake[0]['x']; corner[cornerNum]['y']=snake[0]['y']; corner[cornerNum]['d']=snakeMoveDirection; $("#mapItem_"+corner[cornerNum]['x']+'_'+corner[cornerNum]['y']).addClass('corner'); cornerNum++; } if(cornerNum>0){ //判斷轉角數是否是大於0 if(corner[cornerNum-1]['x']!=snake[0]['x']||corner[cornerNum-1]['y']!=snake[0]['y']){ this.createCorner(); }else{ snakeMoveDirection=_snakeMoveDirection; } }else{ this.createCorner(); }
使用一個變量_snakeMoveDirection來保存以前的方向,目的是爲了,當判斷點重合的時候,再將方向改過來,否則蛇會不聽話的。判斷轉角數大於0後,再判斷一下目標點的座標和前一個轉角點的座標,是否是重合,若是不重合,那麼記錄新點,重合就不記錄點了,而是將以前的方向再附給snakeMoveDirection。這樣就不會出現快速按鍵時的問題了。
爲了不按下蛇前進方向的方向時仍然記錄轉角點,這裏還能夠增長一個判斷,若是目標方向和前進方向相同,則不作任何記錄。完整的控制代碼:
function keyDirection(e){ var evt = e ||window.event; var key=evt.which||evt.keyCode; var notdo=false; var _snakeMoveDirection=snakeMoveDirection; switch(key){ case 37: if(snakeMoveDirection=='W'){ notdo=true; }else{ snakeMoveDirection='W'; } break; case 38: if(snakeMoveDirection=='N'){ notdo=true; }else{ snakeMoveDirection='N'; } break; case 39: if(snakeMoveDirection=='E'){ notdo=true; }else{ snakeMoveDirection='E'; } break; case 40: if(snakeMoveDirection=='S'){ notdo=true; }else{ snakeMoveDirection='S'; } break; } this.createCorner=function(){ corner[cornerNum]=new Array(); corner[cornerNum]['x']=snake[0]['x']; corner[cornerNum]['y']=snake[0]['y']; corner[cornerNum]['d']=snakeMoveDirection; $("#mapItem_"+corner[cornerNum]['x']+'_'+corner[cornerNum]['y']).addClass('corner'); cornerNum++; } if(notdo==false){ if(cornerNum>0){ if(corner[cornerNum-1]['x']!=snake[0]['x']||corner[cornerNum-1]['y']!=snake[0]['y']){ this.createCorner(); }else{ snakeMoveDirection=_snakeMoveDirection; } }else{ this.createCorner(); } } }
程序基本上就這些內容,後面的計數、計分、計時,就不說了。最後是使用定時器,讓蛇移動起來
$("#game_map").click(function(){ if(start==false){ start=true; timeiner=setInterval(timeing,1000); move(); timer=setInterval(move,speed); }else{ start=false; clearInterval(timer); clearInterval(timeiner); } });
這裏使用的是點擊遊戲地圖開始的方式,爲了防止加速的狀況,因此使用了start變量作了一個判斷。點第一次是開始,再點就是暫停,再點又是開始。
最後是完整代碼(JS部分,HTML請直接查看原代碼):
var mapItemX=60; var mapItemY=31; var snakeLen=5; var snakeMoveDirection='E'; var snakeStartPoints={'x':5,'y':15}; var snake=new Array(); var corner=new Array(); var cornerNum=0; var timer,speed=100; var timeiner,timeSecond=0,timeMinute=0,timestr=0; var mouseX,mouseY; var start=false; $("#game_map").click(function(){ if(start==false){ start=true; timeiner=setInterval(timeing,1000); move(); timer=setInterval(move,speed); }else{ start=false; clearInterval(timer); clearInterval(timeiner); } }); $(document).keydown(function(e){ keyDirection(e); }); init(); function init(){ var maps=''; for(var i=0;i<mapItemY;i++){ for(var j=0;j<mapItemX;j++){ maps=maps+'<div id="mapItem_'+j+'_'+i+'" class="mapItem"></div>'; } } $("#game_map").html(maps); initSnake(); initMouse(); } function initSnake(){ if(snakeMoveDirection=='E'){ var j=snakeLen+snakeStartPoints.x; var _snakeLen=snakeLen; for(var i=snakeStartPoints.x;i<j;i++){ if(i==snakeLen*2-1){ $("#mapItem_"+i+"_"+snakeStartPoints.y).addClass('snakehead'); }else{ $("#mapItem_"+i+"_"+snakeStartPoints.y).addClass('snakebody'); } _snakeLen--; snake[_snakeLen]=new Array(); snake[_snakeLen]['x']=i; snake[_snakeLen]['y']=snakeStartPoints.y; snake[_snakeLen]['d']=snakeMoveDirection; } } } function move(){ var _d; $("#mapItem_"+snake[snakeLen-1]['x']+'_'+snake[snakeLen-1]['y']).removeClass('snakebody'); for(var i=0;i<snakeLen;i++){ if(i==0){ $("#mapItem_"+snake[i]['x']+'_'+snake[i]['y']).removeClass('snakehead'); $("#mapItem_"+snake[i]['x']+'_'+snake[i]['y']).addClass('snakebody'); } var _d=corners(snake[i]['x'],snake[i]['y']); if(_d){ snake[i]['d']=_d; } if(i==snakeLen-1&&corner.length>0){ clearCorners(snake[i]['x'],snake[i]['y']); } switch(snake[i]['d']){ case 'E': if(i==0){ eat(snake[i]['x']+1,snake[i]['y']); checkDie(snake[i]['x']+1,snake[i]['y']); } snake[i]['x']=snake[i]['x']+1; break; case 'S': if(i==0){ eat(snake[i]['x'],snake[i]['y']+1); checkDie(snake[i]['x'],snake[i]['y']+1); } snake[i]['y']=snake[i]['y']+1; break; case 'W': if(i==0){ eat(snake[i]['x']-1,snake[i]['y']); checkDie(snake[i]['x']-1,snake[i]['y']); } snake[i]['x']=snake[i]['x']-1; break; case 'N': if(i==0){ eat(snake[i]['x'],snake[i]['y']-1); checkDie(snake[i]['x'],snake[i]['y']-1); } snake[i]['y']=snake[i]['y']-1; break; } } $("#mapItem_"+snake[0]['x']+'_'+snake[0]['y']).addClass('snakehead'); } function corners(x,y){ var _d=''; for(var i in corner){ if(corner[i]['x']==x&&corner[i]['y']==y){ _d=corner[i]['d']; } } return _d; } function clearCorners(x,y){ if(corner[0]['x']==x&&corner[0]['y']==y){ $("#mapItem_"+corner[0]['x']+'_'+corner[0]['y']).removeClass('corner'); corner.shift(); cornerNum=corner.length; } } function checkDie(x,y){ if(x<0||x>mapItemX-1||y<0||y>mapItemY-1){ clearInterval(timer); clearInterval(timeiner); var _scores=scores(); alert('你死了!總分:'+_scores.toFixed(2)); location.reload(); return false; } for(var i in snake){ if(snake[i]['x']==x&&snake[i]['y']==y){ clearInterval(timer); clearInterval(timeiner); var _scores=scores(); alert('你死了!總分:'+_scores.toFixed(2)); location.reload(); return false; } } } function eat(x,y){ if(mouseX==x&&mouseY==y){ var _snakeLast=snake[snakeLen-1]; snake[snakeLen]=new Array(); switch(_snakeLast['d']){ case 'E': snake[snakeLen]['x']=_snakeLast['x']-1; snake[snakeLen]['y']=_snakeLast['y']; break; case 'S': snake[snakeLen]['x']=_snakeLast['x']; snake[snakeLen]['y']=_snakeLast['y']-1; break; case 'W': snake[snakeLen]['x']=_snakeLast['x']+1; snake[snakeLen]['y']=_snakeLast['y']; break; case 'N': snake[snakeLen]['x']=_snakeLast['x']; snake[snakeLen]['y']=_snakeLast['y']+1; break; } snake[snakeLen]['d']=_snakeLast['d']; snakeLen=snake.length; $("#snake_len").html(snakeLen); $("#mapItem_"+mouseX+"_"+mouseY).removeClass('mouse'); initMouse(); changeSpeed(); } } function changeSpeed(){ if((snakeLen-5)%20==0){ speed=speed-10<30?30:speed-10; $("#snake_speed").html(speed); clearInterval(timer); timer=setInterval(move,speed); } } function initMouse(){ var _x,_y; var _this=this; this.randoms=function(){ _x=Math.floor(Math.random()*(mapItemX-1)); _y=Math.floor(Math.random()*(mapItemY-1)); _this.checkMouse(); } this.checkMouse=function(){ var _e=false; for(var i in snake){ if(snake[i]['x']==_x&&snake[i]['y']==_y){ _e=true; break; } } if(_e==true){ _this.randoms(); }else{ mouseX=_x; mouseY=_y; $("#mapItem_"+mouseX+'_'+mouseY).addClass('mouse'); } } this.randoms(); } function keyDirection(e){ var evt = e ||window.event; var key=evt.which||evt.keyCode; var notdo=false; var _snakeMoveDirection=snakeMoveDirection; switch(key){ case 37: if(snakeMoveDirection=='W'){ notdo=true; }else{ snakeMoveDirection='W'; } break; case 38: if(snakeMoveDirection=='N'){ notdo=true; }else{ snakeMoveDirection='N'; } break; case 39: if(snakeMoveDirection=='E'){ notdo=true; }else{ snakeMoveDirection='E'; } break; case 40: if(snakeMoveDirection=='S'){ notdo=true; }else{ snakeMoveDirection='S'; } break; } this.createCorner=function(){ corner[cornerNum]=new Array(); corner[cornerNum]['x']=snake[0]['x']; corner[cornerNum]['y']=snake[0]['y']; corner[cornerNum]['d']=snakeMoveDirection; $("#mapItem_"+corner[cornerNum]['x']+'_'+corner[cornerNum]['y']).addClass('corner'); cornerNum++; } if(notdo==false){ if(cornerNum>0){ if(corner[cornerNum-1]['x']!=snake[0]['x']||corner[cornerNum-1]['y']!=snake[0]['y']){ this.createCorner(); }else{ snakeMoveDirection=_snakeMoveDirection; } }else{ this.createCorner(); } } } function timeing(){ if(timeSecond<60){ timeSecond++; $("#second").html(timeSecond); }else{ timeMinute++; $("#minute").html(timeMinute); timeSecond=0; $("#second").html(timeSecond); } timestr++; } function scores(){ var _snakeLen=snakeLen-5 return Math.abs(_snakeLen*_snakeLen-timestr)/timestr*1000/speed; }