在网上能查找到通过 Javascript 编写俄罗斯方块的资料,而且更牛的是只需要不到百行的代码就实现了,笔者非常佩服这些牛人。佩服之余,笔者也想尝试通过 Javascript 编写俄罗斯方块。在翻阅网上的资料和代码中,发现这些代码的可读性不高,因此不能让读者很好地去理解和学习代码。因此,笔者通过本文介绍自己如何通过面向对象的**实现该游戏。
-
地图:大小已经通过 css 样式确定(300px x 600px)。
-
堆积方块:创建 200 个小方块(30px x 30px 的div),填充(通过二维数组存放)到地图中。通过 css 样式区分堆积的方块(done)和可活动的区域(none)。
-
下落方块:方块有7个种类,都是通过4个小方块(30px x 30px 的div)构成。其横向活动区域为[0,9],纵向活动区域为[0,19],通过 css 样式设置颜色,如下表示:
[
{ x: 4, y: 0, className: "current_0" },
{ x: 3, y: 0, className: "current_0" },
{ x: 5, y: 0, className: "current_0" },
{ x: 6, y: 0, className: "current_0" }
]
坐标图表示如下:
-
移动方块:通过设置 position: absolute ,再动态设置 top 和 left 即可。
-
旋转方块:通过公式旋转,以上文的坐标案例演示:
this.current = [
{ x: 4, y: 0, className: "current_0" },
{ x: 3, y: 0, className: "current_0" },
{ x: 5, y: 0, className: "current_0" },
{ x: 6, y: 0, className: "current_0" }
]
for (var j = 1; j < this.current.length; j++) {
var newX = this.current[0].y + this.current[0].x - this.current[j].y;
var newY = this.current[0].y - this.current[0].x + this.current[j].x;
this.current[j].x = newX;
this.current[j].y = newY;
}
-
消行:通过切换样式实现,具体内容下文介绍。
-
动态效果:通过 setInterval 不断刷新页面(调用 map 对象的 _refreshMap 方法改变方块位置)。
DOM操作、面向对象、事件操作和间隔函数 setInterval
由于逻辑较为复杂,代码编写较长,因此只演示关键代码。
none 表示地图中的活动区域样式
current 开头的表示当前活动的方块样式
done 表示堆积的方块样式
游戏开始时,地图(300px x 600px)被 200 个小方块(30px x 30px 的div)填充,其 class 为 none。
当前方块在地图中下落时,设置 class 为 current 开头的样式。
当方块不能下落要堆积时,将其在地图当前区域的 div 样式由 none 改成 done。同时,将当前下落方块坐标设置为下一个方块坐标。
当方块消行时,遍历所有行,设置当前行的样式为上一行的样式,即修改坐标,让堆积的方块下落到消行的位置。
map.js 文件
var Map = function(square) {
// 边界
this.minX = this.minY = 0;
this.maxX = 9;
this.maxY = 19;
// 当前移动的方块
this.square = square;
// 用于记录完成下落动作的方块
this.mapDivs = [];
// 定时器id
this.timeId = null;
// 暂停标记
this.pauseFlag = false;
// 关闭标记
this.closeFlag = false;
// 阴影
this.shadow = null;
this.shadowFlag = false;
}
// 初始化
Map.prototype.init = function(domObjs) {
this.shadow = domObjs.shadow;
// 绘制地图,即填充小方块
for (var i = 0; i <= this.maxY; i++) {
var arr = [];
for (var s = 0; s <= this.maxX; s++) {
var mapDiv = document.createElement("div");
mapDiv.className = "none";
mapDiv.style.top = (i * this.square.size) + "px";
mapDiv.style.left = (s * this.square.size) + "px";
domObjs.map.appendChild(mapDiv);
arr.push(mapDiv);
}
this.mapDivs.push(arr);
}
// 当前下落方块
for (var j = 0; j < this.square.current.length; j++) {
var cdiv = document.createElement("div");
cdiv.className = this.square.current[j].className;
cdiv.style.left = this.square.current[j].x * this.square.size + "px";
cdiv.style.top = this.square.current[j].y * this.square.size + "px";
domObjs.map.appendChild(cdiv);
this.square.currentDivs.push(cdiv);
}
if (this.shadowFlag) {
this._showShadow();
}
// 下一个方块
for (var k = 0; k < this.square.next.length; k++) {
var ndiv = document.createElement("div");
ndiv.className = this.square.next[k].className;
ndiv.style.left = (this.square.next[k].x - 2) * this.square.size + "px";
ndiv.style.top = this.square.next[k].y * this.square.size + "px";
domObjs.next.appendChild(ndiv);
this.square.nextDivs.push(ndiv);
}
var that = this;
// 启动定时器
this.timeId = setInterval(function() {
if (!that.pauseFlag) {
that.square.moveDown(that);
that._refreshMap();
}
}, 300);
// 添加键盘监听器
this.addEventListener();
}
// 刷新地图
Map.prototype._refreshMap = function() {
var squareDivs = this.square.currentDivs;
for (var j = 0; j < squareDivs.length; j++) {
squareDivs[j].className = this.square.current[j].className;
squareDivs[j].style.top = this.square.current[j].y * this.square.size + "px";
squareDivs[j].style.left = this.square.current[j].x * this.square.size + "px";
}
// 阴影
this._showShadow();
var nextDivs = this.square.nextDivs;
for (var k = 0; k < nextDivs.length; k++) {
nextDivs[k].className = this.square.next[k].className;
nextDivs[k].style.left = (this.square.next[k].x - 2) * this.square.size + "px";
nextDivs[k].style.top = this.square.next[k].y * this.square.size + "px";
}
}
square.js 文件
// 方块由4个小方块组成
var Square = function(info) {
this.info = info;
// 小方块大小
this.size = 30;
// 当前下落方块div
this.currentDivs = [];
// 下一个方块div
this.nextDivs = [];
// 当前方块坐标对象
this.current = null;
// 下一个方块坐标对象
this.next = null;
this._init();
}
// 初始化
Square.prototype._init = function() {
if (this.next == null) {
this.current = this._getSquareType();
} else {
this.current = this.next;
}
this.next = this._getSquareType();
}
// 随机获取方块,7种方块类型
Square.prototype._getSquareType = function() {
// 坐标顺序决定旋转点
var data = [
[
{ x: 4, y: 0, className: "current_0" },
{ x: 3, y: 0, className: "current_0" },
{ x: 5, y: 0, className: "current_0" },
{ x: 6, y: 0, className: "current_0" }
],
[
{ x: 4, y: 0, className: "current_1" },
{ x: 3, y: 0, className: "current_1" },
{ x: 4, y: 1, className: "current_1" },
{ x: 5, y: 0, className: "current_1" }
],
[
{ x: 4, y: 0, className: "current_2" },
{ x: 3, y: 0, className: "current_2" },
{ x: 3, y: 1, className: "current_2" },
{ x: 5, y: 0, className: "current_2" }
],
[
{ x: 4, y: 0, className: "current_3" },
{ x: 3, y: 1, className: "current_3" },
{ x: 4, y: 1, className: "current_3" },
{ x: 5, y: 0, className: "current_3" }
],
[
{ x: 5, y: 0, className: "current_4" },
{ x: 4, y: 0, className: "current_4" },
{ x: 4, y: 1, className: "current_4" },
{ x: 5, y: 1, className: "current_4" }
],
[
{ x: 4, y: 0, className: "current_5" },
{ x: 3, y: 0, className: "current_5" },
{ x: 5, y: 0, className: "current_5" },
{ x: 5, y: 1, className: "current_5" }
],
[
{ x: 4, y: 0, className: "current_6" },
{ x: 3, y: 0, className: "current_6" },
{ x: 4, y: 1, className: "current_6" },
{ x: 5, y: 1, className: "current_6" }
]
];
return data[Math.floor(Math.random() * data.length)];
}
square.js 文件
// 移动方块
Square.prototype._move = function(moveX, moveY, map) {
for (var i = 0; i < this.current.length; i++) {
var newX = this.current[i].x + moveX;
var newY = this.current[i].y + moveY;
if (this._isOverZone(newX, newY, map)) {
return false;
}
}
for (var j = 0; j < this.current.length; j++) {
this.current[j].x += moveX;
this.current[j].y += moveY;
}
return true;
}
// 向左移动
Square.prototype.moveLeft = function(map) {
this._move(-1, 0, map);
}
// 向右移动
Square.prototype.moveRight = function(map) {
this._move(1, 0, map);
}
// 坠落
Square.prototype.fastDown = function(map) {
while (this._move(0, 1, map));
}
square.js 文件
// 旋转
Square.prototype.round = function(map) {
// 田字方块不用旋转
if (this.current[0].className == "current_4") {
return;
}
for (var i = 1; i < this.current.length; i++) {
var newX = this.current[0].y + this.current[0].x - this.current[i].y;
this._modify(newX, map);
}
for (var j = 1; j < this.current.length; j++) {
var newX = this.current[0].y + this.current[0].x - this.current[j].y;
var newY = this.current[0].y - this.current[0].x + this.current[j].x;
this.current[j].x = newX;
this.current[j].y = newY;
}
}
square.js 文件
// 向下移动
Square.prototype.moveDown = function(map) {
if (this._move(0, 1, map)) {
return false;
}
// 堆积
for (var i = 0; i < this.current.length; i++) {
map.mapDivs[this.current[i].y][this.current[i].x].className = "done";
}
// 消行
for (var j = 0; j <= map.maxY; j++) {
if (this._isCanRemoveLine(j, map)) {
this._removeLine(j, map);
// 加分
this.info.plusScore(10);
}
}
// 重新初始化
this._init();
}
// 消行
Square.prototype._removeLine = function(row, map) {
for (var col = 0; col <= map.maxX; col++) {
for (var y = row; y > 0; y--) {
// 当前行样式 = 上一行样式
map.mapDivs[y][col].className = map.mapDivs[y - 1][col].className;
}
map.mapDivs[0][col].className = "none";
}
}
// 是否可以消行
Square.prototype._isCanRemoveLine = function(row, map) {
var flag = [];
for (var col = 0; col <= map.maxX; col++) {
flag.push(map.mapDivs[row][col].className);
}
return !(flag.join(",").indexOf("none") > -1);
}
game.js 文件
var Game = function() {
this.info = null;
this.square = null;
this.map = null;
}
// 开始游戏
Game.prototype.start = function() {
// 初始化数据
this.info = new Info();
this.square = new Square(this.info);
this.map = new Map(this.square);
this.map.init({
"map":document.getElementById("map"),
"shadow":document.getElementById("shadow"),
"next":document.getElementById("next")
});
// 监听游戏状态
var that = this;
var timeId = setInterval(function() {
if (that.map.closeFlag) {
that.map.close();
clearInterval(that.map.timeId);
clearInterval(that.info.timeId);
var startBtn = document.getElementById("startBtn");
startBtn.removeAttribute("disabled");
startBtn.innerHTML = "重新开始";
document.getElementById("pauseBtn").setAttribute("disabled",true);
clearInterval(timeId);
}
},10);
}
window.onload = function() {
var game = new Game();
var startBtn = document.getElementById("startBtn");
var pauseBtn = document.getElementById("pauseBtn");
startBtn.addEventListener("click", function() {
if (this.innerHTML == "开始游戏") {
this.setAttribute("disabled",true);
pauseBtn.style.display = "inline-block";
pauseBtn.removeAttribute("disabled");
game.start();
} else {
this.setAttribute("disabled",true);
pauseBtn.style.display = "inline-block";
pauseBtn.removeAttribute("disabled");
game.restart();
}
});
pauseBtn.addEventListener("click",function() {
if (this.innerHTML == "暂停游戏") {
this.innerHTML = "恢复游戏";
game.pause();
} else {
this.innerHTML = "暂停游戏";
game.recover();
}
});
}