es5实现推箱子

284 阅读6分钟

文章的开始

推箱子是一个很经典的游戏,玩法很简单,简单的逻辑实现起来也有一定的难度。开始我想着用面向对象的思想去实现,发现并不需要。想要实现功能,只需要推箱子的小人移动一次就重新渲染一次界面就好了。

效果图

页面渲染的思想

毋庸置疑整个游戏的界面需要一个玩家,箱子,以及围墙。这里用一个二维数组来实现。 游戏界面分解成每一个小的项目,这些项目可能是空白,玩家,墙,箱子。不同的项目用不同的值表示存放在二维数组中,然后遍历数组获取数组每一位的value,每一个值进行不同的渲染。遍历结束也意味着渲染结束。

游戏的实现过程&重点代码解读

1.初始化界面的渲染

map表示整体的地图,correct数组中存放的每一个对象表示设定的箱子的正确位置。最后可以correct数组中的row,col带入到map数组中来判断所有箱子是否都在正确的位置上,如果所有箱子都在正确位置上那么游戏就胜利。

比如当{ row: 3, col: 4 },map[3][4]==3表示箱子在正确位置上。

初始化需要的数据,两个数组代码如下

  • 数据1 二维数组表示每一个项目坐标位置,数组具体的值指代具体的项目
/**
 * 
 * 0:空白
 * 1:玩家
 * 2:墙
 * 3:箱子
 */
var map = [
    [0, 0, 2, 2, 2, 2, 2, 0, 0],
    [0, 0, 2, 0, 1, 0, 2, 0, 0],
    [0, 0, 2, 0, 3, 0, 2, 0, 0],
    [2, 2, 2, 0, 0, 0, 2, 2, 2],
    [2, 0, 0, 0, 3, 0, 0, 0, 2],
    [2, 0, 3, 3, 3, 3, 3, 0, 2],
    [2, 0, 0, 0, 3, 0, 0, 0, 2],
    [2, 2, 0, 3, 3, 3, 0, 2, 2],
    [0, 2, 0, 0, 0, 0, 0, 2, 0],
    [0, 2, 0, 0, 3, 0, 0, 2, 0],
    [0, 2, 0, 0, 0, 0, 0, 2, 0],
    [0, 2, 2, 2, 2, 2, 2, 2, 0]
];//地图

  • 数据2
//地图中的正确位置
var correct = [
    { row: 3, col: 4 },
    { row: 4, col: 4 },
    { row: 5, col: 2 },
    { row: 5, col: 3 },
    { row: 5, col: 4 },
    { row: 5, col: 5 },
    { row: 5, col: 6 },
    { row: 6, col: 4 },
    { row: 7, col: 4 },
    { row: 8, col: 4 },
    { row: 9, col: 4 },
    { row: 10, col: 4 }
];
  • 渲染地图的时候i控制行数,j控制列数。初次渲染的时候,当获取二维数组中值为0表示空白部分。值为1表示皮卡丘,值为2表示墙,值为3表示箱子。

-但是空白存在两种情况,一种本来是空白,后面箱子应该被玩家推至到该空白上。另外一种是游戏刚开始的时候是空白,游戏结束的时候该位置任然是空白。所以还需要遍历correct数组来判断该空白位置是否为箱子的正确位置。如果是的话给予一个绿色细线框。

/**
 * 渲染地图
 * 根据地图中的不同元素,生成不同的div,加入到id为game的div中去
 */
function render() {
    //1. 清空游戏容器
    gameDom.innerHTML = "";
    //2. 遍历地图的所有内容
    for (var i = 0; i < map.length; i++) {
        var row = map[i]; //拿到当前行,当前行也是一个数组
        for (var j = 0; j < row.length; j++) {
            // row[j] = map[i][j] 表示 第i行,第j列
            // 生成对应的div
            var div = document.createElement("div");
            div.className = "item";
            // 得到当前行当前列的值
            var value = map[i][j]; // row[j] 一样 
            var isRight = isCorrect(i, j); //当前位置是否是箱子正确位置
            if (value === 0) {
                // 空白
                if (isRight) {//正确位置的空白
                    div.classList.add("correct"); //添加一个类样式
                }
                else {
                    //非正确位置的空白
                    continue; //进行下一次循环
                }
            }
            else if (value === 1) {
                div.classList.add("player"); //玩家
            }
            else if (value === 2) {
                div.classList.add("wall"); //墙
            }
            //value为3正确位置的绿色箱子和value为3不正确的黄色箱子
            else {
                if (isRight) {
                    div.classList.add("correct-box")
                }
                else {
                    div.classList.add("box")
                }
            }
            //设置div的坐标
            div.style.left = size * j + "px";
            div.style.top = size * i + "px";
            gameDom.appendChild(div);
        }
    }
    //3. 设置容器的宽高
    var rowNumber = map.length; //地图的行数
    var colNumber = map[0].length; //得到第一行的列数,随便取一行即可,因为每一行的列数一致
    gameDom.style.width = colNumber * size + "px";
    gameDom.style.height = rowNumber * size + "px";
}

render();

2.修改后的map数组(移动玩家数组值改变)

  • 皮卡丘移动了也就意味着页面会发生变化,那么二维数组map里面的数据也会发生小小的改动,移动后页面的渲染还是跟初始化渲染一样,只是map数组发生了改变。所以我们需要的到新的map数组。

  • 新的map数组其实只是value值发生了数据的交换,数据的位置发生了变化。比如说皮卡丘向下移动,下一个位置是空白。皮卡丘和空白位置的交换,value值进行交换。

  • 通过键盘控制玩家移动的方向,通过监听键盘事件来获取移动的方向,利用将要移动的方向和皮卡丘当前的方向可以得到皮卡丘的下一个方向,甚至下下个放向也可以得到。

  • 拿到下一个方向后,根据value值来确定能否进行数据交换,如果是空白可以,如果是箱子就要分情况,当箱子的下一个方向是空白没有东西,就可以交换位置。首先箱子挪到空白位置上,人在挪到原来箱子的位置上,所以会交换两次位置。

function move(direction) {
    //得到玩家在当前方向上的下一个坐标
    var playerLoc = getPlayerLoc(); //得到玩家位置
    var nextLoc = getNextLoc(playerLoc); //得到玩家的下一个位置
    //通过坐标,拿到地图上皮卡丘想移动下一个坐标的值
    var value = map[nextLoc.row][nextLoc.col];

    if (value === 2) {
        //前方是墙
        return;
    }
    else if (value === 0) {
        //前方是空白
        //交换 playerLoc 和 nextLoc 的值
        exchange(playerLoc, nextLoc);
    }
    else if (value === 3) {
        //前方是箱子 nextLoc位置是箱子
        //要看箱子的前方
        var nextNextLoc = getNextLoc(nextLoc) //传入箱子的坐标,得到该方向上,箱子的下一个位置
        var nextNextValue = map[nextNextLoc.row][nextNextLoc.col]; //得到箱子下一个位置的值
        if (nextNextValue === 0) {
            //箱子前方没有东西,可以移动
            //箱子和下一个位置的空白互换
            exchange(nextLoc, nextNextLoc);
            //玩家和下一个位置互换
            exchange(playerLoc, nextLoc);
        }
        else {
            //其他情况都不能动
            return;
        }
    }

    //重新渲染
    render();

3.根据新的map数组重新渲染

再次渲染跟初次渲染的思想相同

4.判断游戏是否胜利

当所有的箱子都在正确的位置上,此位置的箱子在map数组里value == 3的时候表示游戏胜利,判断胜利代码如下

/**
 * 判断游戏是否胜利
 */
function isWin() {
    //循环所有的正确坐标
    for (var i = 0; i < correct.length; i++) {
        var loc = correct[i];
        var value = map[loc.row][loc.col];//拿到这个坐标下的值
        if (value !== 3) {
            //这个位置没有箱子
            return false; //没有胜利
        }
    }
    return true;
}

文章的结束

推箱子的思想大致就是根据map数组渲染页面,当发生键盘事件后修改map数组,再次渲染页面的过程。实现过程中没有是什么就想办法去获取创造,比如说不知道玩家的下一个位置左边,那就写一个函数获取当前玩家位置,再根据监听键盘事件得到下一个方向。根据这两个值获取下一个位置。希望这篇文章对大家有所帮助欢迎大家留言点赞哟~