一塊兒來作webgame,《Javascript貪食蛇》

2016-09-22更新:html

使用canvas實現:https://github.com/onlyfu/SnakeSir-Javascriptgit

 

如下爲HTML4實現:github

今天來個略有意思的,《貪食蛇》。這個估計沒有人沒玩過吧。它稍有點難度,不過仍然算是簡單的遊戲,實現代碼也很少,下面就開始canvas

試玩

這裏效果好點數組

說明:點擊屏幕中間開始,使用方向鍵控制小蛇,再次點擊屏幕暫停。每吃20個老鼠速度增長10ms,祝你好運!
蛇長: 5 格 速度: 100 ms 時間: 00
分數: 0
正在努力加載...

思路

貪食蛇主要幾個問題須要解決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');
}

 

移動的代碼稍多,主要是思路是:

  • 每一個蛇身點,都有三個值:一、所在橫座標。二、所在縱座標。三、下一步移動方向。移動方向是個很重要的值,初始化蛇的時候就已經根據初始前進方向設定了
  • 普通移動只須要將蛇頭前進方向的下一個點記爲新的蛇頭,而蛇尾的點從蛇身上移除
  • 轉角移動,需考慮目標方向,corner裏記錄有轉角的座標和方向,那麼,當蛇身上的點通過轉角點的時候,根據它的方向重設蛇身點的方向,根據不一樣的方向增長或減小對應的座標值。每一個點通過它的時候,都會作這樣的事情,因此整條蛇就轉過去了。代碼中的switch,就是來作這個事情的。
  • 每次移動,都對第一個點作判斷:一、是否是吃到了,eat()。二、是否是死掉了,checkDie()。它們都有兩個參數,分別是目標點的座標,因此,4個方向都調用了一次這兩個方法。

判斷轉角

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;
}
View Code
相關文章
相關標籤/搜索