前言
用es6来写俄罗斯方块这类小游戏也已经是老掉牙的东西了;
但是对于我们这种初学者来说还是很有必要加以练习的;
究其原因,主要还是心血来潮的写了一个,也没什么其他的更深层次因素;
...
本项目全部用es6以及面向对象编写;
CELL模型
- 保存每个格子的坐标和class;
- 绘制div
- 消除div
这样面向对象的好处就是之后写起来的会非常方便,不用考虑太多其他的方法
方块模型
constructor(map, fixed, arr, color) {
this.blockCells = [];
this.color = color;
this.arr = arr; //方块的形态
this.fixed = fixed; //位置是否随机
this.x = 1; //记录移动位置
this.y = 1; //记录移动位置
this.map = map;
this.pos = null; //所有方块cell的位置[]
this.rotateFlag = true; //是否可以旋转
this.moveFlag = true; //是否可以移动
}
初始化
- 随机生成方块形状
- 随机生成方块坐标
- 随机生成方块颜色
- 渲染出方块
方块形状
用this.arr
来保存当前方块的形状;
采用二维数组来保存各种形状
[
[1, 0],
[1, 0],
[1, 1]
]
这一个代表一个2*3的L型方块,1
代表了需要渲染的位置,0
代表了不需要渲染的位置;
绘制方块
- 首先分析形状二维数组中对应的
1
和0
- 根据
1
和0
绘制
获得1的坐标
getXY()
对形状进行双层循环,得到1的位置角标 [0,0], [1,0], [2,0], [2,1]
,然后再根据位置角标进行渲染
旋转变化
其实可以用数组将全部的形状以及对应的旋转形状都写出,然后在数组里调用就可以了,但是年少如我,还是写了一个旋转公式 😈
顺时针旋转的话,假设
[
[1, 0],
[1, 0],
[1, 1]
]
变换为
[
[1, 1, 1],
[1, 0, 0]
]
可以发现其所有1
和0
的坐标都只是x和y互换(同一1
或0
不存在对应呼唤关系);
所以其实就是一个近似矩阵转置的问题,但还是有区别
let rotateArr = [];
for (let i = 0, len = arr.length; i < len; i++) {
let inArr = [];
for (let j = arr.length - 1; j >= 0; j--) {
inArr.push(arr[j][i]);
}
rotateArr.push(inArr);
}
return rotateArr;
旋转后重绘
这边其实没必要旋转后没有重绘dom
,以此节省dom
操作;直接让位置不同的cell在css
上进行位移;
在getXY()
中添加判定,获得1
的坐标后,是初始化绘制方块,还是旋转完移动方块;
每一步移动完后,都需要在block里记录移动后的位置,否则旋转变化结束会回到起始点
判断边界
这里是根据获得的下一步所占用的坐标位置来检测,在移动和旋转时都需要用到
-
移动
- 获得移动方向上下一步的位置
- 判断边界
-
旋转
- 获得旋转后形成的新形状所占的位置
- 判断边界
判断逻辑
-
有方块堆积
- 使用一个数组来存储所有一堆积的方格位置,看是否和下一步位置重合
-
无方块堆积
- 判定下一步位置是否超出了
map
的边界即可
- 判定下一步位置是否超出了
移动
因为之前写了cell
类,所以这里的移动其实很简单,如果边界判断通过,只需要把方块里的每个方格对应移动到获得的下一步位置
-
block对象
的坐标移动 -
dom
操作对应block对象
移动
堆积
这里就是在方块向下到达边界的时候对堆积方块进行记录,并且改变起class
// 给map记录deadblock位置信息
this.blockCells.forEach(el => {
for (let i = 0, len = map.deadBlocks.length; i < len; i++) {
if (el.x === map.deadBlocks[i][0] && el.y === map.deadBlocks[i][1]) break;
map.deadBlocks.push(el);
break;
}
if (!map.deadBlocks.length) map.deadBlocks.push(el);
})
// 改变class
this.blockCells.forEach(el => {
el.cell.style.background = '#7f8c8d';
})
//消行
this.eliminate();
//控制游戏重启
game.stop();
game.start();
消行
对所有记录的堆积方块进行遍历,当同一行的方块数和地图大小一致时,在数组和dom
中删除对应的方块
for (let r = MAP_COL - 1; r > 0; r--) {
let idxs = []
let num = 0; //该行的cell个数
deadBlocks.forEach((el, i) => {
if (el.y === 0) {
game.over();
return;
}
if (el.y === r) {
num++;
idxs.push(i);
};
})
// 消行
if (num === MAP_LEN) {
idxs.forEach(i => {
deadBlocks[i].remove(this.map);
delete deadBlocks[i];
})
//下移
deadBlocks.forEach((el, i) => {
if (el.y < r) el.move(el.x, el.y + 1);
})
r++; //调整当前行数
//记分
game.addScore();
}
}
Game模型
给game添加一个timer
,用来实现方块的自动落下; 当停止游戏的时候就去除timer
;
- 启动
- 记分
-
暂停
handlePause() { this.pause = !this.pause; if (this.pause) { this.stop(); return; } this.timer = setInterval(() => { block.check('down') }, this.speed); }
- 游戏结束
小结
- 没有添加可调节游戏难度的变量
- 没有写直接下落的功能
传送门
😺 喜欢的朋友欢迎 star 🌟
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。