程序分析
俄罗斯方块是由多种类型的方块与游戏边界背景组成,根据面向对象的方法,把整个程序分隔成两部分--游戏主体、形状两个对象。
其中游戏主体处理的事情包括:
- 绘制游戏界面与边界。
- 容纳方块与形状并绘制。
- 控制器:监听键盘事件,并将之转换成对形状对象的控制,如变形、左移、右移、下移以及直接落地操作。
- 游戏规则控制:包括形状对象的生成,形状对象每次下落的时间间隔,边界检测(形状对象不能移除游戏边界之外)。
形状对象处理的事情:
- 根据指定的方块排列坐标,生成对应数量的方块以及各方块的坐标信息。
- 处理对象移动或变形操作对各方块坐标的影响,并使得游戏主体可以根据这些坐标正确绘制方块信息。
确定了程序的分割及功能逻辑划分下面要就要进行编码了。
程序代码
形状对象:Shape
var Shape = function(x, y, gc){//形状/方块对象,提供方块初始位置
this.gc = gc;
this.x = x;
this.y = y;
};
Shape.prototype = {
init : function(boxes, centerIndex){//初始化方块内容(提供方块各格子与初始位置的偏移量, 以及变型时中心位置(或传入false等于不可变型))
var me = this, gc = me.gc;
me.boxes = boxes.slice();
this.turnAble = true;
if(centerIndex === false){
this.turnAble = false;
}else{
this.centerIndex = (typeof centerIndex == 'undefined') ? 1 : centerIndex;
if(this.centerIndex >= boxes.length){
this.centerIndex = boxes.length >= 2 ? 1 : 0;
}
}
var x = me.x, y = me.y;
me.nodes = [];
for(var i = 0; i < me.boxes.length; i++){
var node = {x : x + me.boxes[i].x, y : y + me.boxes[i].y, dom : gc.createBox()};
me.nodes.push(node);
}
},
putIn : function($p){
for(var i = 0; i < this.nodes.length; i++){
$p.appendChild(this.nodes[i].dom);
}
},
turn : function(flag){//逆时针旋转, flag = true时顺时针旋转
var me = this, nodes = me.nodes, gc = me.gc, centerBox = 1;
if(me.turnAble){
var cx = nodes[centerBox].x, cy = nodes[centerBox].y;
for(var i = 0; i < nodes.length; i++){
var rx = nodes[i].x - cx, ry = nodes[i].y - cy;
rx = !!flag ? rx : -rx;
ry = !!flag ? -ry : ry;
nodes[i].x = cx + ry;
nodes[i].y = cy + rx;
}
var crossInfo = me.detectCross();
if(crossInfo){
me.move(-crossInfo.x, -crossInfo.y, true);
}
}
},
detectCross : function(){//检测是否跃出当前游戏网格(旋转后可能会有此问题), 返回超出范围的最大值,x轴和y轴
var me = this, nodes = me.nodes, gc = me.gc;
var crossX = 0, crossY = 0;
function absMax(a1, a2){return Math.abs(a1) > Math.abs(a2) ? a1 : a2;}
for(var i = 0; i < nodes.length; i++){
var node = nodes[i];
var absX = 0, absY = 0;
absX = node.x < gc.w && node.x >= 0 ? 0 : node.x % (gc.w - 1);
absY = node.y >= 0 && node.y < gc.h ? 0 : node.y % (gc.h - 1);
crossX = absMax(absX, crossX);
crossY = absMax(absY, crossY);
}
return crossX != 0 || crossY != 0 ? {x : crossX, y : crossY} : false;
},
moveAble : function(x, y){
var me = this, nodes = me.nodes, gc = me.gc, maxX = gc.w, maxY = gc.h;
var flag = true;
for(var i = 0; i < nodes.length; i++){
var node = nodes[i], index = i;
if(nodes[index].x + x >= maxX || nodes[index].y + y >= maxY){
flag = false;
break;
}
if(nodes[index].x + x < 0 || nodes[index].y + y < 0){
flag = false;
break;
}
}
return flag;
},
move : function(x, y, force){
var me = this, nodes = me.nodes, gc = me.gc, maxX = gc.w, maxY = gc.h;
var flag = !!force || me.moveAble(x, y);
if(flag){
var len = nodes.length;
for(var i = 0; i < nodes.length; i++){
nodes[i].x += x;
nodes[i].y += y;
}
}
return flag;
},
toRight : function(){
return this.move(1, 0);
},
toLeft : function(){
return this.move(-1, 0);
},
toUp : function(){
return this.move(0, -1);
},
toDown : function(){
return this.move(0, 1);
}
};
上面是形状对象的定义,可以看到形状对象定义了turn、move、toRight、toLeft、toUp、toDown以及detectCross等基本方法。其中detectCross方法是用于检测变型后各个方块的坐标是否还在游戏边界内,并返回最大的超出距离信息,供重置方块坐标。
方块编辑完了后就是要写Game游戏主体对象了。
Game:游戏主体对象
var extend = function(source){
var argLen = arguments.length;
for(var i = 1; i < argLen; i++){
var arg = arguments[i];
for(var i in arg){
source[i] = arg[i];
}
}
};
var Game = function(w, h, frame, score){//游戏世界对象
var me = this;
me.w = w;
me.h = h;
me.holder = new Array(w * h);
me.container = me.$c = frame;
me.score = score;
me.scoreCount = 0;
var scoreCount = 0;
me.getScoreCount = function(){
return scoreCount;
};
me.setScoreCount = function(record){
scoreCount = record;
};
var box = me.createBox();
me.container.appendChild(box);
me.boxWidth = box.clientWidth + 2;//2为边框宽度
me.boxHeight = box.clientHeight + 2;//2为边框宽度
var bw = me.boxWidth, bh = me.boxHeight;
me.container.removeChild(box);
extend(me.container.style, {width : w * bw + 'px', height : h * bh + 'px', position : 'relative', display : 'block'});//设置容器高度
me.eventListener = me.createEveltListener();
var level = 1;
me.setLevel = function(level2){
if(level2){
me.speed = 1000 / level2;
level = level2;
}
};
me.getLevel = function(){
return level;
};
me.setLevel(level);
};
Game.prototype = {
restore : function(){
var me = this, w = me.w, h = me.h;
me.pause();
me.holder = new Array(w * h);
me.curShape = false;
me.speed = 1000;
me.scoreCount = 0;
var children = me.container.children;
for(var i = 0; i < children.length; i++){
me.container.removeChild(children[i]);
}
},
createBox : function(){//创建格子元素
var box = doc.createElement('div');
box.className = 'box bg-yellow';
//box.setAttribute('className', 'box bg-yellow');
//box.setAttribute('class', 'box bg-yellow');
return box;
},
drawShape : function(shape){//绘制方块的位置
var me = this, bw = me.boxWidth, bh = me.boxHeight;
for(var i = 0, nodes = shape.nodes; i < nodes.length; i++){
extend(nodes[i].dom.style, {left : nodes[i].x * bw + 'px', top : nodes[i].y * bh + 'px'});
}
},
refreshDisplay : function(){//绘制/刷新已有方块的位置
var me = this, holder = me.holder, bw = me.boxWidth, bh = me.boxHeight;
var len = holder.length;
var w = me.w, h = me.h;
var index = 0;
for(var row = 0; row < h; row++){//此处不使用除法运算,由于除法运算精度会导致行数计算错误.
for(var col = 0; col < w; col++){
var $dom = holder[index];
if($dom && $dom.nodeType){
extend($dom.style, {left : col * bw + 'px', top : row * bh + 'px'});
}
index++;
}
}
},
detectShapeImpact : function(shape){//检测方块是否与世界冲突(碰撞检测)
var me = this, holder = me.holder,
w = me.w, h = me.h, nodes = shape.nodes;
var flag = false;
for(var index = 0; index < shape.nodes.length; index++){
var node = shape.nodes[index];
var nx = node.x, ny = node.y;
var i = ny * w + nx;
if(holder[i] && holder[i].nodeType){//与现有方块冲突
flag = true;
break;
}
}
return flag;
},
boxesSupport : [
{boxes : [{x:0,y:0},{x:0,y:1},{x:0,y:2},{x:0,y:3}], centerIndex : 1},
{boxes : [{x:0,y:0},{x:0,y:1},{x:1,y:1},{x:1,y:2}], centerIndex : 1},
{boxes : [{x:0,y:0},{x:0,y:1},{x:-1,y:1},{x:-1,y:2}], centerIndex : 1},
{boxes : [{x:0,y:0},{x:0,y:1},{x:0,y:2},{x:1,y:2}], centerIndex : 2},
{boxes : [{x:0,y:0},{x:1,y:0},{x:1,y:1},{x:1,y:2}], centerIndex : 1},
{boxes : [{x:0,y:0},{x:0,y:1},{x:0,y:2},{x:1,y:1}], centerIndex : 2},
{boxes : [{x:0,y:0},{x:1,y:0},{x:0,y:1},{x:1,y:1}], centerIndex : false},
{boxes : [{x:0,y:0}], centerIndex : false}
],
randomShape : function(){
var me = this, len = me.boxesSupport.length, x = Math.round(me.w / 2), y = 0,
rand = Math.floor(Math.random() * len);
var shape = new Shape(x, y, me);
shape.init(me.boxesSupport[rand].boxes, me.boxesSupport[rand].centerIndex);
shape.putIn(me.container);
return shape;
},
createEveltListener : function(){
var me = this;
return function(event){
var shape = me.curShape;
if(!shape){
return;
}
var mi = false;//MoveInfo移动信息
var isTurn = false;
var ev = event || win.event, key = ev.keyCode || ev.which || ev.charCode;
switch(key){
case 37 :
mi = {x:-1, y:0};
break;
case 39 :
mi = {x:1, y:0};
break;
case 40 :
mi = {x:0, y:1};
break;
case 38 :
//shape.toUp();
//break;
case 32 :
shape.turn();
isTurn = true;
break;
default :
isTurn = false;
mi = false;
//console.log('Unknow operate with code ' + event.keyCode);
}
if(mi){//移动操作
if(shape.moveAble(mi.x, mi.y)){
shape.move(mi.x, mi.y);
var flag = me.detectShapeImpact(shape);//检测碰撞信息
if(flag){
shape.move(-mi.x, -mi.y);//还原用于检测碰撞的位置信息.
if(mi.y > 0){//有碰撞,且为向下移动
me.mergeShape(shape);
return;
}
}
}else if(mi.y > 0){//向下,写不能移动
me.mergeShape(shape);
return;
}
}
if(isTurn){//旋转操作(变型操作)
var flag = me.detectShapeImpact(shape);//检测碰撞信息
if(flag){
shape.turn(true);//旋转回去
}
}
me.drawShape(shape);
};
},
eventListener : function(event){
},
mergeShape : function(shape){
var me = this, holder = me.holder,
w = me.w, h = me.h, nodes = shape.nodes;
var flag = false;
for(var index = 0; index < shape.nodes.length; index++){
var node = shape.nodes[index];
var nx = node.x, ny = node.y;
var i = ny * w + nx;
holder[i] = node.dom;
}
me.clearLine();
me.refreshDisplay();
me.curShape = me.randomShape();
var flag = me.detectShapeImpact(me.curShape);//检测碰撞信息
me.drawShape(me.curShape);
if(flag){
me.pause();
alert('游戏结束!');
}
},
clearLine : function(){
//清除满行的方块,并移动其上层下来
var me = this, holder = me.holder,
w = me.w, h = me.h, len = holder.length;
var clearLineCount = 0;
for(var i = 0; i < h; i++){
var lineFullFlag = true;
for(var j = 0; j < w; j++){
if(!holder[i * w + j]){
lineFullFlag = false;
break;
}
}
if(lineFullFlag){//当前行被填满
clearLineCount++;
for(var col = 0; col < w; col++){//删除元素
me.container.removeChild(holder[i * w + col]);
}
var oldHolder = holder, tmpHolder;
tmpHolder = holder.slice(0, i * w);//复制前段
tmpHolder = tmpHolder.concat(holder.slice((i+1) * w));//复制后段
tmpHolder = new Array(w).concat(tmpHolder);//填充全段
holder = tmpHolder;//变量交互
me.holder = holder;//维护全局变量
}
}
//console.log('Clear line (' + clearLineCount + ');');
if(clearLineCount > 0){
me.scoreCount += Math.pow(2, clearLineCount);
if(me.score){
me.score.innerHTML = me.scoreCount;
}
}
},
start : function(){
var me = this;
if(!me.started){
me.started = true;
if(!me.curShape){
me.curShape = me.randomShape();
me.drawShape(me.curShape);
}
//document.addEventListener('keydown', me.eventListener);
document.onkeydown = me.eventListener;
var roundFunc = function(){
//下移方块
me.eventListener({keyCode:40});
};
me.runInterval = setInterval(roundFunc, me.speed);
}
},
pause : function(){
var me = this;
me.started = false;
win.clearInterval(me.runInterval);
document.onkeydown = null;
//document.removeEventListener('keydown', me.eventListener);
}
};
HTML代码以及运行代码
<html>
<head>
<meta charset="UTF-8" content="text/html;charset=utf-8">
<title>俄罗斯方块</title>
<style type="text/css" rel="stylesheet">
.box{width:18px; height:18px; border:1px solid lightgray; display: block; position: absolute;}
.bg-red{background-color: red;}
.bg-blue{background-color: blue;}
.bg-yellow{background-color: yellow;}
.bg-gray{background-color: gray;}
.frame{border: 1px solid; position: relative; display: block; background-color: lightblue;}
.score{border: 1px solid; display: block; background-color: lightgray; width:200px; height:36px; font-size: 18px; line-height: 36px; margin-top:5px;}
.wrap{display: block;}
.hide{display:none;}
.hide2{visibility: hidden;}
</style>
<script type="text/javascript" src="js/tetris.js"></script>
</head>
<body>
<div class="wrap">
<div class="frame" id="frame">
</div>
<div class="score" id="score"></div>
</div>
<script type="text/javascript">
(function(win, doc){
g = new Game(15, 20, document.getElementById('frame'), document.getElementById('score'));
g.start();
})(window, document);
</script>
</body>
</html>
程序源代码下载
程序源代码下载地址: http://download.csdn.net/detail/zyb134506/6928983