[JS]使用JavaScript实现简易俄罗斯方块

[JS]使用JavaScript实现简易俄罗斯方块

首先,大家可以点击此处来预览一下游戏效果,随后将会以此为模板讲解如何使用JavaScript实现这样一个简易的俄罗斯方块项目(以下简称"该项目").

文件构成

┬ js ┬ tetris.js
│    └ tetrominoes.js
└ tetris.html
  • tetris.html是该项目的页面文件,提供一个简单的用户交互界面,由文本和画布构成.文本负责作者信息,操作说明等普通文本;画布则是为了绘制游戏界面.
  • tetris.js是该项目的核心文件,里面包含了所有游戏逻辑,也是本篇文章会重点讲解的文件.
  • tetrominoes.js是该项目存储俄罗斯方块组合的文件,里面存有俄罗斯方块所有7种组合以及他们的变体,便于tetris.js调用.

项目结构

用画图工具画的...比较粗糙.

8tvnh9.png

代码逻辑

tetris.html分析

该页面将游戏呈现给用户,结构很简单,引用两个js文件,若干段普通文字说明,一个DOM控制的分数,以及一个用来显示游戏区域的canvas画布.游戏是在canvas内绘制的.

    <canvas id="tetris" width="200" height="400"></canvas>
    <div>
        分数: <div id="score">0</div>
    </div>
    <p style="text-align: center;">操作说明: 使用光标键操作,上-旋转方块,下-快速下落,左/右-平移方块.</p>
    <script src="./js/tetrominoes.js"></script>
    <script src="./js/tetris.js"></script>

tetrominoes.js分析

存储7种俄罗斯方块组合以及他们的旋转变种.

const I = [
    [
        [0, 0, 0, 0],
        [1, 1, 1, 1],
        [0, 0, 0, 0],
        [0, 0, 0, 0],
    ],
    [
        [0, 0, 1, 0],
        [0, 0, 1, 0],
        [0, 0, 1, 0],
        [0, 0, 1, 0],
    ],
    [
        [0, 0, 0, 0],
        [0, 0, 0, 0],
        [1, 1, 1, 1],
        [0, 0, 0, 0],
    ],
    [
        [0, 1, 0, 0],
        [0, 1, 0, 0],
        [0, 1, 0, 0],
        [0, 1, 0, 0],
    ]
];

const J = [
    [
        [1, 0, 0],
        [1, 1, 1],
        [0, 0, 0]
    ],
    [
        [0, 1, 1],
        [0, 1, 0],
        [0, 1, 0]
    ],
    [
        [0, 0, 0],
        [1, 1, 1],
        [0, 0, 1]
    ],
    [
        [0, 1, 0],
        [0, 1, 0],
        [1, 1, 0]
    ]
];

const L = [...]
           
...

以此类推,将7种(Z S T O L I J)组合以及对应的旋转变种全部枚举在文件中.

tetris.js分析

游戏核心,由若干函数和Piece对象构成.

const常量定义

先定义一些常量,在后面代码里能够更加简洁的调用一些函数.

const cvs = document.getElementById("tetris");
const ctx = cvs.getContext("2d"); // Canvas绘图环境,2d为唯一合法值.
const scoreElement = document.getElementById("score");

const ROW = 20;
const COL = COLUMN = 10;
const SQ = squareSize = 20;
const VACANT = "WHITE"; // 空方块的颜色

drawSquare绘制方块

以左上角为原点,x正方向朝右,y正方向朝下,以SQ为单位长度,在坐标(x,y)处绘制方块,是绘制游戏区域的一个基础函数.

function drawSquare(x,y,color) {
    ctx.fillStyle = color; // 填充
    ctx.fillRect(x*SQ,y*SQ,SQ,SQ);

    ctx.strokeStyle = "BLACK"; // 描边
    ctx.strokeRect(x*SQ,y*SQ,SQ,SQ);
}

drawBoard绘制board

画出游戏区域,就是由网格构成的那片区域,同时也给俄罗斯方块提供移动空间.

// 创建
let board = [];
for(r = 0; r < ROW; r++) {
    board[r] = [];
    for(c = 0; c < COL; c++) {
        board[r][c] = VACANT;
    }
}
// 绘制
function drawBoard(){
    for(r = 0; r < ROW; r++) {
        for(c = 0; c < COL; c++) {
            drawSquare(c,r,board[r][c]);
        }
    }
}
// 执行
drawBoard();

创建piece数组以及实现randomPiece

规定piece(即俄罗斯方块组合)的各个颜色,分为7个字段,每个字段有一个组合和对应颜色值.

扫描二维码关注公众号,回复: 9890944 查看本文章

randomPiece()内使用随机数实现随机选取7个组合里的一种,返回该组合的模样以及颜色.

// 定义piece和对应颜色
const PIECES = [
    [Z,"red"   ],
    [S,"green" ],
    [T,"grey"  ],
    [O,"blue"  ],
    [L,"purple"],
    [I,"cyan"  ],
    [J,"orange"]
];

// 生成随机的piece
function randomPiece() {
    let r = randomN = Math.floor(Math.random() * PIECES.length) // 0 -> 6
    return new Piece(PIECES[r][0],PIECES[r][1]);
}
let p = randomPiece();

Piece类及若干方法

创建一个Piece类,方便后面对每一块piece进行操作.

function Piece(tetromino,color) {
    this.tetromino = tetromino;
    this.color = color;

    this.tetrominoN = 0; // 从第一个组合开始
    this.activeTetromino = this.tetromino[this.tetrominoN];
    
    // 生成坐标
    this.x = 3;
    this.y = -2; // 在画布外
}
  • fill方法

用以填充方块.

