2091次阅读
俄罗斯方块

要做俄罗斯方块,先来对我们的游戏进行一个规划。首先,游戏要包含一个俄罗斯方块的游戏区,是一个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身份去调用它,然后把它里面的属性全都拿过来了,相当于实现了继承。

这个游戏说实话是有一定难度的,大家可以好好理解。

如果您觉得此教程不错,想支持一下,您可以通过支付宝扫码给我们一点,不要超过100元哦!
评论请先登录