テトリスをJavaScriptで作る 実装編(1)

設計を元に実装していきます。
ソース:tetris.js
ライブラリー:dq-rtg.js,dq-cancas.js(実際に動かすにはライブラリのコアコンポーネントとjQueryも必要です)。
できあがり

状態管理

常にタイマーからの更新要求が飛んでくるので、ゲームの状態を用意して描画を少し管理することにします。

ゲーム状態

Start:
“Push A Button” を表示して、ゲームの開始を待っている状態

Play:
ゲーム進行中、落下中のテトリミノを落としたり、キー操作を画面に反映します。

Pause:
ゲーム進行中ですが、落下などの進行を中断します。

Erase:
ラインの削除演出を表示中です。
テトリミノの落下が抑止されます。

GameOver:
“Game Over” が表示されます。

ライン削除

演出の状態を管理します。
ライン削除演出の状態を管理します。

None:
落下中ではない

Flash:
落下対象のラインを光らせます。

Erase:
落下対象のラインが消えます。

Down:
落下対象のラインを落とします。

実装モデル

設計を元に、クラスを作成します。

実装用に作成したクラスは以下の通りです。

  1. TETRIS:ゲーム全体を管理
  2. Stage: ステージ上のイベントを処理
  3. Line: 各ラインの状態を管理
  4. Tetrimino: 落下中やNextなどのテトリミノを管理
  5. TetriminoFactory: Tetriminoの作成を管理

[TETRIS]+-ステージ[Stage]
[TETRIS]-進行管理[Engine]
[TETRIS]+-current[Tetrimino]
[TETRIS]+-next[Tetrimino]

[Stage]+-[Line]
[Line]+chip[Sprite]

画像

下準備として、ステージやチップ(粒)の画像を用意します。
背景
テトリス背景

粒
ステージ
ステージ
フラッシュ
削除前のフラッシュ

テトリミノの作成

テトリミノは全て4つの粒で構成されています。
それぞれのテトリミノの粒の位置を配列で準備します。
単純なI型は、次の様になります。90度づつ回転するので種別ごとに4つのデータを持ちます。

        [ //I
            [[0, 1], [1, 1], [2, 1], [3, 1]],
            [[1, 0], [1, 1], [1, 2], [1, 3]],
            [[0, 2], [1, 2], [2, 2], [3, 2]],
            [[2, 0], [2, 1], [2, 2], [2, 3]]
        ],

この情報を元にスプライトを作成します。

    create: function (type) {
        var chips = types[type][0]; //指定された型の先頭の粒リストを取得
        var sprites = [];
        for (var i = 0; i < chips.length; i++) {
            sprites.push(
                new DQ.Screen.Canvas.Sprite({
                    image: chipImg.client[0],
                    x: chips[i][0] * DQ.TETRIS.CHIP_SIZE,
                    y: chips[i][1] * DQ.TETRIS.CHIP_SIZE,
                    width: DQ.TETRIS.CHIP_SIZE,
                    height: DQ.TETRIS.CHIP_SIZE,
                    dir: type
                })
            );
        }
        //4つのスプライトを元にグループを作成
        var group = new DQ.Screen.Canvas.SpriteGroup(sprites);
        return new DQ.Tetrimino(type, group);
    }

Spriteは座標とイメージを保持していてCanvasクラスから渡されたコンテキストを元に以下の様な感じで描画しています。

  Sprite.prototype = {
        draw: function (ctx) {
            var x = this.x;
            var y = this.y;
            var w = this.width, h = this.height;
            ctx.drawImage(this.image, 0, this.dir * h, w, h, Math.floor(x), Math.floor(y), w, h);
        }
    }

テトリミノの作成は専用のクラスを用意しておいて、テトリミノ自身からは分離しておきます。createGhost()rotate()は必要に応じて後で説明します。

DQ.TETRIS.TetriminoFactory = {
    DIR: { I: 0, O: 1, S: 2, Z: 3, J: 4, L: 5, T: 6, G: 7 },
    types: [
        [ //I
            [[0, 1], [1, 1], [2, 1], [3, 1]],
            [[1, 0], [1, 1], [1, 2], [1, 3]],
            [[0, 2], [1, 2], [2, 2], [3, 2]],
            [[2, 0], [2, 1], [2, 2], [2, 3]]
        ],
        [ //O
            [[1, 0], [1, 1], [2, 0], [2, 1]],
            [[1, 0], [1, 1], [2, 0], [2, 1]],
            [[1, 0], [1, 1], [2, 0], [2, 1]],
            [[1, 0], [1, 1], [2, 0], [2, 1]]
        ],
        [ //S
            [[0, 1], [1, 1], [1, 0], [2, 0]],
            [[1, 0], [1, 1], [2, 1], [2, 2]],
            [[2, 1], [1, 1], [1, 2], [0, 2]],
            [[0, 0], [0, 1], [1, 1], [1, 2]],
        ],
        [ //Z
            [[0, 0], [1, 0], [1, 1], [2, 1]],
            [[2, 0], [2, 1], [1, 1], [1, 2]],
            [[0, 1], [1, 1], [1, 2], [2, 2]],
            [[1, 0], [1, 1], [0, 1], [0, 2]]
        ],
        [ //J
            [[0, 0], [0, 1], [1, 1], [2, 1]],
            [[1, 0], [1, 1], [1, 2], [2, 0]],
            [[0, 1], [1, 1], [2, 1], [2, 2]],
            [[1, 0], [1, 1], [1, 2], [0, 2]]
        ],
        [ //L
            [[0, 1], [1, 1], [2, 1], [2, 0]],
            [[1, 0], [1, 1], [1, 2], [2, 2]],
            [[0, 1], [1, 1], [2, 1], [0, 2]],
            [[1, 0], [1, 1], [1, 2], [0, 0]],
        ],
        [ //T
            [[0, 1], [1, 1], [2, 1], [1, 0]],
            [[1, 0], [1, 1], [1, 2], [2, 1]],
            [[0, 1], [1, 1], [2, 1], [1, 2]],
            [[1, 0], [1, 1], [1, 2], [0, 1]]
        ]],
    create: function (type) {
        var chips = DQ.TETRIS.TetriminoFactory.types[type][0];
        var sprites = [];
        for (var i = 0; i < chips.length; i++) {
            sprites.push(
                new DQ.Screen.Canvas.Sprite({
                    image: chipImg.client[0],
                    x: chips[i][0] * DQ.TETRIS.CHIP_SIZE,
                    y: chips[i][1] * DQ.TETRIS.CHIP_SIZE,
                    width: DQ.TETRIS.CHIP_SIZE,
                    height: DQ.TETRIS.CHIP_SIZE,
                    dir: type,
                    animation: false,
                    numberOfPause: 0
                })
            );
        }
        var group = new DQ.Screen.Canvas.SpriteGroup(sprites);
        return new DQ.Tetrimino(type, group);
    },
    createGhost: function (mino) {
        var chips = DQ.TETRIS.TetriminoFactory.types[mino.type][0];
        var sprites = [];
        for (var i = 0; i < chips.length; i++) {
            sprites.push(
                new DQ.Screen.Canvas.Sprite({
                    image: chipImg.client[0],
                    x: chips[i][0] * DQ.TETRIS.CHIP_SIZE,
                    y: chips[i][1] * DQ.TETRIS.CHIP_SIZE,
                    width: DQ.TETRIS.CHIP_SIZE,
                    height: DQ.TETRIS.CHIP_SIZE,
                    dir: DQ.TETRIS.TetriminoFactory.DIR.G,
                    animation: false,
                    numberOfPause: 0
                })
            );
        }
        var group = new DQ.Screen.Canvas.SpriteGroup(sprites);
        return new DQ.Tetrimino(mino.type, group);
    },
    rotate: function (mino, dir, ndir) {
        var pre = DQ.TETRIS.TetriminoFactory.types[mino.type][dir];
        var pos = DQ.TETRIS.TetriminoFactory.types[mino.type][ndir];
        for (var i = 0; i < mino._group._member.length; i++) {
            var sp = mino._group._member[i];
            var cx = sp.x - pre[i][0] * DQ.TETRIS.CHIP_SIZE;
            var cy = sp.y - pre[i][1] * DQ.TETRIS.CHIP_SIZE;
            cx += pos[i][0] * DQ.TETRIS.CHIP_SIZE;
            cy += pos[i][1] * DQ.TETRIS.CHIP_SIZE;

            sp.x = cx;
            sp.y = cy;
        }
    }
}

続く

M. K. の紹介

IT屋さんです。プログラミングが大好きで今はJavascriptがお気に入りです。
カテゴリー: JavaScript, ゲーム作成, プログラミング   タグ: ,   この投稿のパーマリンク