Piece.prototype.fill = function(color) {
    for(r = 0; r < this.activeTetromino.length; r++) {
        for(c = 0; c < this.activeTetromino.length; c++) {
            // 只绘制非空方块
            if(this.activeTetromino[r][c]) {
                drawSquare(this.x + c,this.y + r, color);
            }
        }
    }
}
  • draw方法

用以在board上绘制piece.

Piece.prototype.draw = function() {
    this.fill(this.color);
}
  • unDraw方法

在board擦除指定方块,在移动时和消除整行时调用.

Piece.prototype.unDraw = function() {
    this.fill(VACANT);
}
  • moveDown方法

让piece快速下落.

Piece.prototype.moveDown = function() {
    if(!this.collision(0,1,this.activeTetromino)) {
        this.unDraw();
        this.y++; // 下落
        this.draw();
    }else{
        // 如果已经发生了碰撞,锁定该piece并生成新的piece
        this.lock();
        p = randomPiece();
    }
    
}

moveLeftmoveRight方法

让piece左右移动.

Piece.prototype.moveRight = function() {
    if(!this.collision(1,0,this.activeTetromino)) {
        this.unDraw();
        this.x++; // 右移
        this.draw();
    }
}

Piece.prototype.moveLeft = function() {
    if(!this.collision(-1,0,this.activeTetromino)) {
        this.unDraw();
        this.x--; // 左移
        this.draw();
    }
}
  • rotate方法

让piece旋转.原理是在tetrominoes.js中改变当前piece的旋转变种.

kick是为了解决piece靠近墙时不能旋转的问题,当piece紧靠墙时,在旋转时会根据kick值来调整piece位置.

Piece.prototype.rotate = function() {
    let nextPattern = this.tetromino[(this.tetrominoN + 1) % this.tetromino.length]; // 0 -> 3
    let kick = 0;

    if(this.collision(0,0,nextPattern)) {
        if(this.x > COL/2) { // ??
            // 右墙
            kick = -1; // 需要左移
        }else{
            // 左墙
            kick = 1; // 需要右移
        }
    }

    if(!this.collision(kick,0,nextPattern)) {
        this.unDraw();
        this.x += kick;
        this.tetrominoN = (this.tetrominoN + 1) % this.tetromino.length; // (0+1) % 4 => 1
        this.activeTetromino = this.tetromino[this.tetrominoN];
        this.draw();
    }
}
  • lock方法

固定piece的方法,固定当前被激活的piece后(发生collision时触发),就会生成新的随机piece.

同时也会检测有没有满行,若有则擦除满行的方块,并将其上的所有方块下移一格,更新分数.

let score = 0;

Piece.prototype.lock = function() {
    for(r = 0; r < this.activeTetromino.length; r++) {
        for(c = 0; c < this.activeTetromino.length; c++) {
            // 跳过空方块
            if(!this.activeTetromino[r][c]) {
                continue;
            }
            // piece在顶部被锁,则游戏结束
            if(this.y + r < 0) {
                alert("Game Over");
                // 停止对动画框架的请求
                gameOver = true;
                break;
            }
            // 锁定piece
            board[this.y+r][this.x+c] = this.color;
        }
    }
    // 清除满行
    for(r = 0; r < ROW; r++) {
        let isRowFull = true;
        for(c = 0; c < COL; c++) {
            isRowFull = isRowFull && (board[r][c] != VACANT);
        }
        if(isRowFull) {
            // 如果该行已满,把上面的所有方块下移一格
            for(y = r; y > 1; y--) {
                for( c = 0; c < COL; c++) {
                    board[y][c] = board[y-1][c];
                }
            }
            // 顶行以上没有其他行
            for(c = 0; c < COL; c++) {
                board[0][c] = VACANT;
            }
            // 加分
            score += 10;
        }
    }
    // 更新board
    drawBoard();
    // 更新分数
    scoreElement.innerHTML = score;
}
  • collision方法

用以检测是否发生了碰撞.并且判断游戏失败的条件,如果新生成的piece在画布外发生碰撞,游戏结束.

Piece.prototype.collision = function(x,y,piece) {
    for(r = 0; r < piece.length; r++) {
        for(c = 0; c < piece.length; c++) {
            // 跳过空方块
            if(!piece[r][c]) {
                continue;
            }
            // 移动之后的piece的坐标
            let newX = this.x + c + x;
            let newY = this.y + r + y;
            // 出界
            if(newX < 0 || newX >= COL || newY >= ROW) {
                return true;
            }
            // 跳过newY < 0; board[-1]会让游戏崩溃(?)
            if(newY < 0){
                continue;
            }
            // 检测位置是否已有方块
            if(board[newY][newX] != VACANT) {
                return true;
            }
        }
    }
    return false; // 上述条件均不满足,则没有碰撞
}

CONTROL控制piece移动

监听用户键盘输入,来执行对应方法,操控piece的移动.

document.addEventListener("keydown",CONTROL); // 监听'键盘按下'事件

function CONTROL(event) {
    if(event.keyCode == 37) { // <-
        p.moveLeft();
    }else if(event.keyCode == 38) { // ^
        p.rotate();
    }else if(event.keyCode == 39) { // ->
        p.moveRight();
    }else if(event.keyCode == 40) { // v
        p.moveDown();
    }
}

drop控制piece自由下落

让游戏动起来的根本,每隔一定时间让piece下落一格,动态更新canvas.

let dropStart = Date.now(); // 游戏开始时开始计时
let gameOver = false;
function drop() {
    let now = Date.now();
    let delta = now - dropStart;
    if(delta > 1000) {
        p.moveDown();
        dropStart = Date.now();
    }
    if(!gameOver) {
        requestAnimationFrame(drop);
    }
}

drop();

代码下载

完整代码可以点击此处下载.

猜你喜欢

转载自www.cnblogs.com/Clouds42/p/12509755.html