さぁ!検索しよう!

数回に分けて公開している、HTML5+JavaScriptによるテトリスのソースコードを解読するシリーズの4です。

参考にした記事

【JavaScript】200行で作るテトリスのレシピ【HTML5】 – コードレシピ

※自分なりの解釈ですので、正確ではないかもしれません。

今回は、その方向へ操作ブロックを移動できるかどうかを返すvalid()関数を解読します。

valid()関数とは

valid()関数は、キーボードが押されたときに呼び出されるkeyPress()関数と、指定した秒数毎に呼び出されるtick()関数に呼び出される関数です。

// 指定された方向に、操作ブロックを動かせるかどうかチェックする
// ゲームオーバー判定もここで行う
function valid( offsetX, offsetY, newCurrent ) {
  offsetX = offsetX || 0;
  offsetY = offsetY || 0;
  offsetX = currentX + offsetX;
  offsetY = currentY + offsetY;
  newCurrent = newCurrent || current;
  for ( var y = 0; y < 4; ++y ) {
    for ( var x = 0; x < 4; ++x ) {
      if ( newCurrent[ y ][ x ] ) {
        if ( typeof board[ y + offsetY ] == 'undefined'
             || typeof board[ y + offsetY ][ x + offsetX ] == 'undefined'
             || board[ y + offsetY ][ x + offsetX ]
             || x + offsetX < 0
             || y + offsetY >= ROWS
             || x + offsetX >= COLS ) {
               if (offsetY == 1 && offsetX-currentX == 0 && offsetY-currentY == 1){
                 console.log('game over');
                 lose = true; // もし操作ブロックが盤面の上にあったらゲームオーバーにする
               }
               return false;
             }
      }
    }
  }
  return true;
}

https://github.com/ottati/canvas-tetris/blob/master/js/tetris.js

基本的には現在の操作ブロック(current)が引数(offsetX,offsetY)に渡された値の方向へ移動できるかどうかを判定します。

ですが、newCurrentに値が渡されると、newCurrentが引数(offsetX,offsetY)に渡された値の方向へ移動できるかどうかを判定します。newCurrent || current;とすることで、newCurrentに値が何も渡されなかったときにcurrentを代わりに渡すことができます。

処理内容

続いて、valid()関数内でどのようなことが行われているのかを小割りにして解読します。

1. newCurrent[0][0]が1以上だったら

if ( newCurrent[ y ][ x ] ) {

newCurrent[0][0]が1以上(操作ブロック内の色が付いているマス)だったら、次のif文(2.)に進みます。反対に0(色が付いていないマス)であれば処理を抜け、newCurrent[0][1]へ移ります。

2. 1.がtrueで縦方向へのブロックの移動先が盤面の範囲外だったら

if ( typeof board[ y + offsetY ] == 'undefined'

縦方向へのブロックの移動先([y+offsetY])が盤面の範囲外(20番目以降の行)だったら次のif文(3.)に進みます。盤面内は配列で定義されていますが、盤面外は何も定義されていませんよね?そういうことです。

3. 2.又は横方向へのブロックの移動先が盤面の範囲外だったら

|| typeof board[ y + offsetY ][ x + offsetX ] == 'undefined'

2.又は横方向へのブロックの移動先([y+offsetY][x+offsetX])が盤面の範囲外(左端を越えるor右端を越える)だったら次のif文(4.)に進みます。

4. 2.又は移動先のマス内が0ではなく1だったら

|| board[ y + offsetY ][ x + offsetX ]

2.又は移動先のマス内が0ではなく1だったら、そこには既にブロックがあるということなので、次のif文(5.)に進みます。

5. 2.又は横方向の移動先が盤面の左端を越えたら

|| x + offsetX < 0

2.又は横方向の移動先が盤面の左端を越えたら次のif文(6.)に進みます。

6. 2.又は縦方向の移動先が盤面の下端を越えるか、色つきブロックが19番目の行に到達すれば

|| y + offsetY >= ROWS

2.又は縦方向の移動先が盤面の下端を越えるか、色つきブロックが19番目の行に到達すれば、次のif文(7.)に進みます。

7. 2.又は横方向の移動先が盤面の右端を越えるか、色つきブロックがy+offsetY行目の9番目のマスに到達すれば

|| x + offsetX >= COLS ) {

2.又は横方向の移動先が盤面の右端を越えるか、色つきブロックがy+offsetY行目の9番目のマスに到達すれば、次のif文(8.)に進みます。

8. 2.~7.のいずれか1つがtrueで移動量が1且つ横方向への移動が出来なくなった且つ横方向への移動が出来なくなったら

if (offsetY == 1 && offsetX-currentX == 0 && offsetY-currentY == 1){
        console.log('game over');
            lose = true; // もし操作ブロックが盤面の上にあったらゲームオーバーにする
    }
    return false;

移動量(offsetY)が1且つ横方向への移動が出来なくなった(offsetX-currentX==0)且つ横方向への移動が出来なくなった(offsetX-currentX==0)らゲームオーバーとなり、losetrueに切り替わります。
どの条件も満たさなければreturn false;となります。

9. ブロックを固定→揃った行を消去→操作ブロックを盤面上部にセット

// newGameで指定した秒数毎に呼び出される関数。
// 操作ブロックを下の方へ動かし、
// 操作ブロックが着地したら消去処理、ゲームオーバー判定を行う
function tick() {
    // 1つ下へ移動する
    if ( valid( 0, 1 ) ) {
      ++currentY;
    }
    // もし着地していたら(1つしたにブロックがあったら)
    else {
      freeze();  // 操作ブロックを盤面へ固定する
      clearLines();  // ライン消去処理
      if (lose) {
        // もしゲームオーバなら最初から始める
        newGame();
        return false;
      }
      // 新しい操作ブロックをセットする
      newShape();
    }
}

https://github.com/ottati/canvas-tetris/blob/master/js/tetris.js

8.の後、tick()のelse文が実行され、freeze()でブロックを固定、clearLines()で揃った行を消去、newShape()で新たに盤面上部に操作ブロックをセットします。8.がreturn true;の場合のみif(lose){...}が実行されます。

10. 移動先にブロックがなかったり、盤面の両端に当たらなければ

移動先にブロックがなかったり、盤面の両端に当たらなければreturn true;のまま操作ブロックが落下しながら移動します。

以上で、【HTML5+JavaScript】テトリスのコード内にある関数を解読4を終わります。