LoginSignup
2
1

More than 5 years have passed since last update.

TETRISを作りました①

Last updated at Posted at 2018-08-22

記念すべき1記事目。インターンの事前課題でTETRISをつくったのでご紹介。

できたもの

https://verdy89.github.io/tetris/
現段階ではスマホ非対応です。PCで遊んでください。
ソースコードは
https://github.com/verdy89/tetris
です。

実装

参考記事

事前課題の教科書代わりとして、以下のブログを紹介してもらいました。これを読めば大体の実装は終わりです。
http://tech.mti.co.jp/entry/2017/07/21/573/
http://tech.mti.co.jp/entry/2017/07/21/608/
http://tech.mti.co.jp/entry/2017/07/21/663/
http://tech.mti.co.jp/entry/2017/07/21/768/

以下ではブログに書いてなかった部分の紹介をします。
(どの部分がブログに書いてないか検証するのがめんどいので、この部分教えてとかあればリクエストください)

回転

多分一番難しいのが回転です。回転までできるとなんとか遊べるようになります。
回転を実現しているのがrotate()という関数です。
実装の方針としては、

  1. ブロックの形に応じて回転に必要な分の空間が確保されているかを確認する
  2. その空間内のセルを書き換える

という感じです。順にみていきます。


function rotate() { // ブロックを回転させる
    // 1. ブロックの回転に関係する範囲を決める (blockRange ** 2 の範囲)
    var blockRange = 0;
    var initRow, initCol;
    var blockClass;
    for (var row = 0; row < 20; row++) {
        for (var col = 0; col < 10; col++) {
            if (cells[row][col].blockNum === fallingBlockNum) {
                if (cells[row][col].className === "o") {
                    return; // 四角は回転できない
                } else if (cells[row][col].className === "i") {
                    blockRange = 4;
                } else {
                    blockRange = 3;
                }
                blockClass = cells[row][col].className
                break;
            }
        }
    }
    // 下に続く

blockRange は、回転に必要な空間(正方形)の一辺の長さです。className"o"である「黄色い四角」は回転できない(回転する意味がない)ので、四角が落ちているときにrotate()を実行しても何も起こらないようにしました。
className"i"である「水色の棒」は4*4の空間が必要ですが、それ以外の形は3*3で済みます。

    // 2. 起点となるセルに移動する
    for (var row = 0; row < 20; row++) {
        for (var col = 0; col < 10; col++) {
            if (cells[row][col].base) {
                initRow = row;
                initCol = col;
                break;
            }
        }
    }
    // 下に続く

ブロックを配置するところ(function generateNewBlock()2.)でcells[0][3].base = true;としてあって、ブロックとともに回転の基点も移動するようにしてあります。「回転の基点」というのは、上で述べた「回転に必要な空間」の左上のセルのことです。


    // 3. 範囲内に別のブロックが存在しないか確認する
    for (var i = 0; i < blockRange; i++) {
        for (var j = 0; j < blockRange; j++) {
            if (cells[initRow+i][initCol+j].className !== "" && cells[initRow+i][initCol+j].blockNum !== fallingBlockNum) {
                return; // 回転不可
            }
        }
    }

    // 4. ブロックを回転させる
    var rotetedBlockClass;
    if (blockRange === 3) {
        rotetedBlockClass = [["","",""],["","",""],["","",""]]
    } else if (blockRange === 4) {
        rotetedBlockClass = [["","","",""],["","","",""],["","","",""],["","","",""]]
    }
    for (var i = 0; i < blockRange; i++) {
        for (var j = 0; j < blockRange; j++) {
            rotetedBlockClass[j][blockRange-1-i] = cells[initRow+i][initCol+j].className;
        }
    }
    for (var i = 0; i < blockRange; i++) {
        for (var j = 0; j < blockRange; j++) {
            cells[initRow+i][initCol+j].blockNum = null;
            cells[initRow+i][initCol+j].className = rotetedBlockClass[i][j];
            //cells[initRow+i][initCol+j].textContent = "c"
            if (rotetedBlockClass[i][j] !== "") {
                cells[initRow+i][initCol+j].blockNum = fallingBlockNum;
            }
        }
    }   
}

あとはその空間内に別のブロックが存在するかチェックして、なければセルのクラスを書き換えるだけです。

ブロックの生成

ブロックを生成するときに、ブログでは完全にランダムに生成してますが、どうやら本家TETRISは完全にランダムじゃないらしいのです。初耳。
確かに完全にランダムでブロックを生成すると、たまに同じ形のブロックが3連続で落ちてきて厳しいんですね。
どうやら[7種類の順列][7種類の順列][7種類の順列]……という形で順番を決めているらしいので、これを実装しました。

var blockArray = permulation([0, 1, 2, 3, 4, 5, 6]); // ブロックの出てくる順番

function generateNewBlock() { // ランダムにブロックを生成する・一部のみ
    // 1. ブロックパターンからランダムに一つパターンを選ぶ
    var keys = Object.keys(blocks);
    //var nextBlockKey = keys[Math.floor(Math.random() * keys.length)]; // 完全にランダム(これだと同じブロックが何度も連続して出てくることがある)
    var nextFallingBlockNum = fallingBlockNum + 1;
    if (nextFallingBlockNum % keys.length == 0) {
        blockArray = permulation([0, 1, 2, 3, 4, 5, 6]);
    }
    //var nextBlockKey = (nextFallingBlockNum % blockArray.length == 0) ? keys[blockArray[blockArray.length - 1]] : keys[blockArray[nextFallingBlockNum % blockArray.length - 1]];
    if (nextFallingBlockNum % blockArray.length == 0) {
        var nextBlockKey = keys[blockArray[blockArray.length - 1]];
    } else {
        var nextBlockKey = keys[blockArray[nextFallingBlockNum % blockArray.length - 1]];
    }
    var nextBlock = blocks[nextBlockKey];
    // 次の次のブロックの表示
    var nextnextBlockKey = keys[blockArray[nextFallingBlockNum % blockArray.length]];
    var nextnextBlock = blocks[nextnextBlockKey];

function permulation(array) { // ランダムに並び替えた順列の作成
    var answer = [];
    var n = array.length;
    for (var i = 0; i < n; i++) {
        pushKey = Math.floor(Math.random() * array.length);
        answer.push(array[pushKey]);
        array.splice(pushKey, 1);
    }
    return answer;
}

jsは「順列組み合わせ」を生成する関数がないらしいので、function permulation(array)を作りました。

今後

回転まで実装した段階でTwitterで投稿したらそこそこ反響がありまして、いろいろなアドバイスをいただくことができました。ありがとうございます。ブロックの生成は完全にランダムではないというのもそこで教えてもらいました。
今後実装していきたいのは、

  • HOLD機能
  • 次のブロックの表示
  • スマホ対応
  • ランキング表示
  • デザインの改修(CSSむずい)
    • 画面の大きさに対応できるようにしたい
  • 得点を最大化するようにキー操作を最適化するAIもどき(??)

くらいでしょうか。まとまったアップデートがあったらまたブログを更新します。

おまけ(といいつつ重要かも)

「"TETRIS"は商標なので、そのまま公開するのはよくないのでは」というアドバイスをいただきましたが、
http://www.techvisor.jp/blog/archives/489
によれば

1.商標権は特定の言葉や図形を「商売で」かつ「商品・サービスの出所を表わす」ために独占的に使える権利

だそうなので、商売ではない(お金のやり取りをしていない)のでおそらく大丈夫かなーと思っています(商標法第37条を読んだけどよくわかりませんでした)。この分野に明るい方、教えてください。

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1