Loading

俄罗斯方块游戏-JS

黑马Bilibili

流程

代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
    <script src="https://cdn.bootcss.com/lodash.js/4.17.4/lodash.min.js"></script>
    <!-- 父容器使用 相对 位置,子容器选择 绝对 位置 -->
    <style>
        .container{
            width: 200px;
            height: 360px;
            background: url("") no-repeat;
            background-size: 200px 360px;
            position: relative; 
        }

        .activity_mode{
            width: 20px;
            height: 20px;
            /* background-color: cadetblue; */
            border: .3px solid #333333;
            box-sizing: border-box;
            position: absolute;
            background: #ffd75e url("") center no-repeat;
            background-size: 15px;
        }


        .fixed_mode{
            width: 20px;
            height: 20px;
            background-color: #fefefe;
            border: .3px solid #333;
            box-sizing: border-box;
            position: absolute;
            background: #bbbbbb url("") center no-repeat;
            background-size: 15px;
        }
    </style>
    
</head>
<body onload="init()">
    <!-- 背景容器 -->
    <div id ="container" class="container">
        <!-- <div class="activity_mode"></div> -->
    </div>

</body>

<script>

    // 常量
    // 每次移动的距离,步长
    var STEP = 20;

    // 分割容器 18行, 10列
    var ROW_COUNT = 18,
        COL_COUNT = 10;

    // 创建每个模型的数据源
    var MODELS = [
        // 第一个模型数据源(L型)
        /* 在4*4中画一个L型,坐标起始是0
            0 0 0 0
            0 0 1 0
            1 1 1 0
            0 0 0 0
        */
        {
            0: {
                row: 2,
                col: 0
            },
            1: {
                row: 2,
                col: 1
            },
            2: {
                row: 2,
                col: 2
            },
            3: {
                row: 1,
                col: 2
            },
        },
        // 第二个模型数据源(凸型)
        {
            0: {
                row: 1,
                col: 1
            },
            1: {
                row: 0,
                col: 0
            },
            2: {
                row: 1,
                col: 0
            },
            3: {
                row: 2,
                col: 0
            },
        },
        // 第三个模型数据源(田型)
        {
            0: {
                row: 1,
                col: 1
            },
            1: {
                row: 2,
                col: 1
            },
            2: {
                row: 1,
                col: 2
            },
            3: {
                row: 2,
                col: 2
            },
        },
        // 第四个模型数据源(一型)
        {
            0: {
                row: 0,
                col: 0
            },
            1: {
                row: 0,
                col: 1
            },
            2: {
                row: 0,
                col: 2
            },
            3: {
                row: 0,
                col: 3
            },
        },
        // 第五个模型数据源(Z型)
        {
            0: {
                row: 1,
                col: 1
            },
            1: {
                row: 1,
                col: 2
            },
            2: {
                row: 2,
                col: 2
            },
            3: {
                row: 2,
                col: 3
            },
        },

    ]

    // 变量
    // 当前使用的模型
    var currentModel = {};
    // 标记16宫格的位置
    var currentX = 0,
        currentY = 0;
    // 记录所有块元素的位置
    // K=行_列 : V=块元素
    var fixedBlocks = {};
    //定时器
    var mInterval = null;

    // 入口方法
    function init(){
        createModel();
        onKeyDown();
    }

    // 根据模型的数据源创建对应的块元素
    function createModel(){
        // 判断游戏是否结束
        if(isGameOver()){
            gameOver();
        }
        // 确定当前使用哪一个模型
        currentModel = MODELS[_.random(0, MODELS.length-1)];
        // 重新初始化 16宫格的位置
        currentX = 0;
        currentY = 0;
        // 生成对应数量的块元素
        for(var key in currentModel){
            // console.log("key: "+key);
            var divEle = document.createElement("div");
            divEle.className = "activity_mode";
            // 将模型添加到背景容器中
            document.getElementById("container").appendChild(divEle);
        }
        // 定位块元素的位置
        locationBlocks();
        // 模型自动下落
        autoDown();
    }

    // 根据数据源定位块元素的位置
    function locationBlocks(){
        // 判断元素越界并处理
        checkBound();
        //1. 拿到所有的块元素
        var eles = document.getElementsByClassName("activity_mode");
        
        for(var i=0; i<eles.length; i++){
            //单个块元素
            var activityModelEle = eles[i];
            //2. 找到每个块元素对应的数据
            var blockModel = currentModel[i];
            //3. 根据每个块元素对应的数据来指定块元素的位置
            //每个块元素的位置由:1)16宫格所在位置 2)块元素在16宫格中的位置 两个元素确定
            activityModelEle.style.top = (currentY + blockModel.row)*STEP + "px";
            activityModelEle.style.left = (currentX +  blockModel.col)*STEP + "px";
        }
    }



    // 监听用户的键盘事件
    function onKeyDown(){
        document.onkeydown = function(event){
            //console.log(event.keyCode);
            switch(event.keyCode){
                case 37:
                    //console.log("左");
                    move(-1,0);
                    break;  
                case 38:
                    //console.log("上");
                    rotate();
                    //move(0,-1);
                    break;
                case 39:
                    //console.log("右");
                    move(1,0);
                    break;   
                case 40:
                    //console.log("下");
                    move(0,1);
                    break;
            }
        }
    }

    // 移动
    function move(x, y){
        //控制块元素移动
        // var activityModelEle = document.getElementsByClassName("activity_mode")[0];
        // // 初始 top 为空时,取0:parseInt(activityModelEle.style.top || 0) 
        // activityModelEle.style.top = parseInt(activityModelEle.style.top || 0) + y*STEP + "px";
        // activityModelEle.style.left = parseInt(activityModelEle.style.left || 0) + x*STEP + "px";

        if(isMeet(currentX+x, currentY+y, currentModel)){
            // 底部的触碰发生在移动16宫格的时候,并且此次移动是因为Y轴变化引起的
            if(y !== 0){
                // 模型之间底部发生碰撞
                fixedBottomModel();
            }
            return;
        }

        //模型的移动,有16宫格的移动表示
        currentX += x;
        currentY += y;
        //根据16宫格的位置 重新定位块元素
        locationBlocks();
    }

    // 旋转模型
    function rotate(){
        // 旋转后的行 = 旋转前的列
        // 旋转后的列 = 3 - 旋转前的行

        // 克隆 currentModel
        var cloneCurrentModel = _.cloneDeep(currentModel);


        // 遍历 模型数据源
        for(var key in cloneCurrentModel){
            // 块元素的数据源
            var blockModel = cloneCurrentModel[key];
            // 实现
            var temp = blockModel.row;
            blockModel.row = blockModel.col;
            blockModel.col = 3 - temp;
        }
        //如果旋转之后发生触碰,则不允许再旋转
        if(isMeet(currentX, currentY, cloneCurrentModel)){
            return;
        }
        // 没有碰撞 接受此次旋转
        currentModel = cloneCurrentModel;
        locationBlocks();
    }

    // 控制模型只能在容器中移动
    function checkBound(){
        // 定义模型可以活动的边界
        var leftBound = 0,
            rightBound = COL_COUNT,
            bottomBound = ROW_COUNT;
        // 当块元素超出边界之后,让16宫格后退一步 
        for(var key in currentModel){
            // 块元素对应的数据
            var blockModel = currentModel[key];
            // 左侧越界
            if((blockModel.col + currentX) < leftBound){
                currentX++;
            }
            // 右侧越界
            if((blockModel.col + currentX) >= rightBound){
                currentX--;
            }
            // 底部越界
            if((blockModel.row + currentY) >= bottomBound){
                currentY--;
                // 把模型固定在底部
                fixedBottomModel();
            }
        }
    }

    // 把模型固定在底部
    function fixedBottomModel(){
        //1. 改变模型(每个块的)的样式(可移动时是亮色,不可移动变灰)
        var activityModelEles = document.getElementsByClassName("activity_mode");
        //每次修改className后,activityModelEles 会根据当前实际的个数更新 length(4->3->2->1)
        //for(var i=0; i<activityModelEles.length; i++){ //报错 
        for(var i=activityModelEles.length-1; i>=0; i--){
            // 拿到每个块元素
            var activityModelEle = activityModelEles[i];
            // 更改块元素的类明(修改样式)
            //2. 让模型不可以再进行移动(移动时获取的是activity_mode样式)
            activityModelEle.className = "fixed_mode";
            // 把该块元素放入变量中
            var blockModel = currentModel[i];
            fixedBlocks[(currentY + blockModel.row) + "_" + (currentX + blockModel.col)]=activityModelEle;
        }
        // 判断某一行是否需要清理
        isRemoveLine();
        //3. 创建新的模型
        createModel();
    }

    // 判断模型之间的触碰问题
    // x, y 表示16宫格《将要》移动到的位置
    // model 表示当前模型数据源《将要》完成的变化(还没变)
    function isMeet(x, y, model){
        // 所谓模型之间的触碰,在一个固定的位置已经存在一个被固定的块元素时,那么活动中的模型不能再占用该位置
        // 判断触碰,就是再判断活动中的模型《将要移动到的位置》是否已经存在被固定的模型(块元素)
        // 如果存在返回 true,否则返回 false。
        for(var k in model){
            var blockModel = model[k];
            // 该位置是否已经存在块元素?
            if(fixedBlocks[(y+blockModel.row)+"_"+(x+blockModel.col)]){
                return true; //表示将要移动到的位置会发生碰撞
            }
        }
        return false;
    }

    // 判断一行是否被铺满
    function isRemoveLine(){
        // 在一行中, 每一列都存在块元素,那么该行就需要被清理
        // 遍历所有行中的列
        // 遍历所有行
        for(var i=0; i<ROW_COUNT; i++){
            var flag = true;
            for(var j=0; j<COL_COUNT; j++){
                // 如果当前行中有一列没有数据,那么就说明当前行没有被铺满
                if(!fixedBlocks[i+"_"+j]){ //取不到数据
                    flag = false;
                    break;
                }
            }
            if(flag){
                //该行已经被铺满
                //console.log("该行已经被铺满");
                removeLine(i);
            }
        }
    }

    // 清理被铺满的这一行
    function removeLine(line){
        // 遍历该行中的所有列
        for(var i=0; i<COL_COUNT; i++){
            // 1. 删除该行中的所有的块元素
            document.getElementById("container").removeChild(fixedBlocks[line+"_"+i]);
            // 2. 删除该行中所有块元素的数据源
            fixedBlocks[line+"_"+i] = null;
        }
        downLine(line);
    }

    // 让被清理行之上的块元素下落
    function downLine(line){
        //遍历被清理行之上的所有行
        for(var i=line-1; i>=0; i--){
            //遍历该行中的所有列
            for(var j=0; j<COL_COUNT; j++){
                if(!fixedBlocks[i+"_"+j]) continue; //没有元素
                //存在数据
                //1. 被清理行之上的所有块元素数据源所在的行数+1
                fixedBlocks[(i+1)+"_"+j] = fixedBlocks[i+"_"+j];
                //2. 让块元素在容器中的位置下落
                fixedBlocks[(i+1)+"_"+j].style.top = (i+1)*STEP + "px";
                //3. 清理掉之前的块元素
                fixedBlocks[i+"_"+j] = null;
            }
        }
    }

    // 让模型自动下落
    function autoDown(){
        if(mInterval){
            clearInterval(mInterval);
        }
        mInterval = setInterval(function(){
            move(0,1)
        }, 600);
    }

    // 判断游戏结束
    function isGameOver(){
        // 当第0行存在块元素的时候,表示游戏结束
        for(var i=0; i<COL_COUNT; i++){
            if(fixedBlocks["0_"+i]){
                return true;
            }
        }
        return false;
    }

    // 结束游戏
    function gameOver(){
        //1. 停止定时器
        if(mInterval){
            clearInterval(mInterval);
        }
        //2. 弹出对话框
        alert("Game Over!");
    }
</script>
</html>
posted @ 2021-09-23 16:50  喵喵巫  阅读(69)  评论(0编辑  收藏  举报