本俄罗斯方块代码采用 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: "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();
这是一种获取当前事件的方法,可以比较兼容获取当前的事件;