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

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

文件构成

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

项目结构

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

代码逻辑

tetris.html分析

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

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

tetrominoes.js分析

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

  1. const I = [
  2. [
  3. [0, 0, 0, 0],
  4. [1, 1, 1, 1],
  5. [0, 0, 0, 0],
  6. [0, 0, 0, 0],
  7. ],
  8. [
  9. [0, 0, 1, 0],
  10. [0, 0, 1, 0],
  11. [0, 0, 1, 0],
  12. [0, 0, 1, 0],
  13. ],
  14. [
  15. [0, 0, 0, 0],
  16. [0, 0, 0, 0],
  17. [1, 1, 1, 1],
  18. [0, 0, 0, 0],
  19. ],
  20. [
  21. [0, 1, 0, 0],
  22. [0, 1, 0, 0],
  23. [0, 1, 0, 0],
  24. [0, 1, 0, 0],
  25. ]
  26. ];
  27. const J = [
  28. [
  29. [1, 0, 0],
  30. [1, 1, 1],
  31. [0, 0, 0]
  32. ],
  33. [
  34. [0, 1, 1],
  35. [0, 1, 0],
  36. [0, 1, 0]
  37. ],
  38. [
  39. [0, 0, 0],
  40. [1, 1, 1],
  41. [0, 0, 1]
  42. ],
  43. [
  44. [0, 1, 0],
  45. [0, 1, 0],
  46. [1, 1, 0]
  47. ]
  48. ];
  49. const L = [...]
  50. ...

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

tetris.js分析

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

const常量定义

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

  1. const cvs = document.getElementById("tetris");
  2. const ctx = cvs.getContext("2d"); // Canvas绘图环境,2d为唯一合法值.
  3. const scoreElement = document.getElementById("score");
  4. const ROW = 20;
  5. const COL = COLUMN = 10;
  6. const SQ = squareSize = 20;
  7. const VACANT = "WHITE"; // 空方块的颜色

drawSquare绘制方块

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

  1. function drawSquare(x,y,color) {
  2. ctx.fillStyle = color; // 填充
  3. ctx.fillRect(x*SQ,y*SQ,SQ,SQ);
  4. ctx.strokeStyle = "BLACK"; // 描边
  5. ctx.strokeRect(x*SQ,y*SQ,SQ,SQ);
  6. }

drawBoard绘制board

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

  1. // 创建
  2. let board = [];
  3. for(r = 0; r < ROW; r++) {
  4. board[r] = [];
  5. for(c = 0; c < COL; c++) {
  6. board[r][c] = VACANT;
  7. }
  8. }
  9. // 绘制
  10. function drawBoard(){
  11. for(r = 0; r < ROW; r++) {
  12. for(c = 0; c < COL; c++) {
  13. drawSquare(c,r,board[r][c]);
  14. }
  15. }
  16. }
  17. // 执行
  18. drawBoard();

创建piece数组以及实现randomPiece

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

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

  1. // 定义piece和对应颜色
  2. const PIECES = [
  3. [Z,"red" ],
  4. [S,"green" ],
  5. [T,"grey" ],
  6. [O,"blue" ],
  7. [L,"purple"],
  8. [I,"cyan" ],
  9. [J,"orange"]
  10. ];
  11. // 生成随机的piece
  12. function randomPiece() {
  13. let r = randomN = Math.floor(Math.random() * PIECES.length) // 0 -> 6
  14. return new Piece(PIECES[r][0],PIECES[r][1]);
  15. }
  16. let p = randomPiece();

Piece类及若干方法

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

  1. function Piece(tetromino,color) {
  2. this.tetromino = tetromino;
  3. this.color = color;
  4. this.tetrominoN = 0; // 从第一个组合开始
  5. this.activeTetromino = this.tetromino[this.tetrominoN];
  6. // 生成坐标
  7. this.x = 3;
  8. this.y = -2; // 在画布外
  9. }
  • fill方法

用以填充方块.

  1. Piece.prototype.fill = function(color) {
  2. for(r = 0; r < this.activeTetromino.length; r++) {
  3. for(c = 0; c < this.activeTetromino.length; c++) {
  4. // 只绘制非空方块
  5. if(this.activeTetromino[r][c]) {
  6. drawSquare(this.x + c,this.y + r, color);
  7. }
  8. }
  9. }
  10. }
  • draw方法

用以在board上绘制piece.

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

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

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

让piece快速下落.

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

moveLeftmoveRight方法

