読者です 読者をやめる 読者になる 読者になる

自習室

こもります

HTML + CSS + JavaScript でテトリスを作った話

はじめに

前の記事3件にも立て続けに書きましたが、仕事でWeb系の技術を使うことが増えたので、経験値貯めるために家でも何かしようと思って始めました。

こちらで動いています

操作法(テトリスのフレームを選択中は操作できます)

  • r 右回転 / shift + r 左回転
  • 左矢印(←) 左移動 / 右矢印(→) 右移動 / 下矢印(↓) 落下
  • ゲームオーバー時:Enter ゲーム再開

↓キーで画面も動いてしまうので、別ウィンドウで開く場合はこちらへどうぞ

* TETRIS *

仕様

作り込むとかなり時間を要しそうだったので、ある程度仕様を制限して作っています。

機能面

最低限の機能を実装する。これと言って特殊なことはしない

  • ブロックが積まれること
  • ちゃんと回せること
  • ちゃんと移動できること
  • 行が埋まったら消えること
  • 消えたら上からブロックが詰まること
  • 天井まで積み上がってしまったらゲームオーバーになること
  • ゲームオーバーになったら、ゲームを再開できること

ちゃんと移動、ちゃんと回転、と言うのが意外と難しかったです。動作のパタンを分析していくと、特殊な移動を要するケースとして以下の場合があることが分かりました

  • 壁際での回転した際に、壁にめり込まないよう内側にシフトする
  • 床面すれすれで回転した際に、床にめり込まないよう上方向にシフトする

この辺への対応はアルゴリズムを考えました。(ソースコード参照)

サポートしない機能

全部作り込むときりが無いので、以下の内容は今回は作っていません。

  • 得点計算
  • 次に出てくるテトリスを予告する機能
  • 時間が経つにつれレベルが上がる(出てくるインターバルや落下速度が速くなる)機能
  • 消えるときのカッコいいアクション(本当は作りたい)

アルゴリズムを調べない

これは個人的にゲーム作りを楽しむために課したルールなのですが、アルゴリズムは自分で考えるようにしました。具体的には、先に書いた「壁際での回転時や、床面での回転」などです。

テトリス アルゴリズム などで検索すると、いろいろ出てきますが、改めて今見てみたところ、そこまで異なったことはしていないようで安心しました。

実装

HTML + CSS + JavaScript の総合的な勉強、という目的の下、今回は テーブルタグでどうにかする、を主方針として実装しました。

テーブルタグでどうにかする

  • <table><tr><th> 要素をグリッドとする
  • classを差し替えて <th> 要素の背景を塗り替える
  • 不可視の <th> 要素をステージ周囲において .wall クラスを与え、壁や床として当たり判定を行う
  • 落ちて動かなくなったブロックを表す <th> 要素に .inactive クラスを与え、当たり判定を行う
  • <tr> を削除することで行が消えて残ったブロックが下に詰められ、新たに頭から <tr> を加えることで、行数のつじつまを合わせる

f:id:AMANE:20140629163542p:plain

処理のループ

ゲームを進める主要なループは以下の様になっています。特殊エフェクトのためのアニメーション等を入れていないので、非常にシンプルです。setTimeout()loop() を繰り返します

var fallProcess = function() {
  if( block.fallJudge() ) { // 落ちられるなら
    block.fall();           // 落ちます
  }  else {
    block.fix();            // 落ちられないなら、ブロックを固定します
    eraseJudge( block );    // 消える行ありますか?消します。詰めます。
    if( block.gameOverJudge() ) { // ブロックが落ちないと言うことは、もしかしてゲームオーバーですか?
      return false;         // fallProcess終了のおしらせ
    }
    block.initialize();     // ブロックを新たに生成(リセット)
  }
  return true;
}

var loop = function () {
  if( fallProcess() ) {           // ゲームが続いている間は
    loopID = setTimeout(loop, 1000);  // ループ継続
  } else {
    loopID = null;                // 終わっていたらloop継続をせず
    gameOverDisplay();            // 終了画面を出す
  }
};
loopID = setTimeout(loop, 1000);  // 初回のゲーム開始

最後に

コードは上げてあります。気まぐれに参考にしたい、なんて方がいらっしゃったら是非ともどうぞ

izmhr/tetris · GitHubizmhr/tetris · GitHub

既存のゲームのコピーだし、アルゴリズムも特段こだわりがあるわけでもないので、この記事はこの程度でとどめておきます。

しかし、このくらいなら1週間程度で自力で作れるようになってきたので、少しはJavaScript力ついたかなぁ、と少し嬉しく感じております。