使用原生JS实现俄罗斯方块

为什么忽然整了个俄罗斯方块

基本上在大学学编程,大一的期末大作业都会留一个类似于俄罗斯方块的东西。而我便是早早放弃了写代码选择了抱大腿的那位。如今五一将近。为了过一个安稳的假期公司基本没有什么任务了。为了弥补当年的遗憾(绝对不是摸鱼)就自己亲自动手写了这个作业。

代码水平一般,大伙权当看个乐子。

康康代码

<!DOCTYPE html>
<html lang="zh">

<head>
    <meta charset="UTF-8">
    <title>demos</title>
    <style>
        * {
            margin: 0;
            height: calc(100% - 1px);
        }

        .container {
            display: flex;
            flex-direction: row;
            width: 100%;
            height: 100%;
        }

        #failed {
            height: 100%;
            line-height: 100%;
            z-index: 999;
            width: 50%;
            height: 100%;
            display: none;
            color: red;
            position: absolute;
            font-size: 15vh;
            flex-direction: column;
            align-items: center;

        }

        .left-side {
            width: 40%;
            border: 1px black solid;
        }

        .right-side {
            width: 60%;
        }

        #box-container tr td {
            background-color: white;
        }

        .btn-area {
            width: 50%;
            height: 100%;
            display: flex;
            flex-direction: column;
            align-items: center;
        }

        .btn-area button {
            width: 40px;
            height: 25px;
        }

        .btn-area .level-btn button {
            width: 80px;
            height: 25px;
        }
    </style>
</head>

<body>
    <div class="container">
        <div id="failed">
            <div></div>
        </div>
        <div class="left-side">
            <table style="width: 100%;">
                <tbody id="box-container" style="width: 100%;"></tbody>
            </table>
        </div>
        <div class="right-side">
            <div class="btn-area">
                <div id="score-area" style="height: 50px;">total score: 0</div>
                <div id="level-area" style="height: 50px;">level: 1</div>
                <div class="level-btn">
                    <button id="level-up">level up</button>
                    <button id="level-down">level down</button>
                </div>
                <div>
                    <button id="start-btn">start</button>
                    <button id="end-btn">end</button>
                </div>

            </div>
        </div>
    </div>
    <script>
        const rows = 18;
        const columns = 10;
        let totalScore = 0;
        let level = 1;
        let stopNow = false;
        /* 避免重复触发开始 */
        let elsRunning = false;
        /* 存储 [x, y] 结构*/
        let globalPointSet = new Set();
        const container = document.getElementById("box-container");

        // 初始化背景
        const initArea = () => {
            let instr = '';
            for (let i = 0; i < rows; i++) {
                instr += `<tr id='row${i}' style='width: 100%;'>`;
                for (let j = 0; j < columns; j++) {
                    instr += `<td id='cell-${i}-${j}' style='width: 10%;'></td>`;
                }
                instr += `</tr>`;
            }
            container.innerHTML = instr;
            showFailed(false);
        }
        // 控制变色区域
        const turnColor = (i, j, color = 'gray') => {
            let cell = document.getElementById(`cell-${rows - 1 - j}-${i}`);
            if (cell) {
                cell.style.backgroundColor = color;
            }
        }
        // 结束
        const jiNiTaiMei = () => {
            for (let point of globalPointSet) {
                if (point[1] >= rows) {
                    stopNow = true;
                    showFailed();
                    break;
                }
            }
        }
        const showFailed = (flag = true) => {
            let failed = document.getElementById('failed');
            if (flag) {
                failed.style.display = 'flex'
            } else {
                failed.style.display = 'none';
            }
        }
        // 延时
        const sleep = async (timeout) => {
            return new Promise((resolve) => {
                setTimeout(() => {
                    resolve(true);
                }, timeout)
            })
        }

        // 绘制等级
        const drawLevel = () => {
            let levelArea = document.getElementById('level-area');
            levelArea.innerText = `level: ${level}`;
        }
        /* 物品,包含类型、朝向、points、position */
        class Item {
            constructor(num = 0, direction = 0, x = 4, y = 18) {
                this.num = num;
                this.direction = direction;
                this.initItem();
                this.x = x;
                this.y = y;
                this.makeTurn();
            }

            // 初始化图形
            initItem = () => {
                let type = this.num;
                let points = [];
                switch (type) {
                    case 0:
                        points.push(...[[0, 0], [1, 0], [2, 0], [3, 0]]);
                        break;
                    case 1:
                        points.push(...[[0, 0], [0, 1], [1, 0], [1, 1]]);
                        break;
                    case 2:
                        points.push(...[[0, 0], [0, 1], [1, 0], [2, 0]]);
                        break;
                    case 3:
                        points.push(...[[0, 0], [1, 0], [1, 1], [2, 0]]);
                        break;
                    case 4:
                        points.push(...[[0, 0], [1, 0], [2, 0], [2, 1]]);
                        break;
                    case 5:
                        points.push(...[[0, 1], [0, 2], [1, 0], [1, 1]]);
                        break;
                    case 6:
                        points.push(...[[0, 0], [0, 1], [1, 1], [1, 2]])
                        break;
                }
                this.points = points;
            }

            /* 触发旋转 */
            turn = () => {
                /* 旋转 */
                let minX = 10;
                let minY = 10;
                /* 旋转前需要判断是否可以旋转 设置撞墙与触底两个判断 */
                let pointSet = new Set();
                this.points.forEach(point => {
                    let pX = point[0];
                    let pY = point[1];
                    let newX = Math.round(Math.cos(Math.PI / 2) * pX - Math.sin(Math.PI / 2) * pY);
                    let newY = Math.round(Math.cos(Math.PI / 2) * pY + Math.sin(Math.PI / 2) * pX);
                    minX = minX < newX ? minX : newX;
                    minY = minY < newY ? minY : newY;
                    pointSet.add([newX, newY]);
                })

                /* 旋转后需要重新定位 (不然会发生十分鬼畜的现象) */
                let isTurnable = true;
                for (let point of pointSet) {
                    point[0] = point[0] - minX;
                    point[1] = point[1] - minY;
                    if (this.x + point[0] < 0 || this.x + point[0] > columns - 1) {
                        isTurnable = false; break;
                    }
                    if (this.y + point[1] < 0) {
                        isTurnable = false; break;
                    }
                    /* 判断旋转后是否会与底部已有的点重合 */
                    for (let staticPoint of globalPointSet) {
                        if (staticPoint[0] == this.x + point[0] && staticPoint[1] == this.y + point[1]) {
                            isTurnable = false; break;
                        }
                    }
                }

                if (isTurnable) {
                    this.points = pointSet;
                    this.direction = (this.direction + 1) % 4;
                }
                return isTurnable;
            }

            /* 根据传入的数值进行旋转(其实还是在调用turn) */
            makeTurn = () => {
                let originDirection = this.direction;
                for (let i = 0; i < this.direction; i++) {
                    this.turn();
                }
                this.direction = originDirection;
            }

            itemChangeColor = (color) => {
                this.getAllItemPointsPosition().forEach(point => {
                    turnColor(point[0], point[1], color);
                })
            }


            itemStop = () => {
                /* 加入大群 */
                this.getAllItemPointsPosition().forEach(point => {
                    globalPointSet.add([point[0], point[1]]);
                })
                clearRow();
                /* 大群控制整体颜色更新 */
                initArea();
                /* 判断是否存在完整行 */
                /* 重新上色 */
                globalPointSet.forEach(point => {
                    turnColor(point[0], point[1]);
                })
                /* 判断是否结束 */
                jiNiTaiMei();
            }

            /* 获取点的绝对位置 */
            getAllItemPointsPosition = () => {
                let resp = new Set();
                this.points.forEach(point => {
                    let pointPosition = [this.x + point[0], this.y + point[1]];
                    resp.add(pointPosition);
                })
                return resp;
            }

            isItemHitBottom = () => {
                if (this.y == 0) {
                    return true;
                }
                for (let point of globalPointSet) {
                    let points = this.getAllItemPointsPosition();
                    for (let itemPoint of points) {
                        if (point[0] == itemPoint[0] && point[1] + 1 == itemPoint[1]) {
                            return true;
                        }
                    }
                }
                return false;
            }

            isItemXMoveAllowed = (direction = -1) => {
                for (let point of globalPointSet) {
                    let points = this.getAllItemPointsPosition();
                    for (let itemPoint of points) {
                        if (point[1] == itemPoint[1] && point[0] - direction == itemPoint[0]) {
                            return true;
                        }
                    }
                }
                return false;
            }

            /* 相应键盘事件 */
            prepareForKeyEnter = () => {
                document.onkeydown = ({ code }) => {
                    this.itemChangeColor('white');
                    switch (code) {
                        case 'ArrowLeft':
                            /* 判断横向移动是否会撞击底部 */
                            /* 判断是否会触及边框 */
                            this.x = this.x > 0 && !this.isItemXMoveAllowed(-1) ? this.x - 1 : this.x;
                            break;
                        case 'ArrowRight':
                            /* 判断横向移动是否会撞击底部 */
                            if (this.isItemXMoveAllowed(1)) {
                                break;
                            }
                            /* 判断是否会触及边框 */
                            let points = this.getAllItemPointsPosition();
                            let resp = true;
                            for (let point of points) {
                                if (point[0] >= columns - 1) {
                                    resp = false;
                                }
                            }
                            if (resp) {
                                this.x = this.x + 1;
                            }
                            break;
                        case 'ArrowUp':
                        case 'Space':
                            this.turn();
                            break;
                        case 'ArrowDown':
                            if (!this.isItemHitBottom()) {
                                this.itemChangeColor('white');
                                this.y--;
                                this.itemChangeColor('#39C5BB');
                            }
                            break;
                    }
                    this.itemChangeColor('#39C5BB');
                }
            }

            // 物品下落(1个单位)
            itemFallen = () => {
                /* 触发触底事件 */
                if (this.isItemHitBottom()) {
                    this.itemStop();
                    return false;
                } else {
                    /* 下落过程监听键盘事件 */
                    this.prepareForKeyEnter();
                    this.itemChangeColor('white');
                    this.y--;
                    this.itemChangeColor('#39C5BB');
                    return true;
                }
            }
        }

        // 产生随机物品
        const createItem = () => {
            let num = Math.floor(Math.random() * 7);
            let direction = Math.floor(Math.random() * 5);
            return new Item(num, direction);
        }

        // 将 产生物品 → 下落 → 触底做成循环
        const itemInterval = async () => {
            elsRunning = true;
            while (true) {
                let item = createItem();
                let flag = item.itemFallen();
                while (flag) {
                    if (stopNow) {
                        break;
                    }
                    flag = item.itemFallen();
                    await sleep(1000 / level);
                }
                if (stopNow) {
                    break;
                }
            }
        }

        // 消除整行
        const clearRow = () => {
            let rowSet = new Set();
            let rowMap = new Map();
            for (let point of globalPointSet) {
                let rowNum = rowMap.get(point[1]) || 0;
                rowNum++;
                rowMap.set(point[1], rowNum);
                if (rowNum == columns) {
                    rowSet.add(point[1]);
                }
            }
            /* 计分 */
            totalScore += rowSet.size;
            let scoreArea = document.getElementById('score-area');
            scoreArea.innerHTML = `total score: ${totalScore}`;
            if (totalScore > 0 && totalScore % 10 == 0) {
                level++;
                drawLevel();
            }
            let newGlobalSet = new Set();
            globalPointSet.forEach(point => {
                /* 包含在消除行的点不加入新的集合。同时计算下降 */
                if (!rowSet.has(point[1])) {
                    let count = 0;
                    rowSet.forEach(rowNum => {
                        if (rowNum < point[1]) {
                            count++;
                        }
                    })
                    newGlobalSet.add([point[0], point[1] - count]);
                }
            })
            globalPointSet = newGlobalSet;
        }


        let startBtn = document.getElementById('start-btn');
        startBtn.addEventListener('click', () => {
            if (elsRunning == true) {
                return;
            }
            stopNow = false;
            this.globalPointSet = new Set();
            initArea();
            itemInterval();
        });

        let endBtn = document.getElementById('end-btn');
        endBtn.addEventListener('click', () => {
            elsRunning = false;
            stopNow = true;
            globalPointSet = new Set();
            initArea();
        });

        let levelUpBtn = document.getElementById('level-up');
        levelUpBtn.addEventListener('click', () => {
            level++;
            drawLevel()
        })

        let levelDownBtn = document.getElementById('level-down');
        levelDownBtn.addEventListener('click', () => {
            if (level > 0) {
                level--;
                drawLevel();
            }
        })

    </script>
</body>

</html>
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值