让piece左右移动.

  1. Piece.prototype.moveRight = function() {
  2. if(!this.collision(1,0,this.activeTetromino)) {
  3. this.unDraw();
  4. this.x++; // 右移
  5. this.draw();
  6. }
  7. }
  8. Piece.prototype.moveLeft = function() {
  9. if(!this.collision(-1,0,this.activeTetromino)) {
  10. this.unDraw();
  11. this.x--; // 左移
  12. this.draw();
  13. }
  14. }
  • rotate方法

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

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

  1. Piece.prototype.rotate = function() {
  2. let nextPattern = this.tetromino[(this.tetrominoN + 1) % this.tetromino.length]; // 0 -> 3
  3. let kick = 0;
  4. if(this.collision(0,0,nextPattern)) {
  5. if(this.x > COL/2) { // ??
  6. // 右墙
  7. kick = -1; // 需要左移
  8. }else{
  9. // 左墙
  10. kick = 1; // 需要右移
  11. }
  12. }
  13. if(!this.collision(kick,0,nextPattern)) {
  14. this.unDraw();
  15. this.x += kick;
  16. this.tetrominoN = (this.tetrominoN + 1) % this.tetromino.length; // (0+1) % 4 => 1
  17. this.activeTetromino = this.tetromino[this.tetrominoN];
  18. this.draw();
  19. }
  20. }
  • lock方法

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

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

  1. let score = 0;
  2. Piece.prototype.lock = function() {
  3. for(r = 0; r < this.activeTetromino.length; r++) {
  4. for(c = 0; c < this.activeTetromino.length; c++) {
  5. // 跳过空方块
  6. if(!this.activeTetromino[r][c]) {
  7. continue;
  8. }
  9. // piece在顶部被锁,则游戏结束
  10. if(this.y + r < 0) {
  11. alert("Game Over");
  12. // 停止对动画框架的请求
  13. gameOver = true;
  14. break;
  15. }
  16. // 锁定piece
  17. board[this.y+r][this.x+c] = this.color;
  18. }
  19. }
  20. // 清除满行
  21. for(r = 0; r < ROW; r++) {
  22. let isRowFull = true;
  23. for(c = 0; c < COL; c++) {
  24. isRowFull = isRowFull && (board[r][c] != VACANT);
  25. }
  26. if(isRowFull) {
  27. // 如果该行已满,把上面的所有方块下移一格
  28. for(y = r; y > 1; y--) {
  29. for( c = 0; c < COL; c++) {
  30. board[y][c] = board[y-1][c];
  31. }
  32. }
  33. // 顶行以上没有其他行
  34. for(c = 0; c < COL; c++) {
  35. board[0][c] = VACANT;
  36. }
  37. // 加分
  38. score += 10;
  39. }
  40. }
  41. // 更新board
  42. drawBoard();
  43. // 更新分数
  44. scoreElement.innerHTML = score;
  45. }
  • collision方法

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

  1. Piece.prototype.collision = function(x,y,piece) {
  2. for(r = 0; r < piece.length; r++) {
  3. for(c = 0; c < piece.length; c++) {
  4. // 跳过空方块
  5. if(!piece[r][c]) {
  6. continue;
  7. }
  8. // 移动之后的piece的坐标
  9. let newX = this.x + c + x;
  10. let newY = this.y + r + y;
  11. // 出界
  12. if(newX < 0 || newX >= COL || newY >= ROW) {
  13. return true;
  14. }
  15. // 跳过newY < 0; board[-1]会让游戏崩溃(?)
  16. if(newY < 0){
  17. continue;
  18. }
  19. // 检测位置是否已有方块
  20. if(board[newY][newX] != VACANT) {
  21. return true;
  22. }
  23. }
  24. }
  25. return false; // 上述条件均不满足,则没有碰撞
  26. }

CONTROL控制piece移动

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

  1. document.addEventListener("keydown",CONTROL); // 监听'键盘按下'事件
  2. function CONTROL(event) {
  3. if(event.keyCode == 37) { // <-
  4. p.moveLeft();
  5. }else if(event.keyCode == 38) { // ^
  6. p.rotate();
  7. }else if(event.keyCode == 39) { // ->
  8. p.moveRight();
  9. }else if(event.keyCode == 40) { // v
  10. p.moveDown();
  11. }
  12. }

drop控制piece自由下落

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

  1. let dropStart = Date.now(); // 游戏开始时开始计时
  2. let gameOver = false;
  3. function drop() {
  4. let now = Date.now();
  5. let delta = now - dropStart;
  6. if(delta > 1000) {
  7. p.moveDown();
  8. dropStart = Date.now();
  9. }
  10. if(!gameOver) {
  11. requestAnimationFrame(drop);
  12. }
  13. }
  14. drop();

代码下载

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

[JS]使用JavaScript实现简易俄罗斯方块的更多相关文章

  1. JavaScript 实现简易版贪吃蛇(Day_13)

    时光永远在变迁,你始终要丢下过去. 使用语言 JavaScript  概述 运用JavaScript  实现简易版<贪吃蛇>.     Html 页面 1 <!DOCTYPE htm ...

  2. [JS]jQuery,javascript获得网页的高度和宽度

    [JS]jQuery,javascript获得网页的高度和宽度网页可见区域宽: document.body.clientWidth 网页可见区域高: document.body.clientHeigh ...

  3. 了不起的Node.js: 将JavaScript进行到底(Web开发首选,实时,跨多服务器,高并发)

    了不起的Node.js: 将JavaScript进行到底(Web开发首选,实时,跨多服务器,高并发) Guillermo Rauch 编   赵静 译 ISBN 978-7-121-21769-2 2 ...

  4. Rainyday.js – 使用 JavaScript 实现雨滴效果

    Rainyday.js 背后的想法是创建一个 JavaScript 库,利用 HTML5 Canvas 渲染一个雨滴落在玻璃表面的动画.Rainyday.js 有功能可扩展的 API,例如碰撞检测和易 ...

  5. JS Nice – JavaScript 代码美化和格式化工具

    JS Nice 是一款让经过混淆处理的 JavaScript 代码可读更好的工具.它使用一种新型的用于 JavaScript 代码美化的去混淆和去压缩引擎.JSNice 采用先进的机器学习和程序分析技 ...

  6. Node.js: What is the best "full stack web framework" (with scaffolding, MVC, ORM, etc.) based on Node.js / server-side JavaScript? - Quora

    Node.js: What is the best "full stack web framework" (with scaffolding, MVC, ORM, etc.) ba ...

  7. three.js是JavaScript编写的WebGL第 三方库

    three.js是JavaScript编写的WebGL第 三方库.提供了非常多的3D显示功能.Three.js 是一款运行在浏览器中的 3D 引擎,你可以用它创建各种三维场景,包括了摄影机.光影.材质 ...

  8. js基础--javaScript数据类型你都弄明白了吗?绝对干货

    欢迎访问我的个人博客:http://www.xiaolongwu.cn 数据类型的分类 JavaScript的数据类型分为两大类,基本数据类型和复杂数据类型. 基本数据类型:Null.Undefine ...

  9. node.js和JavaScript的关系

    node.js是一个基于 Chrome V8 引擎的 JavaScript 运行时环境. 一.类比JavaScript和java JavaScript java V8 JVM node.js JRE ...

随机推荐

  1. leaflet加载各种地图

    Leaflet调用各种地图的功能十分复杂,幸好有leaflet.ChineseTmsProviders这个插件,这四种地图直接就可以加载进来,十分方便. 下面是我做的例子: <!DOCTYPE ...

  2. numpy和pandas的基础索引切片

    Numpy的索引切片 索引 In [72]: arr = np.array([[[1,1,1],[2,2,2]],[[3,3,3],[4,4,4]]]) In [73]: arr Out[73]: a ...

  3. crack|erosion|strip|

    V-ERG (使)破裂;(使)裂开;(使)断裂 If something hard cracks, or if you crack it, it becomes slightly damaged, w ...

  4. EventBus 3.0 的基本使用

    EventBus 3.0 的基本使用 1.什么是EventBus? EventBus 是一个Android端优化的publish/subscribe消息总线,简化了应用程序内各组件间.组件与后台线程间 ...

  5. js 实现排序算法 -- 选择排序(Selection Sort)

    原文: 十大经典排序算法(动图演示) 选择排序(Selection Sort) 选择排序(Selection-sort)是一种简单直观的排序算法.它的工作原理:首先在未排序序列中找到最小(大)元素,存 ...

  6. 【转】蛋糕尺寸(寸)、尺寸(CM)、重量(磅)、食用人数对照换算参考表

    转自:https://www.douban.com/note/324832054/ 蛋糕尺寸(寸).尺寸(CM).重量(磅).食用人数对照换算参考表 馋嘴猫DIY烘焙 2014-01-04 12:15 ...

  7. 基于seo的话 一个页面里的h1标签应该控制在多少个

    不能出现多个,一个页面只能出现一次,次数多了就会造成权重分散

  8. 一个自动递增生成目录和文件的cop文件类

    package com.hudong.util.orther; import java.io.File; import java.io.FileInputStream; import java.io. ...

  9. Leetcode 412.FizzBuzz

    题目描述 写一个程序,输出从 1 到 n 数字的字符串表示. 1. 如果 n 是3的倍数,输出"Fizz": 2. 如果 n 是5的倍数,输出"Buzz": 3 ...

  10. Kafka常用命令及配置文件

    创建topic,指定备份分区数 bin/kafka-topics.sh --create --zookeeper zk:2181 --replication-factor 2 --partitions ...