要做俄罗斯方块,先来对我们的游戏进行一个规划。首先,游戏要包含一个俄罗斯方块的游戏区,是一个10X20的矩形区域,然后旁边有一个区域提示下一个方块是什么,同时还有计时和计分。计分规则如下:同时消灭一行得10分,两行得30分,三行得60分,四行得100分。我们通过上下左右和空格来控制方块的移动,上表示旋转,下表示下移一行,空格表示直接下落,左右就不说了。
首先,我想先把界面做出来,看看效果才有心思做逻辑呀!
html:
<!DOCTYPE html> <head> <meta charset="utf-8" /> <title>俄罗斯方块</title> <link href="style.css" rel="stylesheet" /> </head> <body> <div class="game" id="game"></div> <div class="next" id="next"></div> <div class="info"> <div>已用时:<span id="time">0</span>s</div> <div>已得分:<span id="score">0</span>分</div> </div> <script src="script.js"></script> </body>check the result
css:
.game { width: 200px; height: 400px; background: #F2FAFF; border-left: 1px solid blue; border-right: 1px solid blue; border-bottom: 1px solid blue; position: absolute; top: 10px; left: 10px; } .next { width: 80px; height: 80px; position: absolute; top: 10px; left: 250px; border: 1px solid blue; background: #F2FAFF; } .info { position: absolute; top: 100px; left: 250px; } .none, .current, .done { width: 20px; height: 20px; position: absolute; box-sizing: border-box; } .none { background: #F2FAFF; } .current { background: pink; border: 1px solid red; } .done { background: gray; border: 1px solid black; }
javascript:
var nextData = [ [2, 2, 0, 0], [0, 2, 2, 0], [0, 0, 0, 0], [0, 0, 0, 0] ]; //代表竖棍 var gameData = [ [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, 2, 0, 0, 0, 0], [0, 0, 1, 2, 2, 2, 1, 1, 0, 0], [0, 1, 0, 1, 1, 1, 0, 0, 0, 0], [0, 1, 0, 1, 0, 1, 0, 0, 0, 0] ]; var gameDivs = []; var nextDivs = []; var initGame = function() { for(var i=0; i<gameData.length; i++) { var gameDiv = []; for(var j=0; j<gameData[0].length; j++) { var newNode = document.createElement('div'); newNode.className = 'none'; newNode.style.top = (i*20) + 'px'; newNode.style.left = (j*20) + 'px'; document.getElementById('game').appendChild(newNode); gameDiv.push(newNode); } gameDivs.push(gameDiv); } } var initNext = function() { for(var i=0; i<nextData.length; i++) { var nextDiv = []; for(var j=0; j<nextData[0].length; j++) { var newNode = document.createElement('div'); newNode.className = 'none'; newNode.style.top = (i*20) + 'px'; newNode.style.left = (j*20) + 'px'; document.getElementById('next').appendChild(newNode); nextDiv.push(newNode); } nextDivs.push(nextDiv); } } var refreshGame = function() { for(var i=0; i<gameData.length; i++) { for(var j=0; j<gameData[0].length; j++) { if(gameData[i][j] == 0) { gameDivs[i][j].className = 'none'; } else if(gameData[i][j] == 1) { gameDivs[i][j].className = 'done'; } else if(gameData[i][j] == 2) { gameDivs[i][j].className = 'current'; } } } } var refreshNext = function() { for(var i=0; i<nextData.length; i++) { for(var j=0; j<nextData[0].length; j++) { if(nextData[i][j] == 0) { nextDivs[i][j].className = 'none'; } else if(nextData[i][j] == 1) { nextDivs[i][j].className = 'done'; } else if(nextData[i][j] == 2) { nextDivs[i][j].className = 'current'; } } } } initGame(); refreshGame(); initNext(); refreshNext();
思路其实比较明确,我们希望操作数组,然后把数组渲染成界面,也就是可视化,我们用到的是refreshxxx函数,这样,在每次改变数组的时候,调用refresh函数就ok了。Div还是用绝对布局,三种样式表示了div块的三种状态。但是看看代码,其实冗余度挺大的,我们来改写一下,做点面向对象该做的事情吧!
html:
<!DOCTYPE html> <head> <meta charset="utf-8" /> <title>俄罗斯方块</title> <link href="style.css" rel="stylesheet" /> </head> <body> <div class="game" id="game"></div> <div class="next" id="next"></div> <div class="info"> <div>已用时:<span id="time">0</span>s</div> <div>已得分:<span id="score">0</span>分</div> </div> <script src="game.js"></script> <script src="script.js"></script> </body>check the result
css:
.game { width: 200px; height: 400px; background: #F2FAFF; border-left: 1px solid blue; border-right: 1px solid blue; border-bottom: 1px solid blue; position: absolute; top: 10px; left: 10px; } .next { width: 80px; height: 80px; position: absolute; top: 10px; left: 250px; border: 1px solid blue; background: #F2FAFF; } .info { position: absolute; top: 100px; left: 250px; } .none, .current, .done { width: 20px; height: 20px; position: absolute; box-sizing: border-box; } .none { background: #F2FAFF; } .current { background: pink; border: 1px solid red; } .done { background: gray; border: 1px solid black; }
script.js:
var game = new SquareGame(); game.init(); game.start();
game.js:
var SquareGame = function() { // 下落速度 var INTERVAL = 200; // 所有的方块 var squares = [ [ [0, 2, 0, 0], [0, 2, 0, 0], [0, 2, 0, 0], [0, 2, 0, 0] ], [ [0, 2, 0, 0], [0, 2, 0, 0], [0, 2, 2, 0], [0, 0, 0, 0] ], [ [0, 0, 2, 0], [0, 0, 2, 0], [0, 2, 2, 0], [0, 0, 0, 0] ], [ [0, 2, 2, 0], [0, 2, 2, 0], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [0, 2, 0, 0], [2, 2, 2, 0], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [0, 2, 2, 0], [2, 2, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [2, 2, 0, 0], [0, 2, 2, 0], [0, 0, 0, 0], [0, 0, 0, 0] ] ]; // 下一个方块 var nextData = [ [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0] ]; // 现在的方块 var curData = [ [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0] ]; // 游戏矩阵 var gameData = [ [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] ]; // 游戏divs var gameDivs = []; // 下一个的divs var nextDivs = []; // 定时器 var timer; // 原点 var origin = { x: 0, y: 0 } // 初始化div var initDivs = function(container, data, divs) { for(var i=0; i<data.length; i++) { var div = []; for(var j=0; j<data[0].length; j++) { var newNode = document.createElement('div'); newNode.className = 'none'; newNode.style.top = (i*20) + 'px'; newNode.style.left = (j*20) + 'px'; container.appendChild(newNode); div.push(newNode); } divs.push(div); } } // 更新div var refreshDivs = function(data, divs) { for(var i=0; i<data.length; i++) { for(var j=0; j<data[0].length; j++) { if(data[i][j] == 0) { divs[i][j].className = 'none'; } else if(data[i][j] == 1) { divs[i][j].className = 'done'; } else if(data[i][j] == 2) { divs[i][j].className = 'current'; } } } } // 检测点是否合法 var check = function(pos, x, y) { if(pos.x + x < 0) { return false; } if(pos.x + x >= gameData.length) { return false; } if(pos.y + y < 0) { return false; } if(pos.y + y >= gameData[0].length) { return false; } if(gameData[pos.x + x][pos.y + y] == 1) { return false; } return true; } // 检测数据是否可行 var isValid = function(pos, data) { for(var i=0; i<data.length; i++) { for(var j=0; j<data[0].length; j++) { if(data[i][j] != 0) { if(!check(pos, i, j)) { return false; } } } } return true; } // 清除数据 var clearData = function() { for(var i=0; i<curData.length; i++) { for(var j=0; j<curData[0].length; j++) { if(check(origin, i, j)) { gameData[origin.x + i][origin.y + j] = 0; } } } } // 设置数据 var setData = function() { for(var i=0; i<curData.length; i++) { for(var j=0; j<curData[0].length; j++) { if(check(origin, i, j)) { gameData[origin.x + i][origin.y + j] = curData[i][j]; } } } } // 能否下降 var canDown = function() { var test = {}; test.x = origin.x + 1; test.y = origin.y; return isValid(test, curData); } // 下降 var down = function() { clearData(); origin.x = origin.x + 1; setData(); refreshDivs(gameData, gameDivs); } // 能否左移 var canLeft = function() { var test = {}; test.x = origin.x; test.y = origin.y - 1; return isValid(test, curData); } // 左移 var left = function() { clearData(); origin.y = origin.y - 1; setData(); refreshDivs(gameData, gameDivs); } // 能否右移 var canRight = function() { var test = {}; test.x = origin.x; test.y = origin.y + 1; return isValid(test, curData); } // 右移 var right = function() { clearData(); origin.y = origin.y + 1; setData(); refreshDivs(gameData, gameDivs); } // 能否旋转 var canRotate = function() { var testData = [ [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0] ]; for(var i=0; i<curData.length; i++) { for(var j=0; j<curData[0].length; j++) { testData[i][j] = curData[3-j][i]; } } return isValid(origin, testData); } // 旋转 var rotate = function() { clearData(); var testData = [ [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0] ]; for(var i=0; i<curData.length; i++) { for(var j=0; j<curData[0].length; j++) { testData[i][j] = curData[3-j][i]; } } for(var i=0; i<curData.length; i++) { for(var j=0; j<curData[0].length; j++) { curData[i][j] = testData[i][j]; } } setData(); refreshDivs(gameData, gameDivs); } // 消除行 var checkClear = function() { for(var i=gameData.length-1; i>=0; i--) { var clear = true; for(var j=0; j<gameData[0].length; j++) { if(gameData[i][j] != 1) { clear = false; } } if(clear) { for(var m=i; m>0; m--) { for(var n=0; n<gameData[0].length; n++) { gameData[m][n] = gameData[m-1][n]; } } for(var n=0; n<gameData[0].length; n++) { gameData[0][n] = 0; } i++; } } } // 检查游戏结束 var checkGameOver = function() { var gameOver = false; for(var i=0; i<gameData[0].length; i++) { if(gameData[0][i] == 1) { gameOver = true; break; } } if(gameOver) { document.onkeydown = null; clearInterval(timer); window.alert('game over!'); } } // 方块移到底部,给它固定 var fixed = function() { for(var i=0; i<curData.length; i++) { for(var j=0; j<curData[0].length; j++) { if(check(origin, i, j)) { if(gameData[origin.x + i][origin.y + j] == 2) { gameData[origin.x + i][origin.y + j] = 1; } } } } checkClear(); checkGameOver(); } // 生成下一个方块 var randomNext = function() { var ran = Math.random(); var index = Math.ceil(ran * 7) - 1; for(var i=0; i<squares[index].length; i++) { for(var j=0; j<squares[index][0].length; j++) { nextData[i][j] = squares[index][i][j]; } } refreshDivs(nextData, nextDivs); } // 使用下一个方块 var performNext = function() { origin.x = 0; origin.y = 3; for(var i=0; i<nextData.length; i++) { for(var j=0; j<nextData[0].length; j++) { curData[i][j] = nextData[i][j]; } } setData(); randomNext(); } // 绑定按键事件 var bindKeyEvent = function() { document.onkeydown = function(e) { if (e.keyCode == 38) { //up if(canRotate()) { rotate(); } } else if (e.keyCode == 39) { //right if(canRight()) { right(); } } else if (e.keyCode == 40) { //down move(); } else if (e.keyCode == 37) { //left if(canLeft()) { left(); } } else if (e.keyCode == 32) { //space while(!move()); } }; } // 向下移动 var move = function() { if(canDown()) { down(); return false; } else { fixed(); performNext(); return true; } } // 初始化 this.init = function() { initDivs(document.getElementById('game'), gameData, gameDivs); initDivs(document.getElementById('next'), nextData, nextDivs); refreshDivs(gameData, gameDivs); refreshDivs(nextData, nextDivs); randomNext(); } // 开始 this.start = function() { performNext(); bindKeyEvent(); timer = setInterval(move, INTERVAL); } // 结束 this.stop = function() { clearInterval(timer); document.onkeydown = null; } }
在这个例子中,我们基本实现了游戏的逻辑。首先,我们抽象出来一个SquareGame类,放在game.js里面,所以在script.js中,代码出奇地少。但是在game.js中代码却十分复杂。首先,我们将7种方块定义成一个数组,然后,我们把initDiv和refreshDiv的公共逻辑抽取出来,接着,我们实现了方块的旋转、左移、右移和下移,最后在每次方块下移的时候,我们判断能否下移,不能的话,方块就固定住了,然后检查是否可以消行,是否游戏结束。
但是这个版本还有一些bug,比如旋转的时候,行为比较奇怪,计时和计分没有,没有较好的操作提示等等,而且game.js的代码比较多。对于旋转,其实没必要用算法,我们自己定义一些旋转的矩阵就行了,而且也应该分出一个方块类,然后让七种方块去继承这个类,将代码结构调整如下:
html:
<!DOCTYPE html> <head> <meta charset="utf-8" /> <title>俄罗斯方块</title> <link href="style.css" rel="stylesheet" /> </head> <body> <div class="game" id="game"></div> <div class="next" id="next"></div> <div class="info"> <div>已用时:<span id="time">0</span>s</div> <div>已得分:<span id="score">0</span>分</div> <div>请用方向键和空格进行操作:上->旋转,左->左移,右->右移,下->下移,空格->旋转</div> <div id="gameover"></div> </div> <script src="square.js"></script> <script src="square1.js"></script> <script src="square2.js"></script> <script src="square3.js"></script> <script src="square4.js"></script> <script src="square5.js"></script> <script src="square6.js"></script> <script src="square7.js"></script> <script src="game.js"></script> <script src="script.js"></script> </body>check the result
css:
.game { width: 200px; height: 400px; background: #F2FAFF; border-left: 1px solid blue; border-right: 1px solid blue; border-bottom: 1px solid blue; position: absolute; top: 10px; left: 10px; } .next { width: 80px; height: 80px; position: absolute; top: 10px; left: 250px; border: 1px solid blue; background: #F2FAFF; } .info { position: absolute; top: 100px; left: 250px; } .none, .current, .done { width: 20px; height: 20px; position: absolute; box-sizing: border-box; } .none { background: #F2FAFF; } .current { background: pink; border: 1px solid red; } .done { background: gray; border: 1px solid black; }
script.js:
var game = new SquareGame(); game.init(); game.start();
game.js:
var SquareGame = function() { // 下落速度 var INTERVAL = 200; // 下一个方块 var next = Square.prototype.make(); // 现在的方块 var cur; // 游戏矩阵 var gameData = [ [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] ]; // 游戏divs var gameDivs = []; // 下一个的divs var nextDivs = []; // 定时器 var timer; // 分数 var score = 0; // 时间 var time = 0; // 时间计数 var timeCount = 0; // 初始化div var initDivs = function(container, data, divs) { for(var i=0; i<data.length; i++) { var div = []; for(var j=0; j<data[0].length; j++) { var newNode = document.createElement('div'); newNode.className = 'none'; newNode.style.top = (i*20) + 'px'; newNode.style.left = (j*20) + 'px'; container.appendChild(newNode); div.push(newNode); } divs.push(div); } } // 更新div var refreshDivs = function(data, divs) { for(var i=0; i<data.length; i++) { for(var j=0; j<data[0].length; j++) { if(data[i][j] == 0) { divs[i][j].className = 'none'; } else if(data[i][j] == 1) { divs[i][j].className = 'done'; } else if(data[i][j] == 2) { divs[i][j].className = 'current'; } } } } // 检测点是否合法 var check = function(pos, x, y) { if(pos.x + x < 0) { return false; } if(pos.x + x >= gameData.length) { return false; } if(pos.y + y < 0) { return false; } if(pos.y + y >= gameData[0].length) { return false; } if(gameData[pos.x + x][pos.y + y] == 1) { return false; } return true; } // 检测数据是否可行 var isValid = function(pos, data) { for(var i=0; i<data.length; i++) { for(var j=0; j<data[0].length; j++) { if(data[i][j] != 0) { if(!check(pos, i, j)) { return false; } } } } return true; } // 清除数据 var clearData = function() { for(var i=0; i<cur.data.length; i++) { for(var j=0; j<cur.data[0].length; j++) { if(check(cur.origin, i, j)) { gameData[cur.origin.x + i][cur.origin.y + j] = 0; } } } } // 设置数据 var setData = function() { for(var i=0; i<cur.data.length; i++) { for(var j=0; j<cur.data[0].length; j++) { if(check(cur.origin, i, j)) { gameData[cur.origin.x + i][cur.origin.y + j] = cur.data[i][j]; } } } } // 下降 var down = function() { if(cur.canDown(isValid)) { clearData(); cur.down(); setData(); refreshDivs(gameData, gameDivs); return true; } else { return false; } } // 左移 var left = function() { if(cur.canLeft(isValid)) { clearData(); cur.left(); setData(); refreshDivs(gameData, gameDivs); } } // 右移 var right = function() { if(cur.canRight(isValid)) { clearData(); cur.right(); setData(); refreshDivs(gameData, gameDivs); } } // 旋转 var rotate = function() { var a = cur.canRotate(isValid); if(a) { clearData(); cur.rotate(); setData(); refreshDivs(gameData, gameDivs); } } // 加分 var addScore = function(line) { var s = 0; if(line == 1) { s = 10; } else if(line == 2) { s = 30; } else if(line == 3) { s = 60; } else if(line == 4) { s = 100; } score = score + s; document.getElementById('score').innerHTML = score; } // 消除行 var checkClear = function() { var line = 0; for(var i=gameData.length-1; i>=0; i--) { var clear = true; for(var j=0; j<gameData[0].length; j++) { if(gameData[i][j] != 1) { clear = false; break; } } if(clear) { line++; for(var m=i; m>0; m--) { for(var n=0; n<gameData[0].length; n++) { gameData[m][n] = gameData[m-1][n]; } } for(var n=0; n<gameData[0].length; n++) { gameData[0][n] = 0; } i++; } } addScore(line); } // 检查游戏结束 var checkGameOver = function() { var gameOver = false; for(var i=0; i<gameData[0].length; i++) { if(gameData[0][i] == 1) { gameOver = true; break; } } if(gameOver) { document.onkeydown = null; clearInterval(timer); window.alert('game over!'); } } // 方块移到底部,给它固定 var fixed = function() { for(var i=0; i<cur.data.length; i++) { for(var j=0; j<cur.data[0].length; j++) { if(check(cur.origin, i, j)) { if(gameData[cur.origin.x + i][cur.origin.y + j] == 2) { gameData[cur.origin.x + i][cur.origin.y + j] = 1; } } } } checkClear(); checkGameOver(); } // 生成下一个方块 var randomNext = function() { next = next.make(); } // 使用下一个方块 var performNext = function() { cur = next; setData(); randomNext(); refreshDivs(next.data, nextDivs); } // 绑定按键事件 var bindKeyEvent = function() { document.onkeydown = function(e) { if (e.keyCode == 38) { //up rotate(); } else if (e.keyCode == 39) { //right right(); } else if (e.keyCode == 40) { //down move(); } else if (e.keyCode == 37) { //left left(); } else if (e.keyCode == 32) { //space while(!move()); } }; } // 向下移动 var move = function() { timeCount = timeCount + 1; if(timeCount == 5) { timeCount = 0; time = time + 1; document.getElementById('time').innerHTML = time; } if(down()) { return false; } else { fixed(); performNext(); refreshDivs(gameData, gameDivs); return true; } } // 初始化,这里是入口 this.init = function() { initDivs(document.getElementById('game'), gameData, gameDivs); initDivs(document.getElementById('next'), next.data, nextDivs); refreshDivs(gameData, gameDivs); refreshDivs(next.data, nextDivs); } // 开始 this.start = function() { performNext(); bindKeyEvent(); timer = setInterval(move, INTERVAL); } // 结束 this.stop = function() { clearInterval(timer); document.onkeydown = null; } }
square.js:
var Square = function() { // 方块数据 this.data = [ [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0] ]; // 原点 this.origin = { x: 0, y: 0 } // 旋转方向 this.dir = 0; } Square.prototype.make = function() { var index = Math.ceil(Math.random() * 7) - 1; var s; if(index == 0) { s = new Square1(); } else if(index == 1) { s = new Square2(); } else if(index == 2) { s = new Square3(); } else if(index == 3) { s = new Square4(); } else if(index == 4) { s = new Square5(); } else if(index == 5) { s = new Square6(); } else if(index == 6) { s = new Square7(); } s.origin.x = 0; s.origin.y = 3; var dir = Math.ceil(Math.random() * 4) - 1; s.rotate(dir); return s; } Square.prototype.canRotate = function(isValid) { var d = this.dir + 1; if(d == 4) { d = 0; } var t = [ [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0] ]; for(var i=0; i<this.data.length; i++) { for(var j=0; j<this.data[0].length; j++) { t[i][j] = this.rotates[d][i][j]; } } return isValid(this.origin, t); } Square.prototype.rotate = function(num) { if(!num) num = 1; this.dir = this.dir + num; while(this.dir >= 4) { this.dir = this.dir - 4; } for(var i=0; i<this.data.length; i++) { for(var j=0; j<this.data[0].length; j++) { this.data[i][j] = this.rotates[this.dir][i][j]; } } } Square.prototype.canDown = function(isValid) { var test = {}; test.x = this.origin.x + 1; test.y = this.origin.y; return isValid(test, this.data); } Square.prototype.down = function() { this.origin.x = this.origin.x + 1; } Square.prototype.canLeft = function(isValid) { var test = {}; test.x = this.origin.x; test.y = this.origin.y - 1; return isValid(test, this.data); } Square.prototype.left = function() { this.origin.y = this.origin.y - 1; } Square.prototype.canRight = function(isValid) { var test = {}; test.x = this.origin.x; test.y = this.origin.y + 1; return isValid(test, this.data); } Square.prototype.right = function() { this.origin.y = this.origin.y + 1; }
square1.js:
var Square1 = function() { this.square = Square; this.square(); this.rotates = [ [ [0, 2, 0, 0], [0, 2, 0, 0], [0, 2, 0, 0], [0, 2, 0, 0] ], [ [0, 0, 0, 0], [2, 2, 2, 2], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [0, 2, 0, 0], [0, 2, 0, 0], [0, 2, 0, 0], [0, 2, 0, 0] ], [ [0, 0, 0, 0], [2, 2, 2, 2], [0, 0, 0, 0], [0, 0, 0, 0] ] ]; } Square1.prototype = Square.prototype;
square2.js:
var Square2 = function() { this.square = Square; this.square(); this.rotates = [ [ [0, 2, 0, 0], [0, 2, 0, 0], [0, 2, 2, 0], [0, 0, 0, 0] ], [ [0, 0, 0, 0], [0, 2, 2, 2], [0, 2, 0, 0], [0, 0, 0, 0] ], [ [0, 0, 0, 0], [0, 2, 2, 0], [0, 0, 2, 0], [0, 0, 2, 0] ], [ [0, 0, 0, 0], [0, 0, 2, 0], [2, 2, 2, 0], [0, 0, 0, 0] ] ]; } Square2.prototype = Square.prototype;
square3.js:
var Square3 = function() { this.square = Square; this.square(); this.rotates = [ [ [0, 0, 2, 0], [0, 0, 2, 0], [0, 2, 2, 0], [0, 0, 0, 0] ], [ [0, 0, 0, 0], [0, 2, 0, 0], [0, 2, 2, 2], [0, 0, 0, 0] ], [ [0, 0, 0, 0], [0, 2, 2, 0], [0, 2, 0, 0], [0, 2, 0, 0] ], [ [0, 0, 0, 0], [2, 2, 2, 0], [0, 0, 2, 0], [0, 0, 0, 0] ] ]; } Square3.prototype = Square.prototype;
square4.js:
var Square4 = function() { this.square = Square; this.square(); this.rotates = [ [ [0, 2, 2, 0], [0, 2, 2, 0], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [0, 2, 2, 0], [0, 2, 2, 0], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [0, 2, 2, 0], [0, 2, 2, 0], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [0, 2, 2, 0], [0, 2, 2, 0], [0, 0, 0, 0], [0, 0, 0, 0] ] ]; } Square4.prototype = Square.prototype;
square5.js:
var Square5 = function() { this.square = Square; this.square(); this.rotates = [ [ [0, 2, 0, 0], [2, 2, 2, 0], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [2, 0, 0, 0], [2, 2, 0, 0], [2, 0, 0, 0], [0, 0, 0, 0] ], [ [2, 2, 2, 0], [0, 2, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [0, 2, 0, 0], [2, 2, 0, 0], [0, 2, 0, 0], [0, 0, 0, 0] ] ]; } Square5.prototype = Square.prototype;
square6.js:
var Square6 = function() { this.square = Square; this.square(); this.rotates = [ [ [0, 2, 2, 0], [2, 2, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [2, 0, 0, 0], [2, 2, 0, 0], [0, 2, 0, 0], [0, 0, 0, 0] ], [ [0, 2, 2, 0], [2, 2, 0, 0], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [2, 0, 0, 0], [2, 2, 0, 0], [0, 2, 0, 0], [0, 0, 0, 0] ] ]; } Square6.prototype = Square.prototype;
square7.js:
var Square7 = function() { this.square = Square; this.square(); this.rotates = [ [ [2, 2, 0, 0], [0, 2, 2, 0], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [0, 2, 0, 0], [2, 2, 0, 0], [2, 0, 0, 0], [0, 0, 0, 0] ], [ [2, 2, 0, 0], [0, 2, 2, 0], [0, 0, 0, 0], [0, 0, 0, 0] ], [ [0, 2, 0, 0], [2, 2, 0, 0], [2, 0, 0, 0], [0, 0, 0, 0] ] ]; } Square7.prototype = Square.prototype;
在这里,我们完成了所有逻辑,不过文件也变得多起来了,但是结构却清晰很多。首先,我们把方块的所有操作封装在Square中,一些大家都要用的方法封装在prototype中,而每个Square都要使用的属性封装在this中。然后,我们定义了Square1到Square7,从Square继承下来。注意,这里用this.square = Square;this.square();实现了对象继承,用Square1.prototype = Square.prototype;实现了原型链继承,这样,Square的所有东西都继承下来了。而每一个方块特有的东西就是旋转,所以我们为每一个方块定义了旋转矩阵。然后在Game中,我们实现了计分和计时,这部分比较简单,应该很容易看懂。在实现过程中,有一个比较麻烦的地方就是判断数据是否有效,这个操作应该放在Game中还是Square中呢?其实都有点麻烦,因为这个操作涉及到这两个东西,我们这里采用的方法是,将操作放在Square中,然后将Game中的方法作为参数传递过来。个人觉得这样可以比较好地解决这个问题。
在这里,我们最重要的是使用到了js的继承,下面单独拿出来看看。
html:
<!DOCTYPE html> <head> <meta charset="utf-8" /> <title>js的继承</title> </head> <body> <script src="script.js"></script> </body>check the result
script.js:
function Student(name, age) { this.name = name; this.age = age; this.show = function() { document.write('我是' + this.name + ', 我' + this.age + '岁<br/>'); } } function Pupil(name, age) { this.stu = Student; //这里是关键! this.stu(name, age); this.gotoSchool = function() { document.write('我是小学生,我去上学了<br/>'); } } function Middle(name, age) { this.stu = Student; //这里是关键! this.stu(name, age); this.gotoSchool = function() { document.write('我是中学生,我去上学了<br/>'); } } var p = new Pupil('wanmingniu', 10); p.show(); p.gotoSchool(); var m = new Middle('xinxin', 15); m.show(); m.gotoSchool();
js中继承最重要的就是this.stu = Student;this.stu(name, age);了,首先,我们定义一个this.stu,让它等于Student这个function,然后调用它,注意,这时候是以this身份去调用它,然后把它里面的属性全都拿过来了,相当于实现了继承。
这个游戏说实话是有一定难度的,大家可以好好理解。