曾经用 Turbo C++ 3.0 写过 DOS 下的俄罗斯方块,不久之后又用 VB 写了另一个版本。这次决定用 JavaScript 再写一个并非完全心血来潮,从技术上来说,主要是想尝试使用 webpack + babel 构建的纯 es6 前端项目。

项目结构

这是一个纯静态项目,而且 HTML 只有一页,就是 index.html。样式表内容不多,还是习惯用 LESS 来写,不喜欢用 sass 的原因其实很直白——不想装逼(Ruby)。

重点自然是在脚本上,一个是想尝试完整的 ES6 语法,包括 import/export 的模块管理;二个是想尝试像构建静态语言项目那样,使用构建的思想,通过 webpack + babel 构建出 es5 语法的目标脚本。源(es6语法,模块化)==> 目标(es5语法,打包)

项目中使用了 jQuery,但是因为习惯,不想把 jQuery 打包在目标脚本中,也不想手工去下载,所以干脆尝试了一下 bower。相比手工下载,使用 bower 是有好处的,至少 bower install 可以写入构建脚本。

一开始对项目目录结构考虑得不是特别清楚,所以建出来的目录结构其实有点乱。整个目录结构如下[root>

|-- index.html : 入口

|-- js/ : 构建生成的脚本

|-- css/ : 构建生成的样式表

|-- lib/ : bower 引入的库

`-- app/ : 前端源文件

|-- less : 样式表源文件

`-- src : 脚本(es6)源文件

构建配置

前端构建脚本部分使用的是 webpack + babel,样式表使用的 less,然后通过 gulp 组织起来。所有前端构建配置和源代码都放在 app 目录下。app 目录下是个 npm 项目,有 gulpfile.js 和 webpack.config.js 等构建配置。

因为 gulp 之前用过,fulpfile.js 写起来还比较顺手,但是在配置 webpack 的时候费了点劲。

先在网上抄了一个配置const path = require("path");module.exports = { context: path.resolve(__dirname, "src"), entry: [ "./index" ], output: { path: path.resolve(__dirname, "../js/"), filename: "tetris.js"

}, module: { loaders: [

{ test: /\.js$/, exclude: /(node_modules)/, loader: "babel", query: { presets: ["es2015"]

}

}

]

}

};

然后在写的过程中发现需要引入 jQuery,于是又在网上找了半天,抄了一句externals: {

"jquery": "jQuery"

}

不过后来看到说推荐用 ProvidePlugin ,以后再来研究了。

在代码初成,初次运行的时候,发现调试非常麻烦,因为编译过,找不到错误在 es6 的源码位置。这时候才发现缺少了非常重要的 source map。于是又在网上搜了半天,加上了devtool: "source-map"

程序分析

因为以前写过,所以在数据结构上还是有点映像,游戏区就对应着一个二维数组。每个图形就是一组有着相对位置关系的坐标,当然还有颜色定义。

所有行为都是通过数据(坐标)的变化来实现的。而障碍物(已固定下来的小方块)判断则是通过当前图形位置及定义中所有小方块的相对位置计算出各小方块坐标之后检查大矩阵对应坐标是否存在小方块数据来判断。这需要提前计算出当前图形在下一个形态所需要占用的坐标列表。

方块的自动下落是通过时钟周期控制。如果还要处理消除动画,就可能需要两个时钟周期控制。当然可以取两个时钟周期的了大公约数来合并成一个公共时钟周期,但俄罗斯方块的动画相当简单,似乎没有必要进行这么复杂的处理——可以考虑在消除时暂停下落时钟周期,消除完成之后再重启。

交互部分主要靠键盘处理,只需要给 document 绑定 keydown 事件处理就好。

方块模型

传统的俄罗斯方块只有 7 种图形,加上旋转变形一共也才 19 个图形。所以需要定义的图形不多,懒得去写旋转算法,直接用坐标来定义了。于是先用WPS表格把图形画出来了:

然后照此图形,在 JavaScript 中定义结构。设想的数数据结构是这样的SHAPES: [Shape] // 预定义所有图形Shape: { // 图形的结构

colorClass: string, // 用于染色的 css class

forms: [Form] // 旋转变形的组合}

Form: [Block] // 图形变形,是一组小方块的坐标Block: { // 小方块坐标

x: number, // x 表示横向

y: number // y 表示纵向}

其中 SHAPES 、 Form 都直接用数组表示, Block 结构简单,直接使用字面对象表示,只需要定义一个 Shape 类(当时考虑加些方法在里面,但后来发现没必要)class Shape {

constructor(colorIndex, forms) { this.colorClass = `c${1 + colorIndex % 7}`; this.forms = forms;

}

}

为了偷懒, SHAPE 是用一个三维数组的数据,通过 Array.prototype.map() 来得到的 Shape 数组class Shape {

constructor(colorIndex, forms) { this.colorClass = `c${1 + colorIndex % 7}`; this.forms = forms;

}

}

export const SHAPES = [ // 正方形

[

[[0, 0], [0, 1], [1, 0], [1, 1]]

], // |

[

[[0, 0], [0, 1], [0, 2], [0, 3]],

[[0, 0], [1, 0], [2, 0], [3, 0]]

],

// .... 省略,请参阅文末附上的源码地址].map((defining, i) => { // data 就是上面提到的 forms 了,命名时没想好,后来也没改

const data = defining.map(form => { // 计算 right 和 bottom 主要是为了后面的出界判断

let right = 0;

let bottom = 0;

// point 就是 block,当时取名的时候没想好

const points = form.map(point => {

right = Math.max(right, point[0]);

bottom = Math.max(bottom, point[1]); return {

x: point[0],

y: point[1]

};

});

points.width = right + 1;

points.height = bottom + 1; return points;

}); return new Shape(i, data);

});

