[JS]使用JavaScript实现简易俄罗斯方块
[JS]使用JavaScript实现简易俄罗斯方块
首先,大家可以点击此处来预览一下游戏效果,随后将会以此为模板讲解如何使用JavaScript实现这样一个简易的俄罗斯方块项目(以下简称"该项目").
文件构成
┬ js ┬ tetris.js
│ └ tetrominoes.js
└ tetris.html
tetris.html是该项目的页面文件,提供一个简单的用户交互界面,由文本和画布构成.文本负责作者信息,操作说明等普通文本;画布则是为了绘制游戏界面.tetris.js是该项目的核心文件,里面包含了所有游戏逻辑,也是本篇文章会重点讲解的文件.tetrominoes.js是该项目存储俄罗斯方块组合的文件,里面存有俄罗斯方块所有7种组合以及他们的变体,便于tetris.js调用.
项目结构
用画图工具画的...比较粗糙.

代码逻辑
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个字段,每个字段有一个组合和对应颜色值.
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();
}
}
moveLeft及moveRight方法
让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();
代码下载
完整代码可以点击此处下载.
[JS]使用JavaScript实现简易俄罗斯方块的更多相关文章
- JavaScript 实现简易版贪吃蛇(Day_13)
时光永远在变迁,你始终要丢下过去. 使用语言 JavaScript 概述 运用JavaScript 实现简易版<贪吃蛇>. Html 页面 1 <!DOCTYPE htm ...
- [JS]jQuery,javascript获得网页的高度和宽度
[JS]jQuery,javascript获得网页的高度和宽度网页可见区域宽: document.body.clientWidth 网页可见区域高: document.body.clientHeigh ...
- 了不起的Node.js: 将JavaScript进行到底(Web开发首选,实时,跨多服务器,高并发)
了不起的Node.js: 将JavaScript进行到底(Web开发首选,实时,跨多服务器,高并发) Guillermo Rauch 编 赵静 译 ISBN 978-7-121-21769-2 2 ...
- Rainyday.js – 使用 JavaScript 实现雨滴效果
Rainyday.js 背后的想法是创建一个 JavaScript 库,利用 HTML5 Canvas 渲染一个雨滴落在玻璃表面的动画.Rainyday.js 有功能可扩展的 API,例如碰撞检测和易 ...
- JS Nice – JavaScript 代码美化和格式化工具
JS Nice 是一款让经过混淆处理的 JavaScript 代码可读更好的工具.它使用一种新型的用于 JavaScript 代码美化的去混淆和去压缩引擎.JSNice 采用先进的机器学习和程序分析技 ...
- 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 ...
- three.js是JavaScript编写的WebGL第 三方库
three.js是JavaScript编写的WebGL第 三方库.提供了非常多的3D显示功能.Three.js 是一款运行在浏览器中的 3D 引擎,你可以用它创建各种三维场景,包括了摄影机.光影.材质 ...
- js基础--javaScript数据类型你都弄明白了吗?绝对干货
欢迎访问我的个人博客:http://www.xiaolongwu.cn 数据类型的分类 JavaScript的数据类型分为两大类,基本数据类型和复杂数据类型. 基本数据类型:Null.Undefine ...
- node.js和JavaScript的关系
node.js是一个基于 Chrome V8 引擎的 JavaScript 运行时环境. 一.类比JavaScript和java JavaScript java V8 JVM node.js JRE ...
随机推荐
- 牛客-DongDong数颜色 及其相似题
大佬博客 ps:在牛客上做到这题不会,学会之后补了两道相关题.顺便记录一下. 牛客-DongDong数颜色 sol:dfs序+莫队,先把树上的点标上dfs序,因为子树的dfs序是连续的,所以子树可以表 ...
- CentOS 6.5 搭建DNS服务器
1.安装bind yum install -y bind 2.修改named.conf主配置文件 vim /etc/named.conf 图中圈中的地方改为any 3.配置正向.反向解析 vim /e ...
- 使用 Git 删除本地仓库和远端仓库文件
使用 git bash 来删除 一.将文件(夹)添加到暂存区 这里假设本地和远端都有一个 test.txt 文件先在本地删除,通过 ·git status 查看通过git add test.txt 添 ...
- 在python使用selenium获取动态网页信息并用BeautifulSoup进行解析--动态网页爬虫
爬虫抓取数据时有些数据是动态数据,例如是用js动态加载的,使用普通的urllib2 抓取数据是找不到相关数据的,这是爬虫初学者在使用的过程中,最容易发生的情况,明明在浏览器里有相应的信息,但是在pyt ...
- ES6中 const 关键字
const声明一个只读的常量.一旦声明,常量的值就不能改变. 定义后可以使用但不能修改 但是,const 定义的对象可能与我们想象的不太一样 定义了对象b ,我们可以在b上添加修改属性,再看一个列子 ...
- Mybatis分页插件的使用流程
如果你也在用Mybatis,建议尝试该分页插件,这一定是最方便使用的分页插件.该插件支持任何复杂的单表.多表分页. 1.引入PageHelper的jar包 在pom.xml中添加如下依赖: 12345 ...
- hexo-next-travis-ci 构建自动化部署博客
构建效果如上面视频所示,如果浏览器不支持请戳一下链接: 自动化部署构建效果 .只要将编辑的 .md 文件推送到 github 上,博客网站就可以更新这篇文章. 其实差不多半年前也构建过一次,由于安装 ...
- 烧钱时代终结!O2O还能玩啥花样?
最终的最终,饱受亏损.烧钱玩补贴等争议的美团还是追随滴滴/快的.赶集/58的步伐,与大众点评愉快的在一起了!美团和大众点评作为O2O行业的领军企业,都因为不堪忍受持续地投入却不见回报的模式而不得不放低 ...
- 【转载】ArcGIS中的WKID
原出处:http://www.cnblogs.com/liweis/p/5951032.html 提到坐标系统,大家多少能明白一些,但在运用时,有些朋友搞得不是非常清楚,以后专门来总结.在实地生产项目 ...
- Dubbo源码解析之SPI(一):扩展类的加载过程
Dubbo是一款开源的.高性能且轻量级的Java RPC框架,它提供了三大核心能力:面向接口的远程方法调用.智能容错和负载均衡,以及服务自动注册和发现. Dubbo最早是阿里公司内部的RPC框架,于 ...