JavaScript 俄罗斯方块实现原理解析与代码说明
本文详细解析了基于 JavaScript 实现的俄罗斯方块游戏。文章介绍了代码采用静态类封装的全局配置方式,涵盖键盘事件监听、方块坐标与旋转点定义、界面初始化及渲染逻辑。重点讲解了移动、旋转、消行判定、计分升级机制以及游戏结束条件的判断流程,帮助开发者理解经典游戏在 Web 端的实现细节。
本文通过一个具体的实例,展示了如何使用 JavaScript 开发俄罗斯方块游戏,旨在为读者提供可参考的实现方案。
俄罗斯方块代码说明
/**
名称:Javascript 俄罗斯方块!
作者:Gloot
邮箱:glootz@gmail.com
QQ:345268267
网站:http://www.cnblogs.com/editor/
*/
OLSFK = {};
该俄罗斯方块的实现完全基于 JavaScript 脚本,整体结构简洁清晰,易于理解。
整个代码库采用静态类与静态变量成员进行构建,便于统一管理游戏状态。
脚本通过全局配置对象 OLSFK.Options = {...} 来统一定义游戏的核心参数。
代码中定义了方块的初始坐标位置以及各自的旋转中心点。
游戏从初始化俄罗斯方块界面开始,随后监听键盘事件,根据用户的左右移动、下落及旋转指令,重新渲染方块在界面上的位置。
系统会判断是否满足消行条件,并据此执行相应的加级逻辑、调整下落速度以及执行加分操作。
最后,通过判断当前级别是否超过预设的最大级别,来决定游戏是否结束。
代码说明讲解
OLSFK.Options = { //相关参数
width:12,//界面横向方块数
height:20,//界面纵向方块数
boxWidth : '16px',
curLevel:1,
speed : 1000, //setInterval,setTimeout
direct : { //可以设定是A S D W, 还是← ↓ →
Down: 40 , /*run speed*/
Left: 37,
Right: 39,
Rotate: 38
},
Move:true,//是否正在移动
Eventing:false,
Levels: {
1:1000,
2:900,
3:800,
4:700,
5:600,
6:500,
7:400,
8:300,
9:200,
10:100
},
curBlock:4, //当前移动的图形名称
nextBlock:0,
GampMap:new Object(),
Timer:null,
deline:0,
Score:0,
Deling:false,
Start:false,
lineNum:10, //删除几行了,加级
ScoreNum:40 //消一行加分
}
direct 字段用于定义使用键盘方位键来控制方块移动的方向映射。
开发者可以自由选择方向键的配置,例如使用字母键 A、S、D、W,或者右侧小数字键盘上对应的按键编码。
例如,上(旋转)、下、左、右方向键的按键编码分别为 38、40、37、39。
Levels 字段表示级别配置,本实现共分为 10 级,每个级别对应不同的下落速度,即定时执行的间隔时间。
curBlock 字段表示当前正在活动的方块。
nextBlock 字段表示即将下落的方块索引,并在界面右上角的预览框中显示。
GampMap 用于保存根据定义行列数形成的游戏网格中每个格子的数据信息。
OLSFK.Options.GampMap[x+'_'+y] = 0;
对象表格的 ID 格式为: id: "box_"+x+"_"+y;。
初始化数据为 ‘0',表示该格子未被占用;当有方块占据时,将其值设置为 ‘1'。
Timer 为定时执行器,利用 setTimeout 控制方块下落的频率;定时时间越短,方块下落速度越快。
Deling 字段用于标记是否正在执行消行操作,若为真,则暂时不显示并下落新方块。
lineNum 字段表示消除超过 10 行后,游戏等级提升一级。
ScoreNum 字段表示每消除一行所增加的分数。
OLSFK.ReItems = function (cur){ //key旋转点
switch (cur)
{
case 1:
OLSFK.Items[1] = {//长块 LongBlock
1:{x:4,y:0},
2:{x:5,y:0},
3:{x:6,y:0},
4:{x:7,y:0},
5:{x:5,y:0} //旋转点
};
break;
//....
}
}
该方法用于恢复方块的初始设置状态。
OLSFK.Next = { //key旋转点
//长块 LongBlock
1: {
1:{x:0,y:1},
2:{x:1,y:1},
3:{x:2,y:1},
4:{x:3,y:1}
},
//...
}
为了避免与游戏方块的现有设置冲突,下次随机方块的对象配置被独立出来。
OLSFK.Items = { //key旋转点
//长块 LongBlock
1: {
1:{x:4,y:0},
2:{x:5,y:0},
3:{x:6,y:0},
4:{x:7,y:0},
5:{x:5,y:0}
},
//方块Box
2: {
1:{x:4,y:0},
2:{x:5,y:0},
3:{x:4,y:1},
4:{x:5,y:1},
5:{x:0,y:0}
},
//凸块 TuBlock
3: {
1:{x:4,y:1},
2:{x:5,y:0},
3:{x:5,y:1},
4:{x:6,y:1},
5:{x:5,y:1}
},
//L块 LBlock
4: {
1:{x:5,y:0},
2:{x:5,y:1},
3:{x:5,y:2},
4:{x:6,y:2},
5:{x:5,y:2}
},
5: { //反向L块 FLBlock
1:{x:5,y:2},
2:{x:6,y:2},
3:{x:6,y:1},
4:{x:6,y:0},
5:{x:6,y:2}
},
//Z块 ZBlock
6: {
1:{x:4,y:0},
2:{x:5,y:0},
3:{x:5,y:1},
4:{x:6,y:1},
5:{x:5,y:0}
},
7: {//反向Z块 FZBlock
1:{x:4,y:1},
2:{x:5,y:1},
3:{x:5,y:0},
4:{x:6,y:0},
5:{x:5,y:1}
}
}
方块共分为:长条块、方块、凸块(T块)、L块、反L块、Z块、反Z块几种类型。
共有 7 种方块,以 1,2,3,4,5,6,7 索引键表示;每种方块由四个小块组成,每块都有各自的坐标,其中 1-4 表示组成该块的初始坐标位置,5 表示旋转点。
OLSFK.Init = function() { //初始化界面
//...
}
俄罗斯方块的界面初始化方法,将在 window.onload 事件中调用执行。
var w = OLSFK.Options.width;
var h = OLSFK.Options.height;
var total = w * h;
var x=0,y=0;
for (var i=0; i<total; i++)
{
OLSFK.Options.GampMap[x+'_'+y] = 0;
Lib.Tag('SPAN',{id:"box_"+x+"_"+y,name:"cbox",style:{
width:OLSFK.Options.boxWidth,
height:OLSFK.Options.boxWidth,
border:"2px outset #669",
background:"#ddd",
float:"left",
overflow:"hidden"
},innerHTML:" ",className:"cssbox"},back);
var end = i + 1;
x++;
if (end >= w && end % w == 0)
{
x=0;
y++;
Lib.Tag('DIV',{style:{
clear:"both"
}},back);
}
}
通过设置的 Options.width 和 Options.height 列数与行数,以及设置的小方格宽度,初始化了一个宽为 Options.width 列、高为 Options.height 的表格界面。
Lib.Tag 用于创建标签对象。
Lib.Tag = function(TAG,json,pnode) {
//...
}
TAG 为标签名,例如: div, span 等。
json 参数用于设置标签样式 style。
pnode 是该创建所在的父容器节点。
OLSFK.Init = function() {} 还创建主游戏区域旁边的下次随机方块预览区、当前级别显示、分数显示,以及操作“开始”、“暂停”按钮等界面元素。

游戏初始入口点
window.onload = function() {
if (window.isIE)
{
document.attachEvent("onkeydown",function(e) {
if (OLSFK.Options.Start)
{
var E = OLSFK.KeyCode();
OLSFK.EventFunc(E);
}
});
document.attachEvent("onkeyup",function(e) {
if (!OLSFK.Options.Move && OLSFK.Options.Start)
{
OLSFK.Options.Move = true;
OLSFK.Options.Eventing = false;
OLSFK.Options.Timer = setTimeout(function() {
OLSFK.play();
}, OLSFK.Options.Levels[OLSFK.Options.curLevel]);
}
});
} else {
document.addEventListener("keydown",function(e) {
if (OLSFK.Options.Start)
{
var E = OLSFK.KeyCode();
OLSFK.EventFunc(E);
}
},false);
document.addEventListener("keyup",function(e) {
if (!OLSFK.Options.Move && OLSFK.Options.Start)
{
OLSFK.Options.Move = true;
OLSFK.Options.Eventing = false;
OLSFK.Options.Timer = setTimeout(function() {
OLSFK.play();
}, OLSFK.Options.Levels[OLSFK.Options.curLevel]);
}
},false);
}
OLSFK.Init();
}
主要功能是监听键盘事件,根据键盘事件返回的按钮编码与 OLSFK.Options.direct 设置的方向键进行匹配,从而控制方块的移动和旋转。
keydown 事件用于操作下落方块的移动方向、旋转等,并重新绘制方块的位置。
keyup 事件触发后,方块继续以本级设定的速度向下落。
OLSFK.Options.Levels[OLSFK.Options.curLevel]
该字段表示当前级别对应的速度,即定时器间隔执行的时间(毫秒)。
OLSFK.EventFunc = function(code) {
switch (code)
{
case OLSFK.Options.direct.Left: //LEFT
if (!OLSFK.Options.Deling)
{
clearTimeout(OLSFK.Options.Timer);
OLSFK.Options.Eventing = true;
OLSFK.Options.Move = false;
OLSFK.Left();
}
break;
//...
}
}
该方法是监听 keydown 事件所执行的具体动作;code 为按键编码。
当判断未在消行动作时,清除定时器,将 OLSFK.Options.Eventing 设置为事件中 true,并将 OLSFK.Options.Move 设置为 false 表示停止移动。
随后进入向左移动方法 OLSFK.Left()。
OLSFK.Left = function() {
var block = OLSFK.Items[OLSFK.Options.curBlock];
if (block)
{
var flag = true;
for (var i=1; i<=4; i++)
{
var x = block[i].x;
var y = block[i].y;
if (x-1 < 0)
{
flag = false;
break;
}
if (OLSFK.Options.GampMap[(x-1)+'_'+y] == 1 && !OLSFK.isMe(x-1,y))
{
flag = false;
break;
}
}
if (flag)
{
for (var i=1; i<=4; i++) //清除图形
{
var itm = block[i];
var box = Lib.Getid('box_'+itm.x+'_'+itm.y);
box.style.background = '#ddd';
OLSFK.Options.GampMap[itm.x+'_'+itm.y] = 0;
}
for (var i=1; i<=5; i++)
{
var x = block[i].x;
var y = block[i].y;
OLSFK.Items[OLSFK.Options.curBlock][i] = {x:(x-1),y:y};
}
OLSFK.draw();
}
}
}
var block = OLSFK.Items[OLSFK.Options.curBlock]; 表示获取当前正在移动的方块数据。
if (OLSFK.Options.GampMap[(x-1)+'_'+y] == 1 && !OLSFK.isMe(x-1,y))
{
flag = false;
break;
}
判断该方块四个小块左边是否有被占用的方块,即检查: OLSFK.Options.GampMap[(x-1)+'_'+y] 是否为 1,并且该位置不属于当前方块自身。
当左边方向无占用格时,清除当前方块四个小块的位置,重新绘制方块的新坐标位置;并重置相应的 OLSFK.Options.GameMap [x+y] 中对应格子的值。
当按钮 keyup 时,继承正常的向下落逻辑。
OLSFK.isMe 代码用于判断。
OLSFK.isMe = function(x,y) {
var block = OLSFK.Items[OLSFK.Options.curBlock];
if (block)
{
for (var i=1; i<=4; i++)
{
if (block[i].x == x && block[i].y == y)
{
return true;
}
}
}
return false;
}
即指定的 x,y(参数值)是否还在当前方块四个坐标范围内。
OLSFK.Right() 与 Left() 的逻辑相同。
旋转方块代码的实现部分。
OLSFK.Rotate = function() {
var block = OLSFK.Items[OLSFK.Options.curBlock];
if (block)
{
var flag = true;
var R = block[5];
for (var i=1; i<=4; i++)
{
var x = block[i].x;
var y = block[i].y;
if (R.x == x && R.y == y)
{
} else {
var nson = new Object();
nson.x = R.x + R.y - y;
nson.y = R.y - R.x + x;
if ( nson.x < 0 || nson.y < 0 || nson.x >= OLSFK.Options.width || nson.y >= OLSFK.Options.height )
{
flag = false;
break;
}
if (OLSFK.Options.GampMap[nson.x+'_'+nson.y] == 1 && !OLSFK.isMe(nson.x,nson.y))
{
flag = false;
break;
}
}
}
if (flag)
{
for (var i=1; i<=4; i++) //清除图形
{
var itm = block[i];
var box = Lib.Getid('box_'+itm.x+'_'+itm.y);
box.style.background = '#ddd';
OLSFK.Options.GampMap[itm.x+'_'+itm.y] = 0;
}
var Pnt = 1;
for (var i=1; i<=4; i++)
{
var x = block[i].x;
var y = block[i].y;
if (R.x == x && R.y == y)
{
Pnt = i;
} else {
var nson = new Object();
nson.x = R.x + R.y - y;
nson.y = R.y - R.x + x;
OLSFK.Items[OLSFK.Options.curBlock][i] = {x:nson.x,y:nson.y};
}
}
OLSFK.Items[OLSFK.Options.curBlock][5] = OLSFK.Items[OLSFK.Options.curBlock][Pnt];
OLSFK.draw();
}
}
}
var R = block[5]; 就是获取旋转点坐标。
接着对方块四个小块以旋转点为中心,进行逆时针旋转(并非全是 90 度);当当前块不为旋转点时,应用旋转公式。
var nson = new Object();
nson.x = R.x + R.y - y;
nson.y = R.y - R.x + x;
这个公式需要这样理解。
ResultX = RotateX + (RotateY - CurrentY);
ResultY = RotateY - (RotateX - CurrentX);
//Y的偏移量,就是X的增加值;
//反之同
当旋转四周都无占用物时,清除当前图形,重绘旋转后的图形位置。
重置 OLSFK.Options.GampMap[itm.x+'_'+itm.y] 中各个方块的占用状态值。
OLSFK.Random = function() {
if (OLSFK.Options.nextBlock != 0)
{
OLSFK.Options.curBlock = OLSFK.Options.nextBlock;
var block = OLSFK.Next[OLSFK.Options.nextBlock];
if (block)
{
for (var i=1; i<=4; i++)
{
var itm = block[i];
var box = Lib.Getid('cur_'+itm.x+'_'+itm.y);
box.style.background = '#ddd';
//OLSFK.Options.GampMap[itm.x+'_'+itm.y] = 0;
}
}
} else {
OLSFK.Options.curBlock = Math.floor(Math.random() * 7 + 1);
}
OLSFK.Options.nextBlock = Math.floor(Math.random() * 7 + 1);
OLSFK.drawNext();
}
随机生成下次预下落的方块,并显示到右上角的预览表格中。
OLSFK.play = function(speed) {
var block = OLSFK.Items[OLSFK.Options.curBlock];
if (block && OLSFK.Options.Move)
{
var flag = true;
for (var i=1; i<=4; i++)
{
var x = block[i].x;
var y = block[i].y;
if (y+1 >= OLSFK.Options.height)
{
flag = false;
break;
}
if (OLSFK.Options.GampMap[x+'_'+(y+1)] == 1 && !OLSFK.isMe(x,y+1))
{
flag = false;
break;
}
}
if (flag)
{
for (var i=1; i<=4; i++) //清除图形
{
var itm = block[i];
var box = Lib.Getid('box_'+itm.x+'_'+itm.y);
box.style.background = '#ddd';
OLSFK.Options.GampMap[itm.x+'_'+itm.y] = 0;
}
for (var i=1; i<=5; i++)
{
var x = block[i].x;
var y = block[i].y;
OLSFK.Items[OLSFK.Options.curBlock][i] = {x:x,y:(y+1)};
}
OLSFK.draw();
var S = OLSFK.Options.Levels[OLSFK.Options.curLevel];
if (speed)
{
S = 50;
}
OLSFK.Options.Timer = setTimeout(function() {
OLSFK.play();
}, S);
} else {
OLSFK.ReItems(OLSFK.Options.curBlock);
OLSFK.newRun();
}
}
}
OLSFK.play 是正常下落的方法,需判断下落一格是否有被占用的格子,如果没有,则清除当前方块,绘制方块的新位置。
当方块不能再下落时(flag = false),重置当前方块坐标配置,调用 OLSFK.ReItems(OLSFK.Options.curBlock)。
进入 OLSFK.newRun(),进行新下落方块下落过程的准备。
OLSFK.newRun = function() {
clearTimeout(OLSFK.Options.Timer);
OLSFK.DelFunc();
if (OLSFK.Options.deline >= 10)
{
OLSFK.Options.deline = 0;
OLSFK.Options.curLevel ++;
OLSFK.Element.CurLevel.setHTML("级:"+OLSFK.Options.curLevel);
}
OLSFK.Element.Score.setHTML("分:"+OLSFK.Options.Score);
if (OLSFK.Options.curLevel <= OLSFK.Options.lineNum)
{
OLSFK.Random();
//判断是否结束
OLSFK.ChkEnd();
} else {
OLSFK.Options.Move = false;
OLSFK.Options.Start = false;
OLSFK.Options.Eventing = false;
OLSFK.Options.Deling = false;
Lib.Getid('spn').innerHTML = 'Game Is Over! You Win the Game!';
Lib.Getid('dobtn').innerHTML = ' 开始 ';
}
}
当下落结束时,清除定时器,暂停新方块下落,检测是否有可消除的行,并统计消除的行数。
每消除一行,增加相应的分数。
OLSFK.Options.Score += OLSFK.Options.ScoreNum;
这个方法在特定上下文中执行。
OLSFK.DelFunc = function() {
OLSFK.Options.Deling = true;
OLSFK.Options.Move = false;
OLSFK.Options.Eventing = false;
var Fn = 0;
for (var i=OLSFK.Options.height-1; i>=0; i--)
{
Fn = 0;
for (var j=0; j<OLSFK.Options.width; j++)
{
if (OLSFK.Options.GampMap[j+'_'+i] == 1)
{
Fn++;
}
}
if (Fn == OLSFK.Options.width)
{
OLSFK.Options.deline ++;
OLSFK.Options.Score += OLSFK.Options.ScoreNum;
OLSFK.DelLine(i);
i++;
}
}
OLSFK.Options.Deling = false;
OLSFK.Options.Move = true;
OLSFK.Options.Eventing = true;
}
中执行。
减完一行后,重置该行以上所有行往下降一行,并重置相关数据。
OLSFK.Options.GampMap[x+'_'+y] = OLSFK.Options.GampMap[x+'_'+(y-1)];
该减行为上行的数据。
if (Fn == OLSFK.Options.width)
{
OLSFK.Options.deline ++;
OLSFK.Options.Score += OLSFK.Options.ScoreNum;
OLSFK.DelLine(i);
i++;
}
该判断表示该行上所有格子都被占用。
回到 newRun 流程,当判断消行超过设定行数时,即提升等级。
if (OLSFK.Options.curLevel <= OLSFK.Options.lineNum)
{
OLSFK.Random();
//判断是否结束
OLSFK.ChkEnd();
}
如果级数小于配置的总级数,则进入 OLSFK.random() 流程。
设置当前下落方块,并随机生成下次下落方块,显示在右上角表格中。
OLSFK.ChkEnd = function() {
var block = OLSFK.Items[OLSFK.Options.curBlock];
if (block && OLSFK.Options.Move)
{
var flag = true;
for (var i=1; i<=4; i++)
{
var x = block[i].x;
var y = block[i].y;
if (OLSFK.Options.GampMap[x+'_'+y] == 1)
{
flag = false;
break;
}
}
}
if (flag )
{
OLSFK.draw();
//定时往下掉
OLSFK.Options.Timer = setTimeout(function() {
OLSFK.play();
}, OLSFK.Options.Levels[OLSFK.Options.curLevel]);
} else {
OLSFK.Options.Move = false;
OLSFK.Options.Start = false;
OLSFK.Options.Eventing = false;
OLSFK.Options.Deling = false;
Lib.Getid('spn').innerHTML = 'Game Is Over';
Lib.Getid('dobtn').innerHTML = ' 开始 ';
}
}
当当前下落的方块进入表格上有被占用的格子,即被卡住,游戏结束。
反之,则通过 setTimeout 开始新方块的下落动作。
其他方法说明
OLSFK.Event = function() {
if (window.isIE)
return window.event;
func = OLSFK.Event.caller;
while(func!=null)
{
var arg0=func.arguments[0];
if(arg0)
{
return arg0;
}
func=func.caller;
}
return null;
}
OLSFK.KeyCode = function() {
return OLSFK.Event().keyCode || OLSFK.Event().which;
}
OLSFK.Event = function();
这是一种获取当前事件的方法,可以比较兼容地获取当前的事件对象。
俄罗斯方块 JavaScript 代码
点击此处本站下载。
更多关于 JavaScript 相关内容感兴趣的读者可查看本站专题:《JavaScript数学运算用法总结》、《JavaScript数据结构与算法技巧总结》、《JavaScript数组操作技巧总结》、《JavaScript排序算法总结》、《JavaScript遍历算法与技巧总结》、《JavaScript查找算法技巧总结》及《JavaScript错误与调试技巧总结》。
希望本文所述对大家 JavaScript 程序设计有所帮助。
JS大数值转字符串出现科学计数法怎么办
2026-11-05 17:09:21
JS split切割后出现空字符串怎么过滤
2026-11-05 16:00:30
在 React 或 Vue 项目中封装使用 JS 节流防抖
2026-11-05 15:27:29
JavaScript中怎么将带小数的字符串转为整数?
2026-11-05 14:32:38
怎么用JavaScript策略模式重构电商促销计算逻辑?
2026-11-05 14:01:09