游戏区模型

虽然游戏区只有一块,但是就画图的这部分行为来说,还有一个预览区的行为与之相仿。游戏区除了显示外还需要处理方块下落、响应键盘操作左、右、下移及变形、堆积、消除等。

对于显示,定义了一个 Matrix 类来处理。 Matrix 主要是用来在 HTML 中创建用来显示每一个小方块的 以及根据数据绘制小方块。当然所谓的“绘制”其实只是设置 的 css class 而已,让浏览器来处理绘制的事情。

Matrix 根据构建传入的 width 和 height 来创建 DOM,每一行是一个

作为容器,但实际需要操作的是每一行中,由 表示的小方块。所以其实 Matrix 的结构也很简单,这里简单的列出接口,具体代码参考后面的源码链接class Matrix {

constructor(width, height) {}

build(container) {}

render(blockList) {}

}

逻辑控制

上面提到主游戏区有一些逻辑控制,而 Matrix 只处理了绘制的问题。所以另外定义了一个类: Puzzle 来处理控制和逻辑的问题,这些问题包括

预览图形的生成的显示

游戏图形和已经固定的方块显示

进行中的图形行为(旋转、左移、右移、下移等)

边界及障碍判断

下落结束后可消除行的判断

下落动画处理

消除动画处理

消除后的数据重算(因为位置改变)

Game Over 判断

......

其实比较关键的问题是图形和固定方块的显示、边界及障碍判断、动画处理。

游戏区方块绘制

已经确定了 Matrix 用于处理绘制,但绘制需要数据,数据又分两部分。一部分是当前下落中的图形,其位置是动态的;另一部分是之前落下的图形,已经固定在游戏区的。

从当前下落中的图形生成一个 blocks 数组,再将已经固定的小方块生成另一个 blocks 数组,合并起来,就是 Matrix.render() 的数据。 Matrix 拿到这个数据之后,先遍历所有 ,清除颜色 class,再遍历得到的数据,根据每一个 block 提供的位置和颜色,去设置对应的 的 css class。这样就完成了绘制。

边界和障碍判断

之前提到的 Shape 只是一个形状的定义,而下落中的图形是另一个实体,由于 Shape 命名已经被占用了,所以源代码中用 Block 来对它命名。

