html+css+js适合前端小白的实战全解(超详细)——2048小游戏(四)

4.1游戏循环

大多数游戏采用的是基于时间的游戏循环
这种循环会随着时间的流逝更新游戏数据
根据时间流逝判断游戏是否结束
比如:星际争霸、魔兽争霸

另一类是基于玩家响应的游戏循环
这种游戏循环是基于玩家响应而更新游戏数据
玩家响应后继续判断游戏是否结束
比如:棋类游戏,2048游戏
html+css+js适合前端小白的实战全解(超详细)——2048小游戏(四)_第1张图片

4.2 基于玩家响应的游戏循环

那么我们怎么接收玩家的操作呢?也就是说,玩家如何控制游戏的进行?
2048游戏是玩家通过键盘上下左右操作,进而推进游戏的继续,于是,我们需要获取玩家的按键信息
这里,我们需要使用keydown事件

$(document).keydown(function(event){};

我们传入event参数,可以获得按键信息
接着,我们用switch语句来判断玩家按下的是上下左右的哪个键

$(document).keydown(function(event){
    switch(event.keyCode){};
    case 37: ;break;
    case 38: ;break;
    case 39: ;break;
    case 40: ;break;
}

37、38、39、40 分别表示 左上右下
如果按下左,则会向左移动,创建一个moveleft()函数,让number-cell左移动

但是别忘了,我们移动之前,还得判断一下可不可以移动,如果左边没有空格,则移动不了。

所以我们还需要在moveleft()函数里判断是否能左移。我们将moveleft()放在if语句里,如果左移之后,则产生新的随机数,我们调用generateOneNumber()函数,每次新增一个随机数后,可能都会导致游戏的结束。我们还要判断,此时游戏是否结束了?
我们加上isgameover()函数来判断。

case 37:
            if (moveleft()){
                generateOneNumber();
                isgameover();
            };
            break;

同理case 38、39、40,请自行补充完整。

4.3 交互设计

重点来了,moveleft(同理另外三个)是这个小游戏的核心。
首先,我们需要判断当前局势是否能够左移,我们使用一个新的函数canMoveLeft(board),如果返回是真,则可以向左移动。如果返回假,则不可以左移,我们return false就可以了。
我们需要把当前局势(board)作为参数传进去。

    if(!canMoveLeft(board))
        return false;

一起分析一下,如何判断此时是否能向左移动?
html+css+js适合前端小白的实战全解(超详细)——2048小游戏(四)_第2张图片
其实第一列我们是不用判断的,因为第【0】列的数字不能再向左移了,所以我们只需要考虑绿色的12个格子是否能左移。
判断能否左移,首先得找左边有没有空位,或者根据游戏规则,左边的数字是否和自己相等,相等则可以左移合成更大的数字。

1. 该数字左边是否没有数字?

2. 左边数字是否和自己想等?

只有满足其中之一,则才能左移。

我们把 canMoveLeft(board) 放在support2048.js里,先进行一次双重循环,遍历12个元素。

这里需要注意:j是从1开始,因为第0列不需要考虑

  1. 如果满足自身是一个不为0的数
  2. 如果自身左边的数为0 或 左边的数与自己相等
function canMoveLeft(board) { 
    for(var i = 0; i < 4; i++)
        for(var j = 1; j <4 ; j++){
            if(board[i][j]!= 0) 
                if(board[i][j-1] == 0 || board[i][j-1] == board[i][j] )
                    return true;
        }
    return false;
}

判断完是否能向左移动后,我们才真正开始向左移动了。
向左移动的函数 比 判断是否能向左移动的函数 要复杂得多。
html+css+js适合前端小白的实战全解(超详细)——2048小游戏(四)_第3张图片

  1. 落脚位置是否为空,即左边有空格子
    如(1,0)(1,1)(1,2)都是空的,(1,3)格子可以移到(1,0)处

  2. 落脚位置数字和自身元素数字相等,则可以左移
    如(1,2)和(1,3)相等,(1,3)格子可以左移与(1,2)结合成更大的数

  3. 移动的路途中不能有障碍物
    如(1,1)(1,2)都是空的,(1,3)格子可以移到(1,1)处

我们回到moveLeft()函数,我们依旧对3列格子进行遍历,依次循环,j仍然从1开始,只要待判断的(i,j)数不为0,则有机会左移。

我们需要对(i,j)左边的数依次考察,则需要引入一个新的索引变量k,再做一次循环,从0开始一直走到<j 的位置

这里是引用for(var i = 0; i < 4; i++)
for(var j = 1; j <4 ; j++){};

左移的第一种方式,我们要(i,k)的值为0,而且 k 到 j 之间没有障碍物。

要判断是否有障碍物,我们又要进入一个新的函数noBlockHorizontal(i,k,j,board), 引入4个参数,表示第 i 行,从 k 到 j 列是否有障碍物,如果没有障碍物,则可以产生一次移动。 如果产生了移动,则可以返回continue。

if(board[i][k] == 0 && noBlockHorizontal(i,k,j,board)){
                        //move
                        continue;
                    }

左移的第二种方式:board[i][k]==board[i][j] ,而且 k 到 j 之间没有障碍物

else if(board[i][k] == board[i][j] && noBlockHorizontal(i,k,j,board)){
                        //move
                        //add
                        continue;

第二种方式左移后,还会产生叠加,这个我们先留着,之后再完善。

4.4 交互逻辑调试

4.41 noBlockHorizontal()函数

我们在support2048.js 里定义noBlockHorizontal(i,k,j,board)函数

noBlockHorizontal ( row , col1 , col2 , board )

表示判断第 row 行, 第 col1 到第 col2 列 之间是否无障碍物。

此时我们需要判断第 k +1 列到第 j 列之间没有障碍物就可以了,一旦有一个数不为0 ,则返回false,否则返回true。

function noBlockHorizontal(row, col1, col2, board){
    for(var i = col1 + 1 ; i < col2; i++){
        if( board[row][i] != 0 )
            return false;
    }
    return true;
}

4.42 showAnimation()函数

下面回到moveLeft()这个函数继续编写,确定能向左移后,我们给移动编写一个动画效果,新增一个函数showMoveAnimation( i, j, i ,k);

我们把board[i][j]的值赋值给board[i][k],并让board[i][j] 变为0

board[i][k] = board[i][j];
board[i][j] = 0;

第二种方式也是类似,移动时有一个动画效果。但是却还有一个add叠加效果。

我们都知道,叠加的数是两个数相加,相加后board[i][j]也为0

board[ i ] [ k ] += board[ i ] [ j ];
board[ i ] [ j ] = 0;

当然,这样也是可以的

board[ i ] [ k ] *= 2;
board[ i ] [ j ] = 0;


左移的两种情况是这样的:

if(board[i][k] == 0 && noBlockHorizontal(i,k,j,board)){
                        //move
                        showMoveAnimation(i, j, i, k);
                        board[i][k] = board[i][j];
                        board[i][j] = 0;
                        continue;
                    }
                    else if(board[i][k] == board[i][j] && noBlockHorizontal(i,k,j,board)){
                        //move
                        showMoveAnimation(i, j, i, k);
                        //add
                        board[i][k] *= 2;
                        board[i][j] = 0;

                        continue;
                    }

4.43 showMoveAnimation()函数

我们再去showanimateion2048.js定义showMoveAnimation(i, j, i, k) 函数

function showMoveAnimation(fromx, fromy, tox, toy){};

首先我们要传入原始xy,和目的xy,再引入numberCell。使用animate()动画函数加上getPosTop()和getPosLeft()来实现。

function showMoveAnimation(fromx, fromy, tox, toy){
    var numberCell = $('#number-cell-' + fromx + '-' + fromy);
    numberCell.animate({
        top:getPosTop(tox,toy),
        left:getPosLeft(tox,toy)
    },200);
}

至此,我们仍遗留有2个小问题

  1. 移动时我们操作的都是board这个数据,我们最后需要调用updateBoardView(),对整体数据进行刷新。
function moveleft(){
    if(!canMoveLeft(board))
        return false;

    for(var i = 0; i < 4; i++)
        for(var j = 1; j <4 ; j++){
            if(board[i][j] != 0){
                for(var k = 0; k < j; k++){
                    if(board[i][k] == 0 && noBlockHorizontal(i,k,j,board)){
                        //move
                        showMoveAnimation(i, j, i, k);
                        board[i][k] = board[i][j];
                        board[i][j] = 0;
                        continue;
                    }
                    else if(board[i][k] == board[i][j] && noBlockHorizontal(i,k,j,board)){
                        //move
                        showMoveAnimation(i, j, i, k);
                        //add
                        board[i][k] *= 2;
                        board[i][j] = 0;

                        continue;
                    }
                
                }
            }

        }

    updateBoardView();
    return true;  
}
  1. 我们在main2048里,keydown事件里还新建了一个isgameover() 函数,对于这个函数,我们暂时先不实现,先把它声明出来

function isgameover() { };

我们现在来调试运行一下,目前只能左移。
html+css+js适合前端小白的实战全解(超详细)——2048小游戏(四)_第4张图片

我们发现我们设置的showMoveAnimation()动画似乎没有起作用,这是为什么呢?

因为我们虽然设置了200ms的动画,但是对于for循环,计算机在瞬间就可以执行完毕
完成后,将立即执行updateBoardView()函数
动画还没来得及播放,就已经被刷新了。

为了解决这个问题,我们需要执行完动画,再执行updateBoardView()这个函数。
我们使用settimeout()函数,使其等待200ms再执行updateBoardView()。

setTimeout( 'updateBoardView()',200);

到这里,大家可以再细细琢磨一下。如果大家都琢磨透了,就自己尝试写一下

  1. moveLeft()以外的另外三个函数
  2. canMoveLeft()以外三个函数
  3. 在垂直方向的noBlockVertical()函数

特别要分清楚 i 、j 、k 的位置,不清楚的最好画个草稿理清一下思路

如果写好的,现在试着运行一下,我们已经可以上下左右操作游戏了
如果报错的可以拉到文章最后,跟代码比较一下,自己哪里出错了
弄清楚为什么会错,再继续往下

如果运行成功,我们离游戏的完成就差一小步了
html+css+js适合前端小白的实战全解(超详细)——2048小游戏(四)_第5张图片

我们现在需要对整个游戏进行收尾工作

还记得我们在 main2048.js 里的**isgameover()**函数吗?
满足游戏结束需要同时满足什么条件呢?

  1. 没有空间 ->>> nospace( board )
  2. 不能移动 ->>> nomove( board )

nospace()函数我们之前已经定义过了
我们现在新定义一个nomove函数,用来表示四周的数与本身不能再结合导致不能移动操作

4.44 isgameover()函数

满足两个条件,则游戏真正**gameover()**了

function isgameover(){
    if( nospace(board) && nomove(board)){
        gameover();
    }
}

下面我们来写gameover()

function gameover() { 
    alert("Game over!")
}

如果游戏结束,就弹出gameover提示
我们对这个函数不做过多的设计,大家可以自己发挥

4.45 nomove()函数

接下来我们来写nomove(),我们放在support2048.js里
其实有了之前的函数作为基础,nomove()函数非常好写

我们思考一下,移动指的是什么呢?
->>> 即 能 向上 or 向下 or 向左 or 向右 移动
->>> 即 **canMoveLeft(board) || canMoveRight(board) || canMoveUp(board) || canMoveDown(board) **

所以只要判断能移动,函数就return false,指的是可以移动
相反,如果上下左右都不能移动,则return true,指的是不可以移动

function nomove( board ){
    if( canMoveLeft( board ) ||
        canMoveRight( board ) ||
        canMoveUp( board ) ||
        canMoveDown( board ) )
        return false;

    return true;
}

到这里,细心的朋友会发现有点小问题,就是和之前的这个问题一样 ↓

setTimeout(“updateBoardView()”,200);

于是我们把这里的 随机生成一个数的generateOneNumber()函数isgameover()函数
都加上setTimeout(),这样动画就可以流畅播放了
html+css+js适合前端小白的实战全解(超详细)——2048小游戏(四)_第6张图片

好了,这一小节结束了,下一小节就是最后最后的工作了
大家坚持到这里真的不容易呀
赶紧来检查一下自己的代码吧

由于 2048.html 和 2048.css 两个文件不变
我就不放上来了,需要的朋友可以去上一节查看

main2048.js

/**
 * Created by liuyubobobo on 14-4-11.
 * my site: http://www.liuyubobobo.com
 */

var board = new Array();
var score = 0;

$(document).ready(function(){
    newgame();
});

function newgame(){
    //初始化棋盘格
    init();
    //在随机两个格子生成数字
    generateOneNumber();
    generateOneNumber();
}

function init(){
    for( var i = 0 ; i < 4 ; i ++ )
        for( var j = 0 ; j < 4 ; j ++ ){

            var gridCell = $('#grid-cell-'+i+"-"+j);
            gridCell.css('top', getPosTop( i , j ) );
            gridCell.css('left', getPosLeft( i , j ) );
        }

    for( var i = 0 ; i < 4 ; i ++ ){
        board[i] = new Array();
        for( var j = 0 ; j < 4 ; j ++ ){
            board[i][j] = 0;
        }
    }

    updateBoardView();
}

function updateBoardView(){

    $(".number-cell").remove();
    for( var i = 0 ; i < 4 ; i ++ )
        for( var j = 0 ; j < 4 ; j ++ ){
            $("#grid-container").append( '
+i+'-'+j+'">
'
); var theNumberCell = $('#number-cell-'+i+'-'+j); if( board[i][j] == 0 ){ theNumberCell.css('width','0px'); theNumberCell.css('height','0px'); theNumberCell.css('top',getPosTop(i,j) + 50 ); theNumberCell.css('left',getPosLeft(i,j) + 50 ); } else{ theNumberCell.css('width','100px'); theNumberCell.css('height','100px'); theNumberCell.css('top',getPosTop(i,j)); theNumberCell.css('left',getPosLeft(i,j)); theNumberCell.css('background-color',getNumberBackgroundColor( board[i][j] ) ); theNumberCell.css('color',getNumberColor( board[i][j] ) ); theNumberCell.text( board[i][j] ); } } } function generateOneNumber(){ if( nospace( board ) ) return false; //随机一个位置 var randx = parseInt( Math.floor( Math.random() * 4 ) ); var randy = parseInt( Math.floor( Math.random() * 4 ) ); while( true ){ if( board[randx][randy] == 0 ) break; randx = parseInt( Math.floor( Math.random() * 4 ) ); randy = parseInt( Math.floor( Math.random() * 4 ) ); } //随机一个数字 var randNumber = Math.random() < 0.5 ? 2 : 4; //在随机位置显示随机数字 board[randx][randy] = randNumber; showNumberWithAnimation( randx , randy , randNumber ); return true; } $(document).keydown( function( event ){ switch( event.keyCode ){ case 37: //left if( moveLeft() ){ setTimeout("generateOneNumber()",210); setTimeout("isgameover()",300); } break; case 38: //up if( moveUp() ){ setTimeout("generateOneNumber()",210); setTimeout("isgameover()",300); } break; case 39: //right if( moveRight() ){ setTimeout("generateOneNumber()",210); setTimeout("isgameover()",300); } break; case 40: //down if( moveDown() ){ setTimeout("generateOneNumber()",210); setTimeout("isgameover()",300); } break; default: //default break; } }); function isgameover(){ if( nospace(board) && nomove(board)){ gameover(); } } function gameover() { alert("Game over!") } function gameover(){ alert('gameover!'); } function moveLeft(){ if( !canMoveLeft( board ) ) return false; //moveLeft for( var i = 0 ; i < 4 ; i ++ ) for( var j = 1 ; j < 4 ; j ++ ){ if( board[i][j] != 0 ){ for( var k = 0 ; k < j ; k ++ ){ if( board[i][k] == 0 && noBlockHorizontal( i , k , j , board ) ){ //move showMoveAnimation( i , j , i , k ); board[i][k] = board[i][j]; board[i][j] = 0; continue; } else if( board[i][k] == board[i][j] && noBlockHorizontal( i , k , j , board ) ){ //move showMoveAnimation( i , j , i , k ); //add board[i][k] += board[i][j]; board[i][j] = 0; continue; } } } } setTimeout("updateBoardView()",200); return true; } function moveRight(){ if( !canMoveRight( board ) ) return false; //moveRight for( var i = 0 ; i < 4 ; i ++ ) for( var j = 2 ; j >= 0 ; j -- ){ if( board[i][j] != 0 ){ for( var k = 3 ; k > j ; k -- ){ if( board[i][k] == 0 && noBlockHorizontal( i , j , k , board ) ){ showMoveAnimation( i , j , i , k ); board[i][k] = board[i][j]; board[i][j] = 0; continue; } else if( board[i][k] == board[i][j] && noBlockHorizontal( i , j , k , board ) ){ showMoveAnimation( i , j , i , k); board[i][k] *= 2; board[i][j] = 0; continue; } } } } setTimeout("updateBoardView()",200); return true; } function moveUp(){ if( !canMoveUp( board ) ) return false; //moveUp for( var j = 0 ; j < 4 ; j ++ ) for( var i = 1 ; i < 4 ; i ++ ){ if( board[i][j] != 0 ){ for( var k = 0 ; k < i ; k ++ ){ if( board[k][j] == 0 && noBlockVertical( j , k , i , board ) ){ showMoveAnimation( i , j , k , j ); board[k][j] = board[i][j]; board[i][j] = 0; continue; } else if( board[k][j] == board[i][j] && noBlockVertical( j , k , i , board ) ){ showMoveAnimation( i , j , k , j ); board[k][j] *= 2; board[i][j] = 0; continue; } } } } setTimeout("updateBoardView()",200); return true; } function moveDown(){ if( !canMoveDown( board ) ) return false; //moveDown for( var j = 0 ; j < 4 ; j ++ ) for( var i = 2 ; i >= 0 ; i -- ){ if( board[i][j] != 0 ){ for( var k = 3 ; k > i ; k -- ){ if( board[k][j] == 0 && noBlockVertical( j , i , k , board ) ){ showMoveAnimation( i , j , k , j ); board[k][j] = board[i][j]; board[i][j] = 0; continue; } else if( board[k][j] == board[i][j] && noBlockVertical( j , i , k , board ) ){ showMoveAnimation( i , j , k , j ); board[k][j] *= 2; board[i][j] = 0; continue; } } } } setTimeout("updateBoardView()",200); return true; }

support2048.js

function getPosTop(i,j){
    return 20 + i*120;
}
function getPosLeft(i,j){
    return 20 + j*120;
}

function getNumberBackgroundColor(number){
    switch(number){
        case 2:return "#eee4da"; break; 
        case 4:return "#ede0c8"; break; 
        case 8:return "#f2b179"; break; 
        case 16:return "#f59563"; break; 
        case 32:return "#f67c5f"; break; 
        case 64:return "#f65e3b"; break; 
        case 128:return "#edcf72"; break; 
        case 256:return "#edcc61"; break; 
        case 512:return "#9c0"; break; 
        case 1024:return "#33b5e5"; break; 
        case 2048:return "#09c"; break; 
        case 4096:return "#a6c"; break; 
        case 28192:return "#93c"; break; 
    }
    return "black";
}

function getNumberColor(number) { 
    if(number <= 4)
        return "#776e65";

    return "white";
 }

 function nospace(board) { 
     for(var i = 0; i < 4; i++)
        for(var j = 0; j < 4; j++)
            if(board[i][j] == 0)
                return false;

    return true;
  }

function canMoveLeft(board) { 
    for(var i = 0; i < 4; i++)
        for(var j = 1; j < 4 ; j++)
            if(board[i][j]!= 0) 
                if(board[i][j-1] == 0 || board[i][j-1] == board[i][j] )
                    return true;
        
    return false;
}

function canMoveRight( board ){

    // 只需要遍历12个元素,因为最右边的元素不能向左移了
    // 右边是否没有数字?
    // 右边数字是否和自己相等?
    for(var i = 0 ; i < 4 ;i ++)
        for( var j = 2 ; j >= 0 ; j --) //第0列不需要遍历
            if( board[i][j] !=0 )
                if( board[i][j+1] == 0 || board[i][j+1] == board[i][j] )
                    return true;
        
    return false;  
}

function canMoveUp( board ){

    // 只需要遍历12个元素,因为最上边的元素不能向上移了
    // 上边是否没有数字?
    // 上边数字是否和自己相等?
    for(var j = 0 ; j < 4 ;j ++)
        for( var i = 1 ; i < 4 ; i ++) //第0行不需要遍历
            if( board[i][j] !=0 )
                if( board[i-1][j] == 0 || board[i-1][j] == board[i][j] )
                    return true;
        
    return false;  
}

function canMoveDown( board ){

    // 只需要遍历12个元素,因为最下边的元素不能向下移了
    // 下边是否没有数字?
    // 下边数字是否和自己相等?
    for(var j = 0 ; j < 4 ;j ++)
        for( var i = 2 ; i >= 0 ; i --) //第0行不需要遍历
            if( board[i][j] != 0 )
                if( board[i+1][j] == 0 || board[i+1][j] == board[i][j] )
                    return true;
        
    return false;  
}



function noBlockHorizontal(row, col1, col2, board){
    for(var i = col1 + 1 ; i < col2; i++)
        if( board[row][i] != 0 )
            return false;
    
    return true;
}

function noBlockVertical(col, row1, row2, board){
    for(var i = row1 + 1 ; i < row2; i++)
        if( board[i][col] != 0 )
            return false;
    
    return true;
}

function nomove() { 
    if(canMoveDown(board) || canMoveLeft(board) || canMoveRight(board) || canMoveUp(board) || )
        return false;
    
    return true;
 }

showanimation2048.js

function showNumberWithAnimation(i, j, randNumber){
    var numberCell = $('#number-cell-'+i+'-'+j);
    numberCell.css('background-color',getNumberBackgroundColor(randNumber));
    numberCell.css('color',getNumberColor(randNumber));
    numberCell.text(randNumber);

    numberCell.animate({
        width:"100px",
        height:"100px",
        top:getPosTop(i,j),
        left:getPosLeft(i,j)
    },50);
}

function showMoveAnimation(fromx, fromy, tox, toy){
    var numberCell = $('#number-cell-' + fromx + '-' + fromy);
    numberCell.animate({
        top:getPosTop(tox,toy),
        left:getPosLeft(tox,toy)
    },200);
}

我们最后一小节再见哦!
html+css+js适合前端小白的实战全解(超详细)——2048小游戏(五)

你可能感兴趣的:(2048小游戏,游戏,javascript,html,css)