欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

用JS实现2048小游戏

程序员文章站 2022-10-16 09:54:52
前言: 一年来一直做得是后端的东西,没有写前端代码,忘干净了,之前也只用jQuery,好歹还能做点效果。想着捡起来一点,要不然枉费了师傅的栽培。 一直在看博客,但没有属于自己的东西。this has to change. why not start now?俗话说只要代码敲得够快,悲伤就追不上我。 ......

前言:

一年来一直做得是后端的东西,没有写前端代码,忘干净了,之前也只用jQuery,好歹还能做点效果。想着捡起来一点,要不然枉费了师傅的栽培。

一直在看博客,但没有属于自己的东西。this has to change.

why not start now?俗话说只要代码敲得够快,悲伤就追不上我。

此篇开博,从简单小游戏开始。目的是做出一些可以快点看到效果的东西,捡回一点程序员的信心。

 

先看最终效果:

用JS实现2048小游戏

 

 

1. 2048游戏功能分析:

核心是4个方向的移动的处理。

往一个方向移动,先看是否有数字的合并。一次移动,一行只允许合并一次,且从终点往起点合并。

合并完成之后,将合并后的数字和其他数字移动,贴边。

在空白位置随机生成一个数字2,移动结束。

 

算法实现:

考虑采用二维数组存储4*4个格子中的值,如果移动,只需要更新数组,然后用数组的值来更新dom元素即可。

(这种想法不太面向对象。要是将每个格子都理解为一个对象,不知道好不好做,等以后研究--TODO)

 

2. 要用到的东西

2.1 html页面布局,4*4个方块得整出来

2.2 简单css:不同的数字设置不同颜色

2.3 简单dom操作:取元素,变换显示的数字,改变class

2.4 取随机数算法

2.5 按键事件监听

2.6 一点点交互操作考虑,比如判定游戏失败结束,成功结束,返回上一步。

 

3. 代码结构:

用JS实现2048小游戏

 

较为粗糙,js几乎全部是直接用html引用了,不会其他方式。先实现功能;

几个图片是:返回上一步按钮,上下左右按钮,重新开始按钮。

js区分:

arrayHelper是操作数组的;

domOperation顾文思意,操作dom;

eventListener,处理按键事件

common:页面初始化,随机数生成等

 

4. 核心代码

4.1 向方向【左】移动的逻辑

评论:写的比较死,一步一步,按照规则来即可

