这是本人在B站学习上跟着老师做的一个小游戏—俄罗斯方块,里面的内容跟老师的视频几乎一样。
视频来源
https://www.bilibili.com/video/BV1Rv41147GP?p=10
采用的技术主要是JavaScript。
全部代码,本人也放在了gitee上了,这是国内的GitHub,速度比较快。
仓库地址
https://gitee.com/jjm1/tetris
1.1 初始化布局
- 代码
//采用这样的形式是为了防止全局污染
(function () {
window.Game = function () {
//设置行和列
this.row = 20;
this.col = 12;
//初始化
this.init();
};
Game.prototype.init = function () {
var $table = $("<table></table>");
for (let i = 0; i < this.row; i++) {
var $tr = $("<tr></tr>");
for (let j = 0; j < this.col; j++) {
var $td = $("<td></td>");
$td.appendTo($tr);
}
$tr.appendTo($table);
}
$("body").append($table);
};
})();
1.2 认识方块
俄罗斯方块中有 7 种状态,每一种又可以旋转成不同的状态
- S 型
- Z 类
- J 类
- L 类
- O 类
- T 类
- I 类
每一种形态用 4*4 表格表示
1.3 方块的表示
使用二维数组去表示一个俄罗斯方块
比如:
[
[0,0,0,0],
[1,1,1,0],
[0,1,0,0],
[0,0,0,0]
]
我们将所有的俄罗斯方块都放在一个 JSON 中
方块渲染
window.Block = function () {
//罗列所有类型
var allType = ["S", "Z", "J", "L", "O", "I", "T"];
//得到一种类型
this.type = allType[parseInt(Math.random() * allType.length)];
//得到一种类型中的总数量
this.allDir = fangkuai[this.type].length;
//随机得到一种
this.dir = parseInt(Math.random() * this.allDir);
//得到随机方块
this.code = fangkuai[this.type][this.dir];
//初始行
this.row = 0;
//初始列
this.col = 4;
};
Block.prototype.render = function () {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (this.code[i][j] != 0) {
game.setColor(i + this.row, j + this.col, this.code[i][j]);
}
}
}
};
1.4 方块地图
(window.Map = function () {
this.codeMap = [
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
[9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9, 9],
];
}),
(Map.prototype.render = function (mapGame) {
for (let i = 0; i < mapGame.row; i++) {
for (let j = 0; j < mapGame.col; j++) {
if (this.codeMap[i][j] != 0) {
game.setColor(i, j, this.codeMap[i][j]);
}
}
}
});
1.5 方块运动
方块进行下落之前需要进行预判断,判断方块下落的下一个位置是否有方块存在,如果没有方块存在就直接 row++,否则停止下落
//预判断方法,判断当前方块与对应位置的方块是否有重合
Block.prototype.check = function (row, col) {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (this.code[i][j] != 0 && game.map.codeMap[row + i][col + j] != 0) {
return false;
}
}
}
return true;
};
//方块进行下落之前需要进行预判断,判断方块下落的下一个位置是否有方块存在,如果没有方块存在就直接row++,否则停止下落
Block.prototype.checkDown = function () {
//下落之前,先进行预判断
if (this.check(this.row + 1, this.col)) {
this.row++;
} else {
game.block = new Block();
}
};
1.6 渲染地图
//将下落的方块渲染到地图中
Block.prototype.renderMap = function () {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (this.code[i][j] != 0) {
game.map.codeMap[this.row + i][this.col + j] = this.code[i][j];
}
}
}
};
1.7 方块左右移动
//事件监听
Game.prototype.bindEvent = function () {
var self = this;
$(document).keydown(function (event) {
if (event.keyCode == 37) {
self.block.checkLeft();
} else if (event.keyCode == 39) {
self.block.checkRight();
} else if (event.keyCode == 32) {
//按空格一键到底
self.block.checkBlockEnd();
}
});
};
//判断是否可用左移
Block.prototype.checkLeft = function () {
if (this.check(this.row, this.col - 1)) {
this.col--;
}
};
//判断是否可用右移
Block.prototype.checkRight = function () {
if (this.check(this.row, this.col + 1)) {
this.col++;
}
};
1.8 方块的旋转
Block.prototype.blockRot = function () {
this.dir++;
if (this.dir > this.allDir - 1) {
this.dir = 0;
}
//改变方向之后渲染心得方向
this.code = fangkuai[this.type][this.dir];
};
问题:
方块的旋转不会顾及左右的方块是否已存在
解决:
我们可以对方块进行一次备份,判断不能变化之后,再将变化后的方块变回来
Block.prototype.blockRot = function () {
//保留旧的状态
var oldDir = this.dir;
this.dir++;
if (this.dir > this.allDir - 1) {
this.dir = 0;
}
//改变方向之后渲染新的方向
this.code = fangkuai[this.type][this.dir];
//判断当前改变后的方块是否有能力继续往下走
if (!this.check(this.row, this.col)) {
this.dir = oldDir;
this.code = fangkuai[this.type][this.dir];
}
};
1.9 判断是否可以移动
//判断是否可以消行
Map.prototype.checkRemove = function () {
for (let i = 0; i < 20; i++) {
if (this.codeMap[i].indexOf(0) == -1) {
this.codeMap.splice(i, 1);
this.codeMap.unshift([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
}
}
};
1.10 判断游戏结束
//判断游戏结束
Block.prototype.checkOver = function () {
for (let i = 0; i < game.col; i++) {
if (game.map.codeMap[0][i] != 0) {
clearInterval(game.timer);
alert("游戏结束了!!!");
}
}
};
1.11 设置预览框
设置预览框的方法
Game.prototype.setNextColor = function () {
for (let i = 0; i < 4; i++) {
for (let j = 0; j < 4; j++) {
if (this.nextBlock.code[i][j] != 0) {
$(".tab2")
.find("tr")
.eq(i)
.children("td")
.eq(j)
.addClass("c" + this.nextBlock.code[i][j]);
}
}
}
};
初始化预览框
Game.prototype.init = function () {
var $table = $("<table></table>");
var $table2 = $("<table></table>");
$table.addClass("tab1");
$table2.addClass("tab2");
for (let i = 0; i < this.row; i++) {
var $tr = $("<tr></tr>");
for (let j = 0; j < this.col; j++) {
var $td = $("<td></td>");
$td.appendTo($tr);
}
$tr.appendTo($table);
}
for (let i = 0; i < 4; i++) {
var $tr = $("<tr></tr>");
for (let j = 0; j < 4; j++) {
var $td = $("<td></td>");
$td.appendTo($tr);
}
$tr.appendTo($table2);
}
$("div").append($table);
$("div").append($table2);
};
Game.prototype.setColor = function (row, col, num) {
$(".tab1")
.find("tr")
.eq(row)
.children("td")
.eq(col)
.addClass("c" + num);
};
方块到底时
//渲染预览框方块
game.block = game.nextBlock;
//渲染新的方块
game.nextBlock = new Block();
1.12 设置帧编号和分数
//分数
this.score = 0;
//速度
this.during = 30;
Game.prototype.start = function () {
var self = this;
//帧编号
this.f = 0;
this.timer = setInterval(function () {
self.f++;
document.getElementById("f").innerHTML = "帧编号:" + self.f;
//清屏
self.clear();
self.block.render();
//渲染预览方块
self.setNextColor();
//渲染地图
self.map.render(self);
//方块下落
self.f % self.during == 0 && self.block.checkDown();
}, 30);
};
//判断是否可以消行
Map.prototype.checkRemove = function () {
for (let i = 0; i < 20; i++) {
if (this.codeMap[i].indexOf(0) == -1) {
this.codeMap.splice(i, 1);
this.codeMap.unshift([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]);
if (game.during <= 30 && game.during >= 20) {
game.score += 10;
} else if (game.during < 20 && game.during >= 10) {
game.score += 20;
} else {
game.score += 30;
}
document.getElementById("score").innerHTML = "分数:" + game.score;
if (game.score % 100 == 0) {
game.during -= 5;
if (game.during < 0) {
game.during = 1;
}
}
}
}
};