web--2048小游戏复现(慕课网)

这周断断续续完成了2048小游戏的网页版,这里主要讲这款游戏的逻辑和代码实现。
参考:慕课网2048私人订制 https://www.imooc.com/learn/76
对着视频把源码敲了一遍,这里作一个思路整理。

一、整体思路

1、游戏页面结构及样式
2、2048游戏逻辑
3、手机端优化
思路可以划分为上三步,在第一步中,首先规划好整体的排版。
在这里插入图片描述

页面内容包括2048header、游戏开始button、得分字段

、整个4*4方格grid-container。在container中每一个cell后期都要绑定一个number。

二、游戏页面结构及样式

2048,开始、得分
设置text-align:center;margin左右auto居中,字体背景颜色等;

<header>
	<h1>2048</h1>
	<a href="javascrpit:newgame();">开始</a>
	<p>得分</p>
</header>

4*4方格,盒子模型

<div id="grid-container">
	<div id="grid-cell-0-0>
	</div>
</div>
//grid-cell-0-0到3-3

container设置width、height、background,bord-radius边框,padding,margin、position(relative相对定位,相当于浮动)
grid-cell设置width,height、background-color、position(absolute绝对定位,可以被覆盖)
number-cell覆盖每个grid-cell,,其中的数字为border-number,初使为0

三、2048游戏逻辑

1、newgame包括:init初始化游戏,生成gridcell4*4,number-cell清零,随机两个位置,generateOneNumber两次。
(0)生成页面结构后,加载DOM(文档对象模型)。通过$(document).ready()完成,ready事件在生成html标签、元素,显示CSS样式后实现。在ready()事件中执行newgame函数,该函数链接到《开始》按钮。
(1)init:通过类选择器number-cell $(.number-cell).remove()清零,对grid-container中添加number-cell,number-cell中的数字都存在board【】【】二维数组中。
(2)generateOneNumber中随机生成两个位置,和随机2或4,
board[randx][randy]=randNumber;

2、moveLeft、moveRight、moveUp、moveDown键盘操作。moveLeft发生,所有数字左移,相同数字求和。
(0)按下键盘的↑↓←→由KaTeX parse error: Expected 'EOF', got '&' at position 171: …,board[i][k]!=0&̲&在左移的这段(k~j)距离,…(’#score’).text(score)使得在得分:0处显示分数。
4、game over,判断所有border-number不为零(没有空格),且canMoveLeft等四个函数return false(不能移动)。

四、手机端优化

(0)在手机上与pc端的主要区别:页面大小、控制方式(按键盘和触控)
(1)为了适应手机和pc,加一个获取屏幕大小的函数documentWidth=window.screen.availWidth,if 屏幕宽度大于500像素,认为是PC,grid-container按500像素显示,小于500,按手机来自适应。width和height按%百分比显示。
(2)触控用document.addEventListener监听,包括touchstart、touchmove和touchend。
(3)touchstart获取手指初始位置;touchmove是优化,使得手指点击无效;touchend获取手指离开屏幕的位置,通过xy坐标和滑动产生的位移来判断,执行moveLeft等上下左右四个函数。

注:bug修改
在一排四个格子出现2248这样的情况下,左移结果应该是4480,但是在三、2、(2)中,结果是16000,应该添加hasConflicted二维数组,判断是否加过,保证只加一次。
最后,附上慕课中给的源代码,仅作少量修改,侵删
整体结构:
在这里插入图片描述
index.html

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="
            width=device_value,
            height=device_value,
            initial-scale=1.0,
            minimum-scale=0.9,
            maxmum-scale=1.0,
<!--            //用户可以缩小或放大屏幕尺寸的最小最大值-->
            user-scalable = yes"
          />

    <title>Title</title>
    <link rel="stylesheet" type="text/css" href="2048.css">
<!--    <script type="text/javascript" src="https://code.jquery.com/jquery-3.3.1.min.js"></script>-->
<!--    <script type="text/javascript" src="http://libs.baidu.com/jquery/1.9.0/jquery.min.js"></script>-->
    <script type="text/javascript" src="jquery.min.js"></script>
    <script type="text/javascript" src="main2048.js"></script>
    <script type="text/javascript" src="showanimation.js"></script>
    <script type="text/javascript" src="support2048.js"></script>
</head>
<body>
    <header>
        <h1>2048</h1>
        <a href="javascript:newgame();" id="newgamebutton">开始</a>
        <p>得分: <span id="score">0</span>  </p>
    </header>

    <div id="grid-container">
        <div class="grid-cell" id="grid-cell-0-0"></div>
        <div class="grid-cell" id="grid-cell-0-1"></div>
        <div class="grid-cell" id="grid-cell-0-2"></div>
        <div class="grid-cell" id="grid-cell-0-3"></div>

        <div class="grid-cell" id="grid-cell-1-0"></div>
        <div class="grid-cell" id="grid-cell-1-1"></div>
        <div class="grid-cell" id="grid-cell-1-2"></div>
        <div class="grid-cell" id="grid-cell-1-3"></div>

        <div class="grid-cell" id="grid-cell-2-0"></div>
        <div class="grid-cell" id="grid-cell-2-1"></div>
        <div class="grid-cell" id="grid-cell-2-2"></div>
        <div class="grid-cell" id="grid-cell-2-3"></div>

        <div class="grid-cell" id="grid-cell-3-0"></div>
        <div class="grid-cell" id="grid-cell-3-1"></div>
        <div class="grid-cell" id="grid-cell-3-2"></div>
        <div class="grid-cell" id="grid-cell-3-3"></div>


    </div>
</body>
</html>
<!--MVC结构:Model模型、visual界面UI、Control逻辑控制-->

<!--待优化:-->
<!--1、game over-->
<!--2、最高得分-->
<!--3、加分动画-->
<!--可以通过phoneGap将web APP转换为native app-->
<!--网站发布:dropBox&dropPages-->
<!--或者发布方式:nginx+花生壳,存在nginx中即可。-->

2048.css

header{
    display: block;
    margin: 0 auto;
    width:100%;
    text-align: center;
}
header h1{
    font-family: Arial;
    font-size: 40px;
    font-weight:bold ;
}
header #newgamebutton{
    display: block;
    margin: 20px auto;
    width: 100px;
    padding:10px 10px;
    /*background-color: #9efff4;*/
    background-color: chocolate;
    font-family: Arial;
    color: white;
    border-radius: 10px;
    text-decoration: none;
}
header #newgamebutton:hover{
    background-color: cyan;
}

header p{
    font-family:Arial;
    font-size: 25px;
    margin: 20px auto;
}

#grid-container{
    width:460px;
    height: 460px;
    padding:20px;
    /*上下左右各自向外20px,盒子为500*500*/

    margin: 50px auto;
    /*background-color: #bbada0;*/
    background-color: #9efff4;
    background-color: rgba(22,27,255,0.9);
    border-radius: 10px;
    position: relative;

}
.grid-cell{
    width: 100px;
    height: 100px;
    border-radius: 6px;
    /*background-color: #ccc0b3;*/
    background-color: yellowgreen;
    position: absolute;
}
.number-cell{
    border-radius:6px;
    font-family: Arial;
    font-size: 60px;
    font-weight: bold;
    font-height:100px;
    text-align:center;

    position:absolute;
}

main2048.js

var board = new Array();
var score =0;
var hasConflicted = new Array();
//用于判断每个小格子是否发生了变化
//游戏初始化---------------------------------
$(document).ready(function(){
    prepareForMobile();
    newgame();
});
function prepareForMobile() {
    if( documentWidth > 500 ){
        gridContainerWidth = 500;
        cellSpace = 20;
        cellSideLength = 100;
    }
    $('#grid-container').css('width',gridContainerWidth-2*cellSpace);
    $('#grid-container').css('height',gridContainerWidth-2*cellSpace);
    $('#grid-container').css('padding',cellSpace);
    $('#grid-container').css('border-radius',0.02*gridContainerWidth)

    $('.grid-cell').css('width',cellSideLength);
    $('.grid-cell').css('height',cellSideLength);
    $('.grid-cell').css('border-radius',0.02*cellSideLength);

    
}
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));
            //每个方格的位置由这两个函数决定,在support2048中
        }

    for(var i = 0;i<4;i++){
        board[i]=new Array();
        hasConflicted[i] = new Array();
        for(var j=0;j<4;j++){
            board[i][j]=0;
            hasConflicted[i][j]= false;
        }
    }
    updateBoardView();
    score = 0;
}
function  updateBoardView(){
    $(".number-cell").remove();
    // 删除当前所有方格中的值
    for (var i=0;i<4;i++)
        for (var j=0;j<4;j++){
            $("#grid-container").append('<div class="number-cell" id="number-cell-'+i+'-'+j+'"></div>');
            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)+cellSideLength/2);
                theNumberCell.css('left',getPosLeft(i, j)+cellSideLength/2);
            }else{
                // 不为零时用number-cell代替gridcell
                theNumberCell.css('width',cellSideLength);
                theNumberCell.css('height',cellSideLength);
                theNumberCell.css('top',getPosTop(i, j));
                theNumberCell.css('left',getPosLeft(i, j));
                theNumberCell.css('background-color',getNumberBackgroudColor(board[i][j]));
                theNumberCell.css('color',getNumberColor(board[i][j]));
                // 背景色和前景色
                theNumberCell.text(board[i][j]);
            }

            hasConflicted[i][j]= false;
        }
    $('.number-cell').css('line-height',cellSideLength+'px');
    $('.number-cell').css('font-size',0.6*cellSideLength+'px');

}
function generateOneNumber(){//先随机找一个位置,再在这个位置上赋值
    if (nospace(board))
        return false;
    //随机一个位置
    var randx=parseInt(Math.floor(Math.random()*4));
    var randy=parseInt(Math.floor(Math.random()*4));
      //产生0-4直接的浮点数,floor向下取整0123,parseInt强制类型转换为int
    var time=0;

    while(time<30){ //===========================================================不懂
        if (board[randx][randy]==0)
            break;
        randx=parseInt(Math.floor(Math.random()*4));
        randy=parseInt(Math.floor(Math.random()*4));
        time++;
    }
    if(time>30){
        for(var i=0;i<4;i++)
            for(var j=0;j<4;j++)
                if(board[i][j]==0){
                    randx=i;
                    randy=j;
                }
    }
    //随机一个数字2,4
    var randNumber = Math.random() <0.5? 2:4;//随机数小于0.5为2,大于0.5为4

    //显示
    board[randx][randy]=randNumber;
    showNumberWithAnimation(randx, randy, randNumber);
    return true;
}
$(document).keydown(function (event) {
    event.preventDefault();//阻挡按键本来会产生的效果,这里防止屏幕滚动条上下移动
    switch (event.keyCode) {
        case 37: //left,向左移动,生成新的数,判断游戏是否结束
            if (moveLeft()){
                setTimeout("generateOneNumber()",200);
                setTimeout("isgameover();",200);
                //设置延时,让alert有出现 的效果
            }
            break;
        case 38://up
            if (moveUp()){
                setTimeout("generateOneNumber()",200);
                setTimeout("isgameover();",200);
            }
            break;
        case 39://right
            if (moveRight()){
                setTimeout("generateOneNumber()",200);
                setTimeout("isgameover();",200);
            }
            break;
        case 40://down
            if (moveDown()){
                setTimeout("generateOneNumber()",200);
                setTimeout("isgameover();",200);
            }
            break;
        default:
            break;

    }
})
document.addEventListener('touchstart',function (event) {
    startx=event.touches[0].pageX;//event.touches获取多个手指触碰屏幕的信息【0】为一个手指
    starty=event.touches[0].pageY;
});
document.addEventListener('touchmove',function (event) {
    event.preventDefault();
});
document.addEventListener('touchend',function (event) {
    endx=event.changedTouches[0].pageX;
    endy=event.changedTouches[0].pageY;
    var deltax=endx-startx;
    var deltay=endy-starty;

    if(Math.abs(deltax)<0.3*documentWidth && Math.abs(deltay)<0.3*documentWidth)
        return;
    //x
    if(Math.abs(deltax)>=Math.abs(deltay)){
        if(deltax>0){
            //向右
            if (moveRight()){
                setTimeout("generateOneNumber()",200);
                setTimeout("isgameover();",200);
            }
        }else{
            //向左
            if (moveLeft()){
                setTimeout("generateOneNumber()",200);
                setTimeout("isgameover();",200);
                //设置延时,让alert有出现 的效果
            }
        }

    }
    else{
        //y
        if(deltay>0){
            //向下
            if (moveDown()){
                setTimeout("generateOneNumber()",200);
                setTimeout("isgameover();",200);
            }
        }else{
            //向上
            if (moveUp()){
                setTimeout("generateOneNumber()",200);
                setTimeout("isgameover();",200);
            }
        }
    }
});
function isgameover() {
    if (nospace(board)&&nomove(board)){
        gameover();
    }
}
function gameover() {
    alert('Game Over');
}
//--------------------------------------------------------Left-------------------------------------
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){//不为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)&& !hasConflicted[i][k]){
                    // else if (board[i][k]==board[i][j] &&noBlockHorizontal(i, k, j, board)){
                        //hasConflicted[i][k]没变化过,即没有发生add
                        //move
                        showMoveAnimation(i, j,i, k);
                        //add
                        board[i][k] +=board[i][j];
                        board[i][j]=0;
                        score+=board[i][k];
                        updateScore(score);
                        hasConflicted[i][k]=true;//只add一次
                        continue;
                    }
                }
            }
        }
    setTimeout("updateBoardView()",200);//设置更新在200ms后发生,move动画就可以显示
    return true;
}
//--------------------------------------------------------Right-------------------------------------

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 )&& !hasConflicted[i][k] ){
                    // 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;
                        score+=board[i][k];
                        updateScore(score);
                        hasConflicted[i][k]=true;

                        continue;
                    }
                }
            }
        }

    setTimeout("updateBoardView()",200);
    return true;
}
function moveUp() {
    if (!canMoveUp(board)){
        return false;
    }
    //
    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 && noBlockupdown(j, k, i, board)) {
                        //move
                        showMoveAnimation(i, j, k, j);
                        board[k][j] = board[i][j];
                        board[i][j] = 0;
                        continue;
                    }
                    // else if (board[k][j]==board[i][j]&&noBlockupdown(j, k, i, board)&& !hasConflicted[k][j]) {
                    else if (board[k][j]==board[i][j]&&noBlockupdown(j, k, i, board)) {
                        //move
                        showMoveAnimation(i,j, k, j);

                        //add
                        board[k][j]+=board[i][j];
                        board[i][j]=0;
                        score+=board[k][j];
                        updateScore(score);
                        // hasConflicted[k][j]=true;

                        continue;
                    }
    setTimeout("updateBoardView()",200);
    return true;
}
function moveDown() {
    if (!canMoveDown(board)){
        return false;
    }
    //
    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&&noBlockupdown(j, i, k, board)){
                        //move
                        showMoveAnimation(i, j, k, j);
                        board[k][j]=board[i][j];
                        board[i][j]=0;
                    }
                    // else if (board[i][j]==board[k][j]&&noBlockupdown(j, i, k, board) && !hasConflicted[k][j]){
                    else if (board[i][j]==board[k][j]&&noBlockupdown(j, i, k, board)){
                        //move,add
                        showMoveAnimation(i, j, k, j)
                        board[k][j]+=board[i][j];
                        board[i][j]=0;
                        score+=board[k][j];
                        updateScore(score);
                        // hasConflicted[k][j]=true;

                    }
            }
    setTimeout("updateBoardView()",200);
    return true;

}

support2048.js

documentWidth=window.screen.availWidth;
gridContainerWidth=0.92*documentWidth;
cellSideLength=0.18*documentWidth;
cellSpace=0.04*documentWidth;

function getPosTop(i,j) {
    return cellSpace + (cellSpace+cellSideLength)*i;
}
function getPosLeft(i,j) {
    return cellSpace + (cellSpace+cellSideLength)*j;

}

function getNumberBackgroudColor(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 8192: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)//默认初始值为0,为0说明还有空格
                return false;
    return true;
}

function noBlockHorizontal(row, col1,col2,board) {
    for (var i=col1+1;i<col2;i++)
        if (board[row][i]!=0)
            return false;
    return true;
}
function noBlockupdown(col, row1,row2,board) {
    for (var i=row1+1;i<row2;i++)
        if (board[i][col]!=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)//这个点不为0判断要向左移动
                if (board[i][j-1]==0 || board[i][j-1]==board[i][j])//左侧是空的或者左右相等
                    return true;
        }
    return false;
}
function canMoveRight(board){
    for (var i=0;i<4;i++)
        for (var j=2;j>=0;j--)
            if (board[i][j]!=0)
                if (board[i][j+1]==board[i][j] || board[i][j+1]==0)
                    return true;
    return false;
}

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

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

showanimation.js 移动的动画效果

function showNumberWithAnimation(i, j, randNumber) {
    var numberCell=$('#number-cell-'+i+"-"+j);

    numberCell.css('background-color', getNumberBackgroudColor(randNumber));
    numberCell.css('color', getNumberColor(randNumber));
    numberCell.text(randNumber);

    numberCell.animate({
        // animate函数是jQUERY中的动画函数,动画渐变50ms
        width:cellSideLength,
        height:cellSideLength,
        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);
}
function updateScore(score) {
    $('#score').text(score);

}
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值