这个命名确实有点乱,需要这样解理: Shape -> ShapeDefinition ; Block -> Shape 。

现在下落中的图形是一个 Block 的实例(对象)。在判断边界和障碍判断的过程中需要用到其位置信息、边界信息(right、bottom)等;另外还需要知道它当前是哪一个旋转形态……所以定义了一些属性。

不过关键问题是需要知道它的下个状态(位置、旋转)会占用哪些坐标的位置。所以定义了几个方法

fasten() ,不带参数的时候返回当前位置当前形态所占用的坐标,主要是绘图用;带参数时可以返回指定位置和指定形态所需要占用的坐标。

fastenOffset() ,因为通常需要的位移坐标数据都相对原来的位置只都有少量的偏移,所以定义这个方法,以简化调用 fasten() 的参数。

fastenRotate() ,简化旋转后对 fasten() 的调用。

这里有一点需要注意,就是有图形在到在边界之后,旋转可能会造成出界。这种情况下需要对其进行位移,所以 Block 的 rotate() 和 fastenRotate() 都可以输入边界参数,用于计算修正位置。而修正位置则是通过模块中一个局部函数 getRotatePosition() 来实现的。

动画控制

前面已经提到了,动画时钟分两个,下落动画时钟和消除动画时钟。对于人工操作引起的动画,在操作之后直接重绘,就不需要通过时钟来进行了。

考虑到在开始消除动画时需要暂停下落动画,之后又要重新开始。所以为下落动画时钟定义为一个 Timer 类来控制 stop() 和 start() ,内部实现当然是用的 setInterval() 和 clearInterval() 。当然 Timer 也可以用于消除动画,但是因为在写消除动画的时候发现代码比较简单,就直接写 setInterval() 和 clearInterval() 解决了。

在 Puzzle 类中,某个图形下图到底的时候,通过 fastenCurent() 为固定它,这个方法里固定了当前图形之后会调用 eraseRows() 来检查和删除已经填满的行。从数据上消除和压缩行都是在这里处理的,同时这里还进行了消除行的动画处理——对需要消除的行从左到右清除数据并立即重绘。let columnIndex = 0;const t = setInterval(() => { // fulls 是找出来的需要消除的行

fulls.forEach((rowIndex) => {

matrix[rowIndex][columnIndex] = null; this.render();

});

// 消除列达到右边界时结束动画

if (++columnIndex >= this.puzzle.width) {

clearInterval(t);

reduceRows(); this.render(); this.process();

}

}, 10);

小结

俄罗斯方块的算法并不难,但这个仓促完成的小游戏中仍然存在一些问题需要将来处理掉:

没有交互方式的开始和结束,页面一旦打开就会持续运行。

还没有引入计分

每次绘制都是全部重绘,应该可以优化为局部(变化的部分)重绘

更多用JavaScript写一个俄罗斯方块相关文章请关注PHP中文网!

本文原创发布php中文网,转载请注明出处,感谢您的尊重!

php开发俄罗斯方块,用JavaScript写一个俄罗斯方块相关推荐

  1. 用 Python 写一个俄罗斯方块游戏

    使用 Python 的 PyGame 库写一个俄罗斯方块游戏的逐步指南 在这篇教程中,我们会用 Python 的 PyGame 库写一个简单的俄罗斯方块游戏.里面的算法很简单,但对新手可能有一点挑战性 ...

  2. python俄罗斯方块算法详解_用 Python 写一个俄罗斯方块游戏 (

    @@ -2,34 +2,34 @@ > * 原文作者:[Dr Pommes](https://medium.com/@pommes) > * 译文出自:[掘金翻译计划](https://g ...

  3. Python 写一个俄罗斯方块游戏

    使用 Python 的 PyGame 库写一个俄罗斯方块游戏的逐步指南 很多人学习python,不知道从何学起. 很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手. 很多已经做案例 ...

  4. JavaScript写一个虚拟软键盘,可拼音输入

    在某些业务场景中,无法使用外接实体键盘,也不方便调出windows自带的虚拟键盘,这时候我们可以利用JavaScript写一个虚拟键盘,实现点击输入框即可唤醒,大小写中.英文及数字的输入.可满足实际使 ...

  5. 用JavaScript写一个正则表达式测试工具

    用JavaScript写一个正则表达式测试工具 <!DOCTYPE html> <html><head><meta charset="utf-8&q ...

  6. 【Linux服务器开发系列】手写一个用户态网络协议栈,瞬间提升你网络功底丨netmap/dpdk的实现

    手写一个用户态网络协议栈,瞬间提升你网络功底 1. 网卡基础架构 2. netmap/dpdk的实现 3. 网络协议栈实战 [Linux服务器开发系列]手写一个用户态网络协议栈,瞬间提升你网络功底丨n ...

  7. 使用JavaScript写一个三级下拉框联动

    使用JavaScript写一个三级下拉框联动 首先我们需要确定思路,定义三个下拉框: 1.从后台获取包含的数组1.1定义数组2.将数组中的信息添加到下拉框中2.1遍历数组,将数组信息添加下拉框2.2每 ...

  8. 用JavaScript写一个可以聊天的桌面宠物

    好的,我们可以用 JavaScript 写一个聊天桌面宠物.首先,我们需要使用 JavaScript 的 GUI 库,比如 Electron 或 NW.js 来创建一个桌面应用程序.然后,我们可以使用 ...

  9. DuiVision开发教程(2)-如何写一个简单的界面程序

    基于DuiVision界面库开发的界面程序主要包括如下几部分内容: 1.资源定义,包括图片资源.各个窗口界面的xml定义文件 2.事件处理类代码,用于处理界面响应消息 3.其他业务逻辑代码 下面举例说 ...

最新文章

  1. Linux在shell终端中清空DNS缓存,刷新DNS的方法(ubuntu,debian)
  2. 思科发布安全大数据分析架构 OpenSOC
  3. MS/OR国际期刊排名
  4. easyui datagrid onLoadSuccess方法 正确使用
  5. mysql数据库的注释语句是_coding++ :MySQL 使用 SQL 语句查询数据库所有表注释已经表字段注释...
  6. java上转型对象特点_Java 浅析三大特性之一继承
  7. 少一些计较多_做人,少一点套路,多一些真诚,少一点计较,多一些宽容
  8. Qt-做一个快速打包插件(一键完成项目软件打包)
  9. html设计有项目的页面,HTML+CSS项目开发经验总结(推荐)
  10. netframework2.0安装
  11. 高通工具QXDM、QCAT和QPST
  12. 建筑施工技术【14】
  13. 阿里云中标“金关工程二期”大数据云项目,总金额8568万!(含标单)
  14. 色彩混合模型——两种三原色
  15. H3C运维审计系统 Web配置指导(笔记)
  16. c语言如何编程出声音,C语言播放声音最简单的两种方法
  17. matlab 反步法,反步法的Matlab仿真学习程序
  18. 成果分享:边缘智能视频预取和缓存机制
  19. 不要使用矿泉水瓶接热水喝——致癌
  20. 微信公众平台消息接口开发(30)大众点评商户信息团购及优惠券查询

热门文章

  1. Unity 使用VideoPlayer做一个类似于视频播放器的界面
  2. Bidirectional Machine Reading Comprehension for Aspect Sentiment Triplet Extraction 论文阅读
  3. 火狐双击关闭标签页设置
  4. 四路监控 php,4种常见道路监控系统,老司机带你认识,必须了解的3种摄像头
  5. AVR与ARM区别以及常用Arduino
  6. 4款电子书制作工具比拼
  7. Miranda显IP和显QQ版本号
  8. VL02N、VL09交货单相关增强
  9. 2021年毕节民中高考成绩查询,毕节民族中学2021年排名
  10. dijkstra的matlab程序,最短路径算法dijkstra的matlab程序,让大家来找茬,交流