使用JavaScript实现一个俄罗斯方块
清明假期期间,闲的无聊,就做了一个小游戏玩玩,目前游戏逻辑上暂未发现bug,只不过样子稍微丑了一些-.-
项目地址:https://github.com/Jiasm/tetris
在线Demo:http://blog.jiasm.org/tetris/?width=16&height=40 (修改URL参数可以调整难度)
整体分成三块进行开发,使用面向对象式编程进行开发(其实我更喜欢用函数式编程,但苦于游戏的一些状态用对象来存储会更直观一些):
Game
:- 负责生成新的方块
- 负责方块移动的处理
- 方块触底的判断
- 移除满足清除条件的行
Render
:- 负责用
Game
的数据来渲染整个游戏界面
- 负责用
Controller
:- 负责接受用户输入(上下左右各种操作)并处理
- 向用户反馈当前游戏的状态
这样分层带来了一个好处,我们游戏的逻辑Game
模块并不依赖于当前程序运行的环境,而Render
可以是Canvas
、DOM
,甚至是控制台输出。我们要移植到其他平台,只需要修改Render
即可。
项目结构
忽略了一些与游戏没有直接关系的结构
.
├── model
│ ├── Brick.js
│ ├── Game.js
│ └── index.js
├── utils
│ ├── buildEnum.js
│ ├── deepCopy.js
│ ├── getShape.js
│ ├── index.js
│ ├── lineIndex.js
│ ├── matrixString.js
│ └── rotateArray.js
├── enum
│ ├── gameType.js
│ ├── index.js
│ └── pointType.js
├── data
│ └── shapes.js
├── controller
│ └── index.js
└── view
├── RenderCanvas.js
└── index.js
各目录下的index.js是为了方便同时引用多个文件,大致长这个样子:
export { default as model1 } from './model1'
export { default as model2 } from './model2'然后我们就可以在用到的地方写:
import { model1, model2 } from './XXX'
model
这里是游戏的核心逻辑所在位置。
像俄罗斯方块这种的矩阵类游戏,存储数据最合适的方法就是一个二维数组了。
为了更直观一些,我们选择了游戏的高度作为第一层数组的长度:
matrix = new Array(height).fill(new Array(width)) // width: 2 height: 4
[
[ 1, 1],
[ 1, 1],
[ 1, 1],
[ 1, 1]
]
而且这样选择在一些逻辑处理上也会更方便一些:
- 下移操作时,我们只需改变元素的第一层下标
- 判断是否触底时,我们只需将当前下标 + 1 判断是否有元素即可
我们对数组中的元素进行了定义:
0
: 空,表示当前坐标为空白1
: 新的方块,表示当前活动的方块2
: 老的方块,已经触底固定的方块
接下来,我们就遇到了一个问题,如何处理方块的放置。
我们知道,游戏会不停的向棋盘中加载新的方块。
如果我们每次处理下移的时候,都将当前二维数组中对应的方块元素移除,然后在塞入到新的位置,未免太过繁琐了。
所以我们在初始化数据时,初始化两个二维数组。
当我们加载一个新的方块后,将方块对应的元素塞入其中的一个二维数组。
然后等到我们有进行其他的操作时,比如左右移动,向下之类的。
我们直接使用第二个二维数组覆盖到当前的数组中去,然后再将更改下标后的方块塞入数组。
这样在数据上,我们就完成了方块的移动。
class Game {
init () {
// 初始化两个矩阵
this.matrix = [[], []]
this.oldMatrix = [[], []]
}
move () {
// 重置当前矩阵数据
this.matrix = deepCopy(this.oldMatrix) // 解除引用
// 加载方块数据
this.matrix[y][x1] = 1
this.matrix[y][x2] = 1
}
}
左右移动的处理
左右的移动不能像向下移动一样,单纯的下标+1。
我们需要判断当前的操作是否有效。
比如右侧如果遇到了障碍物或者到达边缘,我们肯定是不能够再进行移动的。
// blend 为活动砖块的形状描述 [[1, 1, 1], [0, 1, 0]] 类似这样的结构
if (
x >= width - brickWidth ||
blend.some((row, rowIndex) => {
let _pos = oldMatrix[y + rowIndex]
return row && row[brickWidth - 1] && _pos && _pos[x + brickWidth]
})
)
return // 右侧有障碍物,无法移动
使用类似这样的逻辑进行判断,保证当前方块向右移动后不会覆盖之前的方块。
快速向下的处理
我看有些游戏实现的,貌似下降触发只是加速下降而已(这种情况只需要改变定时下降的速度即可)-.-这里的实现是,直接触底
所以就会遇到一个问题,当前砖块最多可以下降到什么位置?
[1, 1, 1]
[0, 0, 0]
[0, 2, 0]
[2, 2, 2]
就像这样的一个数据,0|2
这两列都可以向下移动两列,但是这样就会导致中间一列的重叠。
我们一定要取出下降幅度最小的那个值。
所以我们就要算出最后一行1的下标以及第一行2的下标,将这两个下标进行相减,最小值即为我们当前方块可下降的距离。
旋转方块的处理
旋转方块应该是游戏中比较复杂的一块逻辑了。
绝不是仅仅简单的将方块的二维数组由行改为列,在有些时候,我们还需要判断方块是否可以进行旋转。
就像这样的,中间的绿色长条是不能够进行旋转的。
所以我们要先拿到旋转后的数据,来与当前游戏中的数据进行比较,检验是否会出现重叠的情况,如果出现了,则表示不能够进行旋转。
触底检测
每完成一个移动的动作后,我们都需要进行方块的触底检测。
也就是判断当前方块下,是否已经有元素占位,如果有的话,则表示已经触底了,当前元素就会被固定进矩阵数组中。
同样的,我们在判断时,不需要将方块所有的下标都检查一遍,只需要检查最底部一层的有效元素即可。
[1, 1],
[0, 1],
[0, 1],
像这样的一个方块,我们仅需要判断第一列的第二行&第二列的第四行是否有元素即可完成检查。
移除行
当某一行被填满元素后,我们就要将它进行移除。
在触底检测触发后,如果有方块被固定进数组,此时我们再进行移除行的操作。
因为如果没有新的方块进入,移除行的这步操作就不是必要的。
同时,得分的计数也应该在此处进行,我们将移除的行数进行记录,获取到的行数便是得分了。
至此,所有有关矩阵数据的操作就结束了。Game
对象只去维护这么一个二维数组,对象本身不包含任何游戏相关的操作,只会在被调用时进行对应的处理。
然后生成新的二维数组。
utils
这里放置了一些比较通用的方法,用来提高开发效率使用。
比如获取方块最底部一层的下标之类的工具函数。
enum
存放了一些状态的枚举,游戏状态以及方块所对应的状态,类似这样的数据:
{
empty: 0,
newBrick: 1,
oldBrick: 2
}
data
存放了游戏中各种使用到的方块信息。
正方形,梯形之类的方块在二维数组中所对应的描述。
controller
就是上边我们所说的,用来与用户交互的模块,由Controller
来获取游戏相关的信息,并调用Render
进行渲染。
监听键盘事件,在页面中渲染一些控制按钮。
以及定时触发Game
的下落方法。
view
游戏界面的渲染部分,目前选定的是使用canvas
,所以只写了RenderCanvas
。
在渲染的这部分,稍微做了一些优化处理,将活动中的方块与固定的方块进行分开渲染。
这样在用户操作上下左右移动时,并不会重新渲染整个游戏布局,而只是渲染活动方块的canvas
。
小记
两天多的时间进行开发,其中有半天时间在修复FlowType
的Warning提示。。。
搞完了以后,觉得实现这个的主要难点就在于方块旋转&触底的判断这里了。
能够清晰的管理游戏对应的二维数组,这个游戏开发起来就会很顺畅。
界面还有待优化。
Tips
我的博客即将搬运同步至腾讯云+社区,邀请大家一同入驻:https://cloud.tencent.com/developer/support-plan?invite_code=1vas1z072yivn
使用JavaScript实现一个俄罗斯方块的更多相关文章
- [JS]使用JavaScript实现简易俄罗斯方块
[JS]使用JavaScript实现简易俄罗斯方块 首先,大家可以点击此处来预览一下游戏效果,随后将会以此为模板讲解如何使用JavaScript实现这样一个简易的俄罗斯方块项目(以下简称"该 ...
- 用html5的canvas和JavaScript创建一个绘图程序
本文将引导你使用canvas和JavaScript创建一个简单的绘图程序. 创建canvas元素 首先准备容器Canvas元素,接下来所有的事情都会在JavaScript里面. <canvas ...
- SharePoint 自定义的列表页面中添加javascript的一个 For循环语句后,该页面就打不开了。
一个sharepoint 2013的普通的列表的自定义新建页面,我在其中新添加几行javascript代码后页面就打不开了.如图所示: 真是一言不合,友谊的页面说打不开就打不开啊.后来慢慢比对发现是因 ...
- 使用javascript打开一个新页而不被浏览器屏蔽
使用javascript打开一个新页面可以有几种方式,但各有利弊,以下做下分析 1.window.open(url) 这是新手最常用的方法,好处是简单易用,坏处,很简单,会被很多浏览器拦截而导致功能失 ...
- Javascript的一个生产PDF的库: unicode和中文问题的解决
Javascript的一个生产PDF的库: unicode和中文问题的解决基于canvas和jspdf库, 实现用javascript的支持中文pdf生成实用工具.参考:http://javascri ...
- Javascript 判断一个数字是否含有小数点
JavaScript 判断一个数字是否含有小数点,如果含有,则返回该数字:如果不含小数点,则小数点后保留两位有效数字: function hasDot(num){ if(!isNaN(num)){ r ...
- JavaScript 创建一个 form 表单并提交
<!DOCTYPE HTML> <html lang="en-US"> <head> <meta charset="UTF-8& ...
- javascript编写一个简单的编译器(理解抽象语法树AST)
javascript编写一个简单的编译器(理解抽象语法树AST) 编译器 是一种接收一段代码,然后把它转成一些其他一种机制.我们现在来做一个在一张纸上画出一条线,那么我们画出一条线需要定义的条件如下: ...
- Javascript的一个怪现象
javascript有一个怪现象,就是减法也会导致小数位数问题,是一个麻烦的问题,比如. <html><script> var a=10,b=20.1; alert( a - ...
随机推荐
- APNS IOS 消息推送沙盒模式和发布模式
在做.NET向IOS设备的App进行消息推送时候,采用的是PushSharp开源类库进行消息的推送,而在开发过程中,采用的是测试版本的app,使用的是测试的p12证书采用的是ApnsConfigura ...
- README.md用法
简单说明常用标签 # 标题标签与h1~h6 相似 也有6个 = 高级标题标签 - 中标签 * 列表标签 与 <li> 标签相似 ` 实体标签,内容会换行 ``` 实体标签 内容保持编辑 ...
- Unity3D NGUI事件监听的综合管理
首先,将Event Listener挂在按钮上 Event Listener的源码很简单 就是利用C#的时间委托机制 注册了UI场景的事件而已 public class UIEventListener ...
- memcache搭建
memcache搭建步骤: 个人原创,允许转载,请注明出处,作者,否则追究法律责任. 0.1 memcache的服务端0.2 mysql的服务端0.3 memcache 的客户端:lamp架构 1,环 ...
- 分享PHP中的10个实用函数
分享PHP中的10个实用函数 PHP的功能越来越强大,里面有着非常丰富的内置函数.资深的PHP程序员对它们可能都很熟悉,但很多参加PHP培训的PHP初学者,仍然对一些非常有用的函数不太熟悉.这篇文章里 ...
- 归并排序及优化(Java实现)
普通归并排序 public class MergeSort { /** * @param arr 待排序的数组 * @param left 本次归并的左边界 * @param mid 本次归并的中间位 ...
- leetCode:206 反转链表
206. 反转链表 题目:反转一个单链表. 进阶:链表可以迭代或递归地反转.你能否两个都实现一遍? 非递归代码: class Solution { public ListNode reverseLis ...
- struts2基于注解配置action
如果使用struts2,就需要配置文件或者注解,关于struts2的配置文件struts.xml非常熟悉,对于注解可能spring使用的比较多.配置文件的繁琐衬托出了注解的简洁方便,一条或者几条注解解 ...
- c++编码及读写文件
写文件 #include <fstream> #include <iostream> using namespace std; int main() { ofstream ou ...
- python装饰器使用
看了不少python关于装饰器文章,觉得还挺实用,发贴来mark一下(以前做笔记总会长时间不看就忘记,放这里应该不会忘了吧 - -) 先来看一个简单的知识点: def a(): print(" ...