// 向一个方向移动,更新数组(左),其他方向也是一样的
function moveLeft(arrayCopy){
    var gameSuccess = false;
    
    // 是否有合并或者移动,如果没有合并也没有移动,就是没有操作那就不要生成新的数字
    var anyChange = false;
    // 每一行,从【左】往【右】对比是否有一样的数字(排除0)--并不需要挨着!中间隔了空气也可以合并
    for (var i=0;i<4;i++){
        var firstIndex = 0;
        var secondIndex = 1;
        while (secondIndex<4){
            if (arrayCopy[i][firstIndex] == 0){
                // 第一个是0,不可能有合并,往后移动
                firstIndex++;
                secondIndex++;
                continue;
            }
            if (arrayCopy[i][secondIndex] == 0){
                // 第二个是0,第二个继续往后找,第一个不变
                secondIndex++;
                // 1. 后面找不到数字,一直往后找,结束循环
                continue;
            }            
            // 有一次一样的数字:计算得到和,放到第一个位置,这一行完了
            else if (arrayCopy[i][firstIndex] == arrayCopy[i][secondIndex]){
                arrayCopy[i][firstIndex] = arrayCopy[i][firstIndex] * 2;
                arrayCopy[i][secondIndex] = 0;
                anyChange = true;
                if (arrayCopy[i][firstIndex] >= 2048){
                    // 得到了2048,游戏结束
                    gameSuccess = true;
                }
                // 2. 后面找到数字,且可以合并,就合并,结束循环
                break;
            }
            else{
                // 3. 后面找到数字,但是不能合并,更新第一个数字为当前数字,第二个数字为下一个数字
                firstIndex = secondIndex;
                secondIndex += 1;
                continue;
            }
        }
    }
    
    // 将每一行数字向【左】挪动
    for (var i=0;i<4;i++){
        var newLine = [];//临时存储从【左】到【右】的非0数字
        var index = 0;
        for (var j=0;j<4;j++){
            if (arrayCopy[i][j] != 0){
                newLine[index] = arrayCopy[i][j];
                index++;
            }
        }
        
        // 用临时存储的数字给数组更新
        for (var m=0;m<index;m++){
            if (arrayCopy[i][m] != newLine[m]){
                anyChange = true;
            }
            arrayCopy[i][m] = newLine[m];
        }
        // 剩余的位置给0
        for (var n=index;n<4;n++){
            if (arrayCopy[i][n] != 0){
                anyChange = true;
            }
            arrayCopy[i][n] = 0;
        }
    }
    
    if (!anyChange){
        console.log("no change after this move!");
        if (!isEmptyCell(arrayCopy)){
            // 本步不能移动,且没有剩余格子,且任意相邻格子没有相同的,就结束游戏
            if (!canMerge(arrayCopy)){
                console.log("Game Over! Try again.");
                return 'fail';
            }
        }
        if (gameSuccess){
            return 'success';
        }
        // 没有移动
        return 'unmoved';
    }
    
    // 给空闲位置设置数字2
    if (isEmptyCell(arrayCopy))
    {
        initOnePosition(arrayCopy);
    }
    
    if (gameSuccess){
        return 'success';
    }
    return 'moved';
}

4.2.  通用的移动处理。

因为往一个方向移动,和往其他方向移动处理是一样的,不可能复制以上代码4次吧。

换个方向看:往左移动和往右移动对数组的操作,只是行处理相反,列处理一样;

其他方向移动类似,不值得copy整个方法。

// 通用方法
// 所有移动都转换成一个方向上的移动:例如,都想象成向左移动,只需要倒转数组即可,完成移动再倒转回来!
function move(direction){
    if (flagSuccess){
        if (confirm("You've got your 2048! Start Another One?\n已经通关!开始新游戏?")){
            gameRestart();
        }
        return;
    }
    
    // 移动之前,记录上一步的内容,移动失败之后可以恢复
    var lastStepUnchange = createNewArray();
    updateFrontArray(lastStepUnchange,lastStepArray);
    // 将上一步状态设置为当前的
    updateFrontArray(lastStepArray,arrayInit);
    
    // 将当前的去更新,更新失败则回滚上一步的状态
    // 1. 数组倒转复制
    var arrayCopy = new Array();
    if (direction == 'left'){
        arrayCopy = copyArrayLeft();
    }else if (direction == 'right'){
        arrayCopy = copyArrayRight();
    }else if (direction == 'up'){
        arrayCopy = copyArrayUp();
    }else if (direction == 'down'){
        arrayCopy = copyArrayDown();
    }
    
    // 2. 都向一个方向移动
    var moveResult = moveLeft(arrayCopy);
    if ('fail' == moveResult){
        
        updateFrontArray(lastStepArray,lastStepUnchange);
        // 移动失败,没有任何改变
        if (confirm('Game Over! Try again?\n游戏结束,再来一次?')){
            gameRestart();
            return;
        }else{
            // 最后一步了,允许回退
            clickedLastStep = false;
            return;
        }
        return;
    }
    if ('unmoved' == moveResult){
        return;
    }
    
    // 3. 从倒转的数组更新原数组
    if (direction == 'left'){
        restoreArrayLeft(arrayCopy);
    }else if (direction == 'right'){
        restoreArrayRight(arrayCopy);
    }else if (direction == 'up'){
        restoreArrayUp(arrayCopy);
    }else if (direction == 'down'){
        restoreArrayDown(arrayCopy);
    }
    
    // 4. 根据数字更新页面元素
    updatePageByArray();
    updateColor();
    
    if ('success' == moveResult){
        // 因为操作DOM不是实时的,所以要等一段事件操作完成之后再弹出确认框
        setTimeout(function(){
            // 游戏通关,是否重新开始
            if (confirm('Congragulations! Start Another Game?\n恭喜通关!是否再来一把?')){
                gameRestart();
            }else{
                flagSuccess = true;
            }
        },200);
        return;
    }
    
    // 移动完成
    clickedLastStep = false;
}

 

4.3 随机数的生成:

开始做的是生成一个随机数,16个位置的任意一个,如果这个位置已经有人了,就放弃,重新生成一个,直到找到空位为止。

但是到空位变少的时候,这样效率低下。

优化:只在空位中找随机位置。

// 生成随机数,转化成0-15的整数;
function getRamdom(count)
{
    // 0-1
    var r0 = Math.random();
    // 0-16
    var r1 = r0 * count;
    // 只取整数位数的数
    return Math.floor(r1);
}

// 改进:只在当前可用位置中随机找,不用找不可用的位置 
function getOneEmptyCoordinate(array)
{
    // 1. 得到当前数组
    var countEmptyCells = 0;
    var mapIndex2Coordinate = {};
    var coordinates = null;
    for (var i=0;i<4;i++){
        for (var j=0;j<4;j++){
            if (array[i][j] == 0){
                countEmptyCells++;
                coordinates = new Array();
                coordinates.push(i);
                coordinates.push(j);
                mapIndex2Coordinate[countEmptyCells] = coordinates;
            }
        }
    }
    // 取可用位置的随机位置
    var random = getRamdom(countEmptyCells);
    return mapIndex2Coordinate[random];
}

 

 

5. 欠缺:

5.1 布局,样式的调整比较嫌麻烦,瞎调的。

5.2 js代码关系--没什么关系,忘了前端是怎么组织的了;

5.3 刷新页面多次,可能出现从初始化异常,可能是文件加载顺序问题,初始化时dom操作比较慢等原因

5.4 看看别人实现思路如何 

-- http://www.cnblogs.com/-lizi/p/8431030.html 这个写的很漂亮

 

6. 总结:

 开发时间:5天下班时间,实际投入:15个小时左右。

 代码规模:js400行;

 

7. 完整代码粘贴

index.html

<head>
    <meta http-equiv="content-type" content="text/html;charset=utf-8">
</head>
<link href="../css/common.css" rel="stylesheet">

<body>
    <script type="text/javascript" src="../js/common.js"></script>
    <script type="text/javascript" src="../js/eventListener.js"></script>
    <script type="text/javascript" src="../js/domOperation.js"></script>
    <div id="main">
        <div id="refreshDiv" onclick="gameRestart()">
            <span id="refreshButton">
                <img src="../resource/refreshButton.png"  alt="refresh" />
                <a>重新开始</a>
            </span>
        </div>
        <div id="mainTable">
        </div>
        <div id="explation">
            <span>
             2048游戏说明:<br>
             按上下左右方向键可以移动有数字的方块,<br>
             相邻的相同数字的方块往一个方向移动会合并成更大的数字。<br>
             当最大数字出现2048时,游戏胜利!<br>
             温馨提示:你可以使用空格键回退上一步。
            </span>
        </div>
        <div id="rights">
            <span>
                All rights reserved to S.C. Contact me at 857025773@qq.com. 
            </span>
        </div>
    </div>
    
    <!-- 操作 -->
    <div id="keys">
        <div>
            <span id="moveUpButton"onclick="move('up')">
                <img src="../resource/direction.png"  alt="direction" />
            </span>
        </div>
        <div>
            <span id="moveLeftButton"onclick="move('left')">
                <img src="../resource/direction.png"  alt="direction" />
            </span>
            <span id="moveBackButton" onclick="eventSpaceKey()">
                <img src="../resource/moveBack.png"  alt="moveBack" />
            </span>
            <span id="moveRightButton"onclick="move('right')">
                <img src="../resource/direction.png"  alt="direction" />
            </span>
        </div>
        <div>
            <span id="moveDownButton"onclick="move('down')">
                <img src="../resource/direction.png"  alt="direction" />
            </span>
        </div>
    </div>
    
    
    <script type="text/javascript">
        window.onload = function(){
                pageInit();
        }
    </script>
</body>

 

common.css

#main
{
    margin-left:500px;
    margin-top:100px;
    width:404px;
    heignt:400px;
    border: 0px solid gray;
    border-radius: 1px;
}

#keys
{
    margin-left:1150px;
    margin-top:-430px;
    width:303px;
    heignt:300px;
    border: 3px solid green;
    border-radius: 20px;
}


#keys div
{
    display:flex;
    height:75px;
}

#keys div span
{
    height:75px;
    width:100px;
    border: 1px solid gray;
    background:blue;
}

#moveLeftButton img
{
    height:75px;
    width:100px;
    -ms-transform:rotate(180deg); /* IE 9 */
    -moz-transform:rotate(180deg); /* Firefox */
    -webkit-transform:rotate(180deg); /* Safari and Chrome */
    -o-transform:rotate(180deg); /* Opera */
}
#moveRightButton img
{
    height:75px;
    width:100px;
}

#moveUpButton
{
    margin-left:100px;
}
#moveUpButton img
{
    margin-top: -13px;
    margin-left: 12px;
    height:100px;
    width:75px;
    -ms-transform:rotate(270deg); /* IE 9 */
    -moz-transform:rotate(270deg); /* Firefox */
    -webkit-transform:rotate(270deg); /* Safari and Chrome */
    -o-transform:rotate(270deg); /* Opera */
}
#moveDownButton
{
    margin-left:100px;
}
#moveDownButton img
{
    margin-left: 12px;
    margin-top: -13px;
    height:100px;
    width:75px;
    -ms-transform:rotate(90deg); /* IE 9 */
    -moz-transform:rotate(90deg); /* Firefox */
    -webkit-transform:rotate(90deg); /* Safari and Chrome */
    -o-transform:rotate(90deg); /* Opera */
}
#moveBackButton
{
    height:100px;
    width:75px;
    background:yellow !important;
}

#refreshDiv
{
    height:40px;
    width:133px;
    margin-left:140px;
    cursor: pointer;
    background:#ba3537;
    border-radius: 2px;
}

#refreshDiv span
{
    display: contents;
    font-family: '微软雅黑';
    margin-left:159px;
    font-size:20;
}
#refreshDiv span a
{
    margin-bottom: 10px;
}


#explation
{
    margin-top:30px;
}
#rights
{
    margin-top:100px;
}
#rights span
{
    font-size:10;
}

#explation span
{
    font-size:14;
}

#mainTable
{
    margin-top:20px;
    background:#f5f5f5;
    border: 2px solid #478dcd;
    border-radius: 3px;
}

#mainTable div
{
    display:flex;
    width:400px;
    heignt:100px;
}

#mainTable span
{
    height:100px;
    width:100px;
    border: 1px solid gray;
    font-size: 50;
    text-align: center;
    font-weight:bold;
    
}

.color0
{
    background:#f5f5f5;
}

.color2
{
    background:#f5c7ad;
}
.color4
{
    background:#ec9362;
}
.color8
{
    background:#e3631e;
}
.color16
{
    background:#c2fcb1;
    font-size: 45 !important;
}
.color32
{
    background:#76f850;
    font-size: 45 !important;
}
.color64
{
    background:#2dad07;
    font-size: 45 !important;
}
.color128
{
    background:#a8a6f9;
    font-size: 40 !important;
}
.color256
{
    background:#4b47f1;
    font-size: 40 !important;
}
.color512
{
    background:#110da4;
    font-size: 40 !important;
}
.color1024
{
    background:#f799ef;
    font-size: 35 !important;
}
.color2048
{
    background:#a30e98;
    font-size: 35 !important;
}

 

common.js

// 拼成了2048,冻结操作,不允许移动了
var flagSuccess = false;
// 刚才点击了回退,不允许重复点击,要移动之后再点击
var clickedLastStep = false;

// 记录上一步,用于回退
var lastStepArray = [
                       [0,0,0,0],
                       [0,0,0,0],
                       [0,0,0,0],
                       [0,0,0,0]
                     ];

// 数组初始化--正常初始化
var arrayInit = 
    [
     [0,0,0,0],
     [0,0,0,0],
     [0,0,0,0],
     [0,0,0,0]
   ];
//[//调试--将要success
// [0,0,0,0],
// [0,128,0,0],
// [0,0,512,1024],
// [0,0,0,1024]
//];
//[//调试--将要fail
// [1,2,3,4],
// [5,128,6,7],
// [8,9,512,1024],
// [13,22,11,11]
//];



// 页面加载初始化
function pageInit()
{
    addHelper();
    
//    initArray();
    // 页面元素初始化(单元格)
    initElements();
    
    // 初始数据
    initTwoPositions();
    
    // 根据初始数据给单元格填充值
    updatePageByArray();
    
    // 根据单元格的值给单元格设置样式
    updateColor();
}

// 重新开始
function gameRestart(){
    initArray();
    pageInit();
}

function initArray(){
    flagSuccess = false;
    arrayInit = createNewArray();
    clickedLastStep = false;
    lastStepArray = createNewArray();
}


// 给两个位置设置数字2
function initTwoPositions()
{
    initOnePosition(arrayInit);
    initOnePosition(arrayInit);
}
// 给一个位置设置数字2
function initOnePosition(array){
    // 取随机数方式1
//    var num = getRamdom(16);
//    var line = Math.floor(num/4);
//    var col = num % 4;
//    var curNum = array[line][col];
//    if (curNum == 0)
//    {
//        // 成功找到空闲位置
//        array[line][col] = 2;
//        return true;
//    }
//    else{
//        // 递归调用,必须找到一个可用位置才算完
//        return initOnePosition(array);
//    }
    
    // 取随机数方式2
    var coordinate = getOneEmptyCoordinate(array);
    array[coordinate[0]][coordinate[1]] = 2; 
}

function isEmptyCell(arrayCopy)
{
    for (var i=0;i<4;i++)
    {
        for (var j=0;j<4;j++)
        {
            if (arrayCopy[i][j] == 0)
            {
                return true;
            }
        }
    }
    return false;
}

// 生成随机数,转化成0-15的整数;
function getRamdom(count)
{
    // 0-1
    var r0 = Math.random();
    // 0-16
    var r1 = r0 * count;
    // 只取整数位数的数
    return Math.floor(r1);
}

// 改进:只在当前可用位置中随机找,不用找不可用的位置 
function getOneEmptyCoordinate(array)
{
    // 1. 得到当前数组
    var countEmptyCells = 0;
    var mapIndex2Coordinate = {};
    var coordinates = null;
    for (var i=0;i<4;i++){
        for (var j=0;j<4;j++){
            if (array[i][j] == 0){
                countEmptyCells++;
                coordinates = new Array();
                coordinates.push(i);
                coordinates.push(j);
                mapIndex2Coordinate[countEmptyCells] = coordinates;
            }
        }
    }
    // 取可用位置的随机位置
    var random = getRamdom(countEmptyCells);
    return mapIndex2Coordinate[random];
}

 

eventListener,js

/**
 *  引用工具类
 */
function addHelper(){
    var newscript = document.createElement('script');  
    newscript.setAttribute('type','text/javascript');  
    newscript.setAttribute('src','../js/arrayHelper.js');  
    document.body.appendChild(newscript);  
}

//捕捉按键事件
document.onkeyup = function(event) {
    event = event || window.event;
    
    if (event.keyCode == 37){//left
        move('left');
    }else if (event.keyCode == 39){//right
        move('right');
    }else if (event.keyCode == 38){//up
        move('up');
    }else if (event.keyCode == 40){//down
        move('down');
    }else if (event.keyCode == 32){//space
        eventSpaceKey();
    }
}

function eventSpaceKey(){
    // 回退上一步
    if (clickedLastStep){
        console.log('can not move back again');
        return;
    }
    moveBack();
    flagSuccess = false;
    clickedLastStep = true;
}

// 回退上一步
function moveBack(){
    // 判断上一步全是0(刚刚初始化)
    var justStarted = true;
    for (var i=0;i<4;i++){
        for (var j=0;j<4;j++){
            if (lastStepArray[i][j] != 0){
                justStarted =  false;
            }
        }
    }
    if(justStarted){
        return;
    }
    
    // 1. 用备份的数组给当前数组设置值
    updateFrontArray(arrayInit,lastStepArray);
    
    // 3. 更新页面
    updatePageByArray();
    updateColor();
}

// 通用方法
// 所有移动都转换成一个方向上的移动:例如,都想象成向左移动,只需要倒转数组即可,完成移动再倒转回来!
function move(direction){
    if (flagSuccess){
        if (confirm("You've got your 2048! Start Another One?\n已经通关!开始新游戏?")){
            gameRestart();
        }
        return;
    }
    
    // 移动之前,记录上一步的内容,移动失败之后可以恢复
    var lastStepUnchange = createNewArray();
    updateFrontArray(lastStepUnchange,lastStepArray);
    // 将上一步状态设置为当前的
    updateFrontArray(lastStepArray,arrayInit);
    
    // 将当前的去更新,更新失败则回滚上一步的状态
    // 1. 数组倒转复制
    var arrayCopy = new Array();
    if (direction == 'left'){
        arrayCopy = copyArrayLeft();
    }else if (direction == 'right'){
        arrayCopy = copyArrayRight();
    }else if (direction == 'up'){
        arrayCopy = copyArrayUp();
    }else if (direction == 'down'){
        arrayCopy = copyArrayDown();
    }
    
    // 2. 都向一个方向移动
    var moveResult = moveLeft(arrayCopy);
    if ('fail' == moveResult){
        
        updateFrontArray(lastStepArray,lastStepUnchange);
        // 移动失败,没有任何改变
        if (confirm('Game Over! Try again?\n游戏结束,再来一次?')){
            gameRestart();
            return;
        }else{
            // 最后一步了,允许回退
            clickedLastStep = false;
            return;
        }
        return;
    }
    if ('unmoved' == moveResult){
        return;
    }
    
    // 3. 从倒转的数组更新原数组
    if (direction == 'left'){
        restoreArrayLeft(arrayCopy);
    }else if (direction == 'right'){
        restoreArrayRight(arrayCopy);
    }else if (direction == 'up'){
        restoreArrayUp(arrayCopy);
    }else if (direction == 'down'){
        restoreArrayDown(arrayCopy);
    }
    
    // 4. 根据数字更新页面元素
    updatePageByArray();
    updateColor();
    
    if ('success' == moveResult){
        // 因为操作DOM不是实时的,所以要等一段事件操作完成之后再弹出确认框
        setTimeout(function(){
            // 游戏通关,是否重新开始
            if (confirm('Congragulations! Start Another Game?\n恭喜通关!是否再来一把?')){
                gameRestart();
            }else{
                flagSuccess = true;
            }
        },200);
        return;
    }
    
    // 移动完成
    clickedLastStep = false;
}

// 向一个方向移动,更新数组(左),其他方向也是一样的
function moveLeft(arrayCopy){
    var gameSuccess = false;
    
    // 是否有合并或者移动,如果没有合并也没有移动,就是没有操作那就不要生成新的数字
    var anyChange = false;
    // 每一行,从【左】往【右】对比是否有一样的数字(排除0)--并不需要挨着!中间隔了空气也可以合并
    for (var i=0;i<4;i++){
        var firstIndex = 0;
        var secondIndex = 1;
        while (secondIndex<4){
            if (arrayCopy[i][firstIndex] == 0){
                // 第一个是0,不可能有合并,往后移动
                firstIndex++;
                secondIndex++;
                continue;
            }
            if (arrayCopy[i][secondIndex] == 0){
                // 第二个是0,第二个继续往后找,第一个不变
                secondIndex++;
                // 1. 后面找不到数字,一直往后找,结束循环
                continue;
            }            
            // 有一次一样的数字:计算得到和,放到第一个位置,这一行完了
            else if (arrayCopy[i][firstIndex] == arrayCopy[i][secondIndex]){
                arrayCopy[i][firstIndex] = arrayCopy[i][firstIndex] * 2;
                arrayCopy[i][secondIndex] = 0;
                anyChange = true;
                if (arrayCopy[i][firstIndex] >= 2048){
                    // 得到了2048,游戏结束
                    gameSuccess = true;
                }
                // 2. 后面找到数字,且可以合并,就合并,结束循环
                break;
            }
            else{
                // 3. 后面找到数字,但是不能合并,更新第一个数字为当前数字,第二个数字为下一个数字
                firstIndex = secondIndex;
                secondIndex += 1;
                continue;
            }
        }
    }
    
    // 将每一行数字向【左】挪动
    for (var i=0;i<4;i++){
        var newLine = [];//临时存储从【左】到【右】的非0数字
        var index = 0;
        for (var j=0;j<4;j++){
            if (arrayCopy[i][j] != 0){
                newLine[index] = arrayCopy[i][j];
                index++;
            }
        }
        
        // 用临时存储的数字给数组更新
        for (var m=0;m<index;m++){
            if (arrayCopy[i][m] != newLine[m]){
                anyChange = true;
            }
            arrayCopy[i][m] = newLine[m];
        }
        // 剩余的位置给0
        for (var n=index;n<4;n++){
            if (arrayCopy[i][n] != 0){
                anyChange = true;
            }
            arrayCopy[i][n] = 0;
        }
    }
    
    if (!anyChange){
        console.log("no change after this move!");
        if (!isEmptyCell(arrayCopy)){
            // 本步不能移动,且没有剩余格子,且任意相邻格子没有相同的,就结束游戏
            if (!canMerge(arrayCopy)){
                console.log("Game Over! Try again.");
                return 'fail';
            }
        }
        if (gameSuccess){
            return 'success';
        }
        // 没有移动
        return 'unmoved';
    }
    
    // 给空闲位置设置数字2
    if (isEmptyCell(arrayCopy))
    {
        initOnePosition(arrayCopy);
    }
    
    if (gameSuccess){
        return 'success';
    }
    return 'moved';
}

// 是否有任意相邻的格子可以合并
function canMerge(arrayCopy){
    for (var i=0;i<4;i++){
        for (var j=0;j<4;j++){
            if (j+1 < 4&& arrayCopy[i][j] == arrayCopy[i][j+1]){
                return true;
            }
            if (i+1 < 4 && arrayCopy[i][j] == arrayCopy[i+1][j]){
                return true;
            }
        }
    }
    return false;
}

arrayHelper.js

function createNewArray(){
    var newArray = [
                           [0,0,0,0],
                           [0,0,0,0],
                           [0,0,0,0],
                           [0,0,0,0]
                         ];
    return newArray;
}


// 后面的复制给前面
function updateFrontArray(arr1,arr2)
{
    for (var i=0;i<4;i++){
        for (var j=0;j<4;j++){
            arr1[i][j] = arr2[i][j];
        }
    }
}

function copyArray(arr){
    var newArray = createNewArray();
    updateFrontArray(newArray,arr);
    return newArray;
}


//左,原方向
function copyArrayLeft(){
    var arrayCopy = copyArray(arrayInit);
    return arrayCopy;
}
function restoreArrayLeft(arrayCopy){
    updateFrontArray(arrayInit,arrayCopy);
}

//右,上下不变,左右颠倒
function copyArrayRight(){
    var arrayCopy = new Array();
    for (var i=0;i<4;i++){
        var copyOneLine = new Array();
        for (var j=0;j<4;j++){
            copyOneLine[3-j] = arrayInit[i][j];
        }
        arrayCopy[i] = copyOneLine;
    }
    return arrayCopy;
}
function restoreArrayRight(arrayCopy){
    for (var i=0;i<4;i++){
        for (var j=0;j<4;j++){
            arrayInit[i][j] = arrayCopy[i][3-j];
        }
    }
}

//上
//从上往下变成从左往右
//从右往左变成从上往下
//(i,j) 对应:(3-j,i)
function copyArrayUp(){
    var arrayCopy = [
                       [0,0,0,0],
                       [0,0,0,0],
                       [0,0,0,0],
                       [0,0,0,0]
                     ];
    for (var i=0;i<4;i++){
        for (var j=0;j<4;j++){
            arrayCopy[3-j][i] = arrayInit[i][j];
        }
    }
    return arrayCopy;
}
function restoreArrayUp(arrayCopy){
    for (var i=0;i<4;i++){
        for (var j=0;j<4;j++){
            arrayInit[i][j] = arrayCopy[3-j][i];
        }
    }
}

//下
//从上往下变成从下往上
//从左往右变成从左往右
//(i,j) 对应:(j,3-i)
function copyArrayDown(){
    var arrayCopy = [
                       [0,0,0,0],
                       [0,0,0,0],
                       [0,0,0,0],
                       [0,0,0,0]
                     ];
    for (var i=0;i<4;i++){
        for (var j=0;j<4;j++){
            arrayCopy[j][3-i] = arrayInit[i][j];
        }
    }
    return arrayCopy;
}
function restoreArrayDown(arrayCopy){
    for (var i=0;i<4;i++){
        for (var j=0;j<4;j++){
            arrayInit[i][j] = arrayCopy[j][3-i];
        }
    }
}

domOperation.js

// 用数组给元素赋值
function updatePageByArray()
{
    var lines = document.getElementById("mainTable").children;
    var lineIndex = 0;
    
    while (lineIndex < lines.length)
    {
        var spanIndex = 0;
        while (spanIndex < lines[lineIndex].children.length)
        {
            var oneSpan = lines[lineIndex].children[spanIndex];
            oneSpan.innerHTML = arrayInit[lineIndex][spanIndex];
            spanIndex++;
        }
        lineIndex++;
    }
}

// 初始化单元格和颜色
function initElements()
{
    var mainTable = document.getElementById("mainTable");
    var str = '';
    // 创建行
    for (var i=0;i<4;i++)
    {
        str += "<div id='" +"line"+i+"'>";
        for (var j=0;j<4;j++)
        {
            var num = arrayInit[i][j];
            str += "<span id='" +"span"+i+j+"'>"
                        + num+
                    "</span>";
        }
        str += "</div>";
        mainTable.innerHTML = str;
    }
}

// 根据ID更新颜色
function updateColor(){
    for (var i=0;i<4;i++){
        for (var j=0;j<4;j++){
            var span = document.getElementById("span"+i+j);
            var value = span.innerHTML;
            if (value == '0'){
                span.innerHTML = '';
            }
            span.className = (" color" + value);
        }
    }
}