自己写了个H5版本的俄罗斯方块
在实习公司做完项目后,实在无聊。就用H5写了几个游戏出来玩一下。从简单的做起,就搞了个经典的俄罗斯方块游戏。
先上效果:
上面的数字是得分,游戏没有考虑兼容性,只在chrome上测试过,不过大部分现代浏览器还是可以玩的。
-------------------------------我是分割线--------------------------------------------
忽然发现这篇草稿放了好久,赶紧补上。。。哈哈
先上HTML代码
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>俄罗斯方块</title>
<style type="text/css">
body {
padding: 0;
margin-top: 40px;
text-align: center;
}
.tetris {
border: 4px solid black;
}
</style>
</head>
<body>
<div id="score"></div>
<canvas id="tetris" class="tetris" width="240" height="420"></canvas>
<script type="text/javascript" src="js/tetris2.js"></script>
</body>
</html>
代码就不解释了哈,下面着重说一下js的实现方式(貌似有点挫)。
首先是加载图片的函数:
function dlImg(img) { // 返回一个img对象
var oimg = new Image();
oimg.src = 'images/' + img;
return oimg;
} /**
* @param img img对象数组
* @param sw 屏幕适应的一个比值
* @param fun 程序入口函数
*/
function loadAllImg(img, sw, fun) {
var l = img.length,
i,h = 0;
for(i = 0; i < l; i ++){
oimgarr[img[i]] = dlImg(img[i]);
oimgarr[img[i]].onload = function () { // 加载是异步的
this.width = this.width * sw;
this.height = this.height * sw;
h ++;
h >= l && fun(); // 所有图片加载成功后调用fun函数
}
}
}
oimgarr是一个全局变量,暂存着游戏用到的img对象。
然后就是主函数的代码了,包含俄罗斯方块主要的逻辑功能。
主函数内有2个对象,Block对象是形状对象,参数type是形状的类型(上、l、L、田、转形状),Blocks是指整个俄罗斯盘(不知道用什么词形容。。。)的对象
下面是Block对象的实现:
function Block(type) {
this.type = type;
this.i = -1;
this.j = 6;
this.speed = 100;
this.defer = 0;
switch (this.type) {
case 1: // l字
this.outline = [{i: this.i, j: this.j},
{i: this.i - 1, j: this.j},
{i: this.i - 2, j: this.j},
{i: this.i - 3, j: this.j}];
break;
case 2: // 上字
this.outline = [{i: this.i, j: this.j - 1},
{i: this.i - 1, j: this.j},
{i: this.i, j: this.j},
{i: this.i, j: this.j + 1}];
break;
case 3: // L字
this.outline = [{i: this.i - 2, j: this.j - 1},
{i: this.i - 1, j: this.j - 1},
{i: this.i, j: this.j - 1},
{i: this.i, j: this.j}];
break;
case 4: // 田字
this.outline = [{i: this.i - 1, j: this.j - 1},
{i: this.i, j: this.j - 1},
{i: this.i, j: this.j},
{i: this.i - 1, j: this.j}];
break;
case 5: // 转字
this.outline = [{i: this.i - 1, j: this.j - 1},
{i: this.i, j: this.j - 1},
{i: this.i, j: this.j},
{i: this.i + 1, j: this.j}];
break;
}
this.dropBlock = function () { // 下落方块
var that = this;
if(this.defer == this.speed) {
this.outline.map(function (o) {
o.i = o.i + 1;
});
this.defer = 0;
}
else
this.defer ++;
};
this.setSpeed = function () {
this.speed = 2;
this.defer = 0;
};
this.isReady = function () {
return this.speed == this.defer;
}
}
下面是Blocks对象的实现方式:
var Blocks = {
nullimg: imga['null.png'],
cellimg: imga['cell.png'],
pause: false, // 游戏是否处于暂停中
matrix: new Array(21), // 矩阵,-1表示空,0表示正在移动,1表示已存在
block: new Block(1), // 默认第一个出现的方块类型为1
score: 0, // 分数累计
init: function () {
var that = this, code = null;
for(var i = 0; i < 21; i ++) { // 初始化矩阵数组
this.matrix[i] = new Array(12);
for (var j = 0; j < 12; j ++) {
this.matrix[i][j] = -1;
ctx.drawImage(this.nullimg, j * cell, i * cell, this.nullimg.width, this.nullimg.height);
}
}
document.onkeydown = function (e) { // 按键事件
code = e.keyCode || e.which;
switch (code){
case 37: // ←
that.setSite(-1);
break;
case 38: // ↑
that.rotateBlock();
break;
case 39: // →
that.setSite(1);
break;
case 40: // ↓ 长按加速下滑
if(that.block.speed == config.SPEED)
that.block.speedUp(); // 加速
break;
case 32: // 暂停
!that.pause ? that.suspend() : that.start();
break;
default :
return false;
}
};
document.onkeyup = function (e) {
if(e.keyCode == 40){ // 松开↓恢复速度
that.block.speed = config.SPEED;
}
}
},
start: function () { // 开始游戏
var that = this;
time = setInterval(function () {
console.time('all');
that.block.dropBlock(); // 下落方块
that.refreshMat(); // 刷新矩阵
that.reachBottom(); // 检测是否到达底部或者碰到已有方块
console.timeEnd('all');
}, config.TIME);
this.pause = false;
},
suspend: function () { // 暂停
this.pause = true;
clearInterval(time);
},
refreshMat: function () { // 执行一次矩阵刷新
var img = null, that = this;
that.block.outline.forEach(function (o) { // 将移动前的位置都置为-1
if(o.i > 0 && that.matrix[o.i - 1][o.j] != 1 )
that.matrix[o.i - 1][o.j] = -1;
});
that.block.outline.forEach(function (o) { // 刷新移动后的位置
if(o.i >= 0)
that.matrix[o.i][o.j] = 0;
});
this.matrix.forEach(function (l, i) { // 重绘矩阵
l.forEach(function (m, j) {
img = (m == -1 ? that.nullimg : that.cellimg);
ctx.drawImage(img, j * cell, i * cell, img.width, img.height);
});
});
},
rotatePoint: function (c, p) { // c点为旋转中心,p为旋转点,一次顺时针旋转90度。返回旋转后的坐标
return {j: p.i - c.i + c.j, i: -p.j + c.i + c.j};
},
rotateBlock: function () {
var that = this, i, o = null, ctr = that.block.outline[1], l = that.block.outline.length;
if (that.block.type != 4) { // 田字形无法旋转
for (i = 0; i < l; i++) {
o = that.rotatePoint(ctr, that.block.outline[i]);
if (o.j < 0 || o.j > 11 || o.i > 20) { // 旋转时不可以碰到边界
break;
}
else if (o.i > 0 && o.j >= 0 && o.j <= 20 && Blocks.matrix[o.i][o.j] == 1) { // 旋转时不可以已有方块的点
break;
}
}
if (i == 4) {
that.block.outline.forEach(function (o, i) {
if (o.i >= 0)
that.matrix[o.i][o.j] = -1; // 清空变化前的位置
that.block.outline[i] = that.rotatePoint(ctr, o);
});
}
}
},
setSite: function (dir) { // 设置左右移动后的位置
var i, o, l = this.block.outline.length;
for(i = 0; i < l; i ++){
o = this.block.outline[i];
// 是否碰到已存在的方块,是否碰到左右边界
if(o.i >= 0 && ((Blocks.matrix[o.i][o.j + dir] == 1) || (o.j + dir == -1 || o.j + dir == 12))){
break; // 一旦发生碰撞,就退出循环,并不执行移动操作
}
}
if(i == l) { // 当count=l时,表明移动操作没有发生碰撞
this.block.outline.forEach(function (o) {
if (o.i >= 0) {
Blocks.matrix[o.i][o.j] = -1; // 将当前位置置为-1
o.j = (o.j + dir == -1 || o.j + dir == 12) ? o.j : o.j + dir; // 是否允许移动,允许则将o.j+dir的值赋予o.j
Blocks.matrix[o.i][o.j] = 0; // 刷新最新值
}
else { // 小于0时(在矩阵之外),也需进行左右移动
o.j = (o.j + dir == -1 || o.j + dir == 12) ? o.j : o.j + dir;
}
});
}
},
reachBottom: function () {
var that = this, i, j, o, l = that.block.outline.length;
if(that.block.isReady()) { // 当前方块下落帧结束时,然后进行检测是否到达了底部
for (j = 0; j < l; j ++) {
o = that.block.outline[j];
if (o.i >= 0 && (o.i == 20 || that.matrix[o.i + 1][o.j] == 1)) { // 向下移动时发生碰撞
break; // 方块到达底部或落在其他方块上,方块停止下落,产生新的方块
}
}
if (j < l) { // 当方块落在底部或其他方块时,进行检测
for(i = 0; i < l; i ++) {
o = that.block.outline[i];
if(o.i >= 0){
that.matrix[o.i][o.j] = 1; // 方块停止后,修改矩阵数据
}
else {
that.gameOver(); // 游戏结束
return;
}
}
that.ruinMat(); // 检测是否需要爆破行,如果有则执行爆破操作
that.block = new Block(parseInt(Math.random() * 5) + 1);
}
}
},
detectMat: function () { // 检测矩阵,判断是否有连续一行,返回一个数组
var count = 0, s,
detecta = []; // 需要爆破的行号
this.matrix.forEach(function (l, i) {
for(s = 0; s < l.length; s ++){
if(l[s] == 1) count ++; else break;
}
count == 12 && detecta.push(i);
count = 0;
});
return detecta.length == 0 ? false : detecta;
},
ruinMat: function () { // 爆破连续的一行
var dmat = this.detectMat(); // 返回整行都有方块的行号集合
if(dmat){
this.score = this.score + (dmat.length == 1 ? 100 : dmat.length == 2 ? 250 : dmat.length == 3 ? 450 : 700);
score.innerHTML = this.score.toString();
dmat.forEach(function (d) {
Blocks.matrix.splice(d, 1); // 删掉整行都有方块的行
Blocks.matrix.unshift([-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1]); // 弥补被删的行
});
}
dmat = null;
},
gameOver: function () {
clearInterval(time);
alert('你挂了');
}
};
Block这个对象可以按照下图的坐标来理解:
可以看到,棋盘左上角是坐标(0,0),每个小方格代表一个坐标点,坐标点的值对应状态值,即-1表示位置空闲,0表示位置正在刷新,1表示位置被占用。
当然这里还有一个模块没有实现,这个模块就是提示下一个方块的形状,这个有兴趣的同学可以加下哈。
附上github地址:https://github.com/zquancai/tetris
PS。。。这篇草稿在草稿箱里躺了快半年,懒到无法自拔(所以描述啥估计有点乱乱的,不过这也没啥难度,纯属自己玩玩而已了)
自己写了个H5版本的俄罗斯方块的更多相关文章
- JS/Jquery版本的俄罗斯方块(附源码分析)
转载于http://blog.csdn.net/unionline/article/details/63250597 且后续更新于此 1.前言 写这个jQuery版本的小游戏的缘由在于我想通过从零到有 ...
- unity, unity默认的Arial字体在编译出的h5版本中不显示
unity默认的Arial字体在编译出的h5版本中不显示.改用自己的字体可显示.
- 微阅读,不依赖playground,打包成H5版本--案例学习
微阅读,不依赖playground,打包成H5版本 https://github.com/vczero/weex-yy-h5
- 如何在嵌套的app中运用vue去写单页面H5
本文主要介绍移动端.为了避免移动端兼容出现各种奇奇怪怪的bug,所以秉承着能不用复杂的语法就不用,尽量用最基础的语法. 可用惯了各种ES6语法的童鞋们,写原生真是头疼,再加上各种领导催工期,肯定是内心 ...
- 基于TensorFlow的手写中文识别(版本一)
具体效果实现: 第一次由于设备问题所以只训练了是一些个简单的字: 第二选了23个字训练了3000在字迹清晰下能够识别: 类似于默,鼠,鼓,这类文字也能识别,由于训练数据的问题,在测试的时候应尽量写在正 ...
- 我用数据结构花了一夜给女朋友写了个h5走迷宫小游戏
目录 起因 分析 画线(棋盘) 画迷宫 方块移动 结语 @(文章目录) 先看效果图(在线电脑尝试地址http://biggsai.com/maze.html): 起因 又到深夜了,我按照以往在公众号写 ...
- forEach和map的区别,简单写了IE低版本的原形封装
今天有点'不务正业',旧的没有写完又开新的,没办法 -0- 今天遇到这个特感兴趣嘛入正题了 forEach 和 map 的区别 参考:http://blog.csdn.net/boysky0015/a ...
- HTML+JS版本的俄罗斯方块
<!doctype html><html><head></head><body> <div id="box" st ...
- 解决ecshop3.6 H5版本公告页面为空的修改办法
ecshop3.6公告页面打开如下,页面完全无效果,如下图. 经过简单美化后,有返回按钮,页面加以美化.如下图. 是不是要好看多了.简单修改几步即可. 修改文件 \appserver\resource ...
随机推荐
- Support for AMD usage of jwplayer (require js)
使用require js 模块化代码时,其中播放器用的是jwplayer7.x 然后载入jwplayer.js后总是报license无效(license已经加入),最后在jwplayer官网论坛里找到 ...
- MyCat 学习笔记 第十三篇.数据分片 之 通过HINT执行存储过程
1 环境说明 VM 模拟3台MYSQL 5.6 服务器 VM1 192.168.31.187:3307 VM2 192.168.31.212:3307 VM3 192.168.31.150: 330 ...
- POI教程之第二讲:创建一个时间格式的单元格,处理不同内容格式的单元格,遍历工作簿的行和列并获取单元格内容,文本提取
第二讲 1.创建一个时间格式的单元格 Workbook wb=new HSSFWorkbook(); // 定义一个新的工作簿 Sheet sheet=wb.createSheet("第一个 ...
- 在matlab2015b中配置vlfeat-0.9.18
参考链接: 1.http://cnyubin.com/?p=85 2.http://www.cnblogs.com/woshitianma/p/3872939.html ...
- IIS7上设置MIME让其支持android和Iphone的更新下载
Android APP的MIME: 文件扩展名:.apk MIME类型:application/vnd.android.package-archive iPhone APP的MIME: 文件扩展名:. ...
- [转]asp.net的ajax以及json
本文转自:http://www.cnblogs.com/ensleep/p/3319756.html 来现在这家公司以前,从未接触过webform,以前在学校做的项目是php,java以及asp.ne ...
- gnuplot Python API
源文件 #!/usr/bin/env python from os import popen class gnuplot_leon: # Author : Leon Email: yangli0534 ...
- 边工作边刷题:70天一遍leetcode: day 88-5
coins in a line I/II/III: check above 1. recursion的返回和dp[left][right]表示什么?假设game是[left,right],那么play ...
- 酷派5890 ROM教程
一.前言 ROM出处:http://www.in189.com/thread-975035-1-1.html 名称:酷派5890官方056精简优化包V2.2,G卡可用+已ROOT+可用大运存 更新日期 ...
- Android优化——UI优化(二) 使用include标签复用布局
使用include标签复用布局 - 1.include标签的作用 假如说我下图的这个布局在很多界面都用到了,我该怎么办?每个页面都写一遍的话,代码太冗余,并且维护难度加大. <LinearLay ...