<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>仮ぶろぐ &#187; Tetris</title>
	<atom:link href="http://plusb.jp/blog/?feed=rss2&#038;tag=tetris" rel="self" type="application/rss+xml" />
	<link>http://plusb.jp/blog</link>
	<description>今更なことをそれでもつらづらと書くブログ</description>
	<lastBuildDate>Tue, 07 May 2013 09:06:48 +0000</lastBuildDate>
	<language>ja</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.5.1</generator>
		<item>
		<title>テトリスをJavascriptで作る 実装編(3)</title>
		<link>http://plusb.jp/blog/?p=760</link>
		<comments>http://plusb.jp/blog/?p=760#comments</comments>
		<pubDate>Mon, 08 Aug 2011 08:26:52 +0000</pubDate>
		<dc:creator>M. K.</dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[ゲーム作成]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[Tetris]]></category>

		<guid isPermaLink="false">http://plusb.jp/blog/?p=760</guid>
		<description><![CDATA[実装編　その３です。 ステージやラインについて解説します。 その１ その２ ソース:tetris.js ライブラリー:dq-rtg.js,dq-cancas.js(実際に動かすにはライブラリのコアコンポーネントとjQue &#8230; <a href="http://plusb.jp/blog/?p=760">続きを読む <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>実装編　その３です。<br />
ステージやラインについて解説します。<br />
<a href="http://plusb.jp/blog/?p=699">その１</a><br />
<a href="http://plusb.jp/blog/?p=709">その２</a><br />
ソース:<a href="http://plusb.jp/dq/samples/js/tetris.js">tetris.js</a><br />
ライブラリー:<a href="http://plusb.jp/dq/js/rtg/dq-rtg.js">dq-rtg.js</a>,<a href="http://plusb.jp/dq/js/dq-canvas.js">dq-cancas.js</a>(実際に動かすにはライブラリのコアコンポーネントとjQueryも必要です）。<br />
<a href="http://plusb.jp/blog/?p=694">できあがり</a><br />
<span id="more-760"></span></p>
<h3>TETRIS.Stageクラス</h3>
<p>ステージは22本(*1)のラインがあり、ラインは10個の粒（ブロック）で構成されています。<br />
簡単に言えば縦22x横10個の粒で構成されていることになります。<br />
*1 ゲームとして有効なのは20ラインですが、テトリミノが落下する位置として2ライン分追加しています。</p>
<p>ステージの役割は粒の管理で、ラインの削除判定や削除中のラインの状態を管理します。</p>
<h4>初期化</h4>
<p>ラインを作成します。</p>
<pre>
DQ.TETRIS.Stage = function (engine) {
    this._engine = engine;
    this.lines = [];
    for (var i = 0; i &lt; 22; i++) {
        this.lines.push(new DQ.TETRIS.Line(i));
    }
}
</pre>
<p>ゲーム開始時にライン上のチップを削除することで初期化します。</p>
<pre>
DQ.TETRIS.Stage.prototype = {
    left: DQ.TETRIS.STAGE_LEFT,
    top: -14,
    lines: [],
    clear: function () {
        for (var ln = 0; ln < this.lines.length; ln++) {
            for (var i = 0; i &lt; 10; i++) {
                if (this.lines[ln].chips[i]) {
                    this.lines[ln].chips[i].remove();
                    this.lines[ln].chips[i] = null;
                }
            }
        }
    }
}
</pre>
<h4>当たり判定</h4>
<p>Stageクラスが落下中のテトリミノがステージの壁やステージ上の粒にぶつかるかどうかの当たり判定を担当します。</p>
<pre>
    hitTest: function (mino, dx, dy) {
        ///&lt;summary&gt;
        ///指定のテトリミノの当たり判定をします。
        ///&lt;/summary&gt;
        ///&lt;param name="mino" type="Tetrimino"&gt;
        ///対象となるテトリミノを指定
        ///&lt;/param&gt;
        ///&lt;param name="dx", type="number"&gt;
        ///x方向の移動予定幅を指定
        ///&lt;/param&gt;
        ///&lt;param name="dy", type="number"&gt;
        ///y方向の移動予定幅を指定
        ///&lt;/param&gt;

        for (var i = 0; i &lt; 4; i++) {
            var chip = mino.chip(i);
            var cx = Math.floor((chip.x + dx - this.left) / DQ.TETRIS.CHIP_SIZE);
            var cy = Math.floor((chip.y + dy - this.top) / DQ.TETRIS.CHIP_SIZE);
            //wall
            if (cx < 0
                || 10 &lt;= cx
                || 22 &lt;= cy) {
                return true;
            }
            //chip
            if (this.lines[cy].chips[cx]) {
                return true;
            }
        }

        return false;
    }
}
</pre>
<p>落下中のテトリミノを構成する４つの粒に対して壁とステージ上の粒との当たり判定を実施します。<br />
ステージ上の粒の有無は、対象となる座標に粒のインスタンスがあるかどうかで判定しています。<br />
横移動の場合、<code>hitTest()</code>で<code>false</code>が返ると移動できません。<br />
落下処理の場合は、「遊び」状態へ遷移します。「遊び」状態が放置されると落下中のテトリミノはただの粒へと変化します。</p>
<h4>粒への移行</h4>
<p>落下が完了したテトリミノをラインへ粒として登録します。</p>
<pre>
{
    transChip: function (mino) {
        ///&lt;summary&gt;
        ///落下中だったテトリミノをチップへ変換します。
        ///&lt;/summary&gt;
        ///&lt;param name="mino" type="Tetrimino"&gt;
        ///変換するテトリミノを指定
        ///&lt;/param&gt;

        for (var i = 0; i &lt; 4; i++) {
            var chip = mino.chip(i);
            var cx = Math.floor((chip.x - this.left) / DQ.TETRIS.CHIP_SIZE);
            var ln = Math.floor((chip.y - this.top) / DQ.TETRIS.CHIP_SIZE);
            this.lines[ln].chips[cx] = new DQ.Screen.Canvas.Sprite({
                image: chipImg.client[0],
                x: cx * DQ.TETRIS.CHIP_SIZE + this.left,
                y: ln * DQ.TETRIS.CHIP_SIZE + this.top,
                width: DQ.TETRIS.CHIP_SIZE,
                height: DQ.TETRIS.CHIP_SIZE,
                dir: mino.type,
                animation: false,
                numberOfPause: 0
            });
            this._engine._canvas.push(this.lines[ln].chips[cx]);
        }
    }
}
</pre>
<p>テトリミノを構成する４つの粒を該当する座標の粒として登録します。<br />
粒のインスタンスは描画用の<code>Sprite</code>です。スプライトを作成する際には、座標系を粒から画面のpixel単位へ変換しています。<br />
作成したSpriteは、キャンバスへ登録します（これによって、粒の描画はキャンバス(*2)が適宜実行してくれます)。<br />
*2 念のためですが、このキャンバスは拙作のライブラリのキャンバスクラスの事です。</p>
<h4>削除判定</h4>
<p>ライン上に新しい粒が登録された後は、ラインの削除判定を実施します。</p>
<pre>
{
    eraseLine: function () {
        this._lineFlash = [];
        for (var i = 21; 2 <= i; i--) {
            if (this.lines[i].canErase()) {
                //削除可能なラインがあったのでフラッシュ状態へ移行
                this.mode = DQ.TETRIS.StageMode.Flash;
                this._elapse = 0;
                var line = new DQ.Screen.Canvas.Sprite({
                    y: i * DQ.TETRIS.CHIP_SIZE + this.top,
                    x: this.left,
                    width: DQ.TETRIS.CHIP_SIZE * 10,
                    height: DQ.TETRIS.CHIP_SIZE,
                    animation: false,
                    numberOfPause: 0,
                    image: flashImg.client[0]
                });
                this._lineFlash.push(line);
                engine._canvas.push(line);
            }
        }
        return this._lineFlash.length;
    },
</pre>
<p>全てのラインについて削除判定を行います。判定自体はラインクラスに任せていますが、ようはライン内の全ての領域に粒があれば削除可能です。<br />
削除可能なら画面効果用のスプライトを登録しています。<br />
この時点で、ゲーム全体の状態はゲーム中(Play)状態から削除中(Erase)状態へ遷移します。<br />
また、画面効果として削除可能なラインがフラッシュして、しばらくしてからラインが消え、またしばらくしてから全体が落下するようになっています。<br />
ちなみに、メソッドの名前が微妙だったりしますが、そこは目をつぶります。</p>
<h4>状態管理</h4>
<p>Stageクラスではラインが削除中の場合だけ状態を管理します。状態はUpdate()の呼び出しによる一定の時間経過後に遷移します。</p>
<pre>
{
    update: function () {
        if (this.mode == DQ.TETRIS.StageMode.None) {
            return;
        }
        this._elapse += 60 / TETRIS.fps;
        if (this.wait <= this._elapse) {
            //時間経過を超えた
            switch (this.mode) {
                case DQ.TETRIS.StageMode.Flash:
                    //フラッシュ中から削除中へ移行
                    this.mode = DQ.TETRIS.StageMode.Erase;
                    this._elapse = 0;
                    for (var i = 21; 2 <= i; i--) {
                        if (this.lines[i].canErase()) {
                            this.lines[i].clear();
                        }
                    }
                    for (var i = 0; i &lt; this._lineFlash.length; i++) {
                        this._lineFlash[i].remove();
                    }
                    this._lineFlash = [];
                    break;
                case DQ.TETRIS.StageMode.Erase:
                    //削除中から通常へ移行
                    this.mode = DQ.TETRIS.StageMode.None;
                    this._elapse = 0;
                    var count = 0;
                    for (var i = 21; 2 <= i; i--) {
                        if (this.lines[i].canDown()) {
                            this.lines.splice(i, 1);
                            count++;
                        }
                    }
                    //削除した分だけラインを追加
                    for (i = 0; i &lt; count; i++) {
                        this.lines.unshift(new DQ.TETRIS.Line(0));
                    }
                    //ライン番号を振り直す
                    for (i = 0; i &lt; 22; i++) {
                        this.lines[i].line(i);
                    }
                    break;
            }
        }
    }
}
</pre>
<p>ステージの状態は<code>None</code>→<code>Flash</code>→<code>Erase</code>→<code>None</code>へと遷移します。<br />
状態遷移後に経過時間を示す<code>_elapse</code>がクリアされ、一定時間経過後にイベント処理が実施されます。<br />
まず、Flash状態にある場合は削除可能なラインをクリアします。それからフラッシュ効果用のスプライトを削除(remove)します。<br />
（スプライトをremove()するとキャンバスクラスからも削除され、描画の対象から外れます。）<br />
次に削除状態にある場合は、ラインを落下させます。<br />
落下処理については、削除可能なラインを配列から削除して、削除した分のラインを配列の先頭に追加することで落下したように見せています。<br />
余談ですが、ぷよぷよなど粒自体が落ちていく場合にはこの手法は使えないですね。</p>
<h4>全体の状態遷移</h4>
<p><a href="http://plusb.jp/blog/?p=709">その２</a>では、割愛していた全体の状態遷移の管理です。<br />
updateメソッド中で、状態毎のチェックを実施しています。</p>
<pre>
    update: function () {
        var mode = DQ.TETRIS.Mode;
        switch (this.game) {
            case mode.Start:
            case mode.GameOver:
                return;
            case mode.Erase:
                //ラインの削除演出中
                this.stage.update();
                if (this.stage.mode == DQ.TETRIS.StageMode.None) {
                    //演出完了
                    this.game = DQ.TETRIS.Mode.Play;
                    //落下したラインに合わせてゴーストを移動
                    this.moveGhost();
                }
                break;
            case mode.Delay:
                //「遊び」中
                this._elapse += 60 / this.fps;
                if (this.wait <= this._elapse) {
                    this.game = DQ.TETRIS.Mode.Play;
                    this.stage.transChip(this.current);
                    this.current.remove();
                    this.shiftCurrent();
                    soundBox.play(1);

                    var n_line = this.stage.eraseLine();
                    if (n_line > 0) {
                        this.score.score += DQ.TETRIS.Game.ScoreTable[n_line];
                        this.score.line += n_line;
                        var pre = this.score.level;
                        this.score.level = Math.floor(this.score.line / 10) + 1;
                        if (pre != this.score.level) {
                            this.levelUp();
                        }
                        this.updateScore();

                        this.game = DQ.TETRIS.Mode.Erase;
                        return;
                    }
                }
                break;
            case mode.Play:
                //「落下中」
                if (this.isAccelerate()) {
                    if (this.current.y - this._startPos > DQ.TETRIS.CHIP_SIZE) {
                        this.score.score++;
                        this.updateScore();
                        this._startPos += DQ.TETRIS.CHIP_SIZE;
                    }
                }
                var dy = this.gdy;
                if (this.hitTest(0, dy + DQ.TETRIS.CHIP_SIZE)) {
                    //「遊び」に移行
                    this.game = DQ.TETRIS.Mode.Delay;
                    this._elapse = 0;
                    this.stopAccelerate();
                    this.current.move(0, dy);
                    this.current.fit();
                } else {
                    this.current.move(0, dy);

                }
                break;
        }

    }
}
</pre>
<p>ゲーム全体の状態はゲーム開始直後(StartGame)、ゲームオーバー(GameOver)、ゲーム中(Play)、遊び中(Delay)および削除中(Erase)です。<br />
ゲーム開始直後およびゲームオーバー状態はキー操作があるまでやることも状態遷移もありません。<br />
次に基本となるゲーム状態ですが、ここについては前回説明しているので割愛します。<br />
遊び状態は一定時間が経過するとステージへ定着(transChip)します。それからラインの削除判定を実施し、削除可能なラインがなければゲーム中状態へ遷移し、削除可能なラインがあれば削除中へ遷移します。<br />
また、削除可能なラインがあった場合はスコアーの計算やレベルアップの判定も実施していますが、説明は割愛します。<br />
削除中状態ではステージに状態管理を委譲しています。これは、削除中の演出の管理をStageクラスへ局所化する意図があります。</p>
<p>次回は、ゴーストやハードドロップについてです。</p>
]]></content:encoded>
			<wfw:commentRss>http://plusb.jp/blog/?feed=rss2&#038;p=760</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>テトリスをJavaScriptで作る 実装編(2)</title>
		<link>http://plusb.jp/blog/?p=709</link>
		<comments>http://plusb.jp/blog/?p=709#comments</comments>
		<pubDate>Sat, 23 Jul 2011 03:25:59 +0000</pubDate>
		<dc:creator>M. K.</dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[ゲーム作成]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[Tetris]]></category>

		<guid isPermaLink="false">http://plusb.jp/blog/?p=709</guid>
		<description><![CDATA[実装編　その２です。 描画周りやTETRISクラスの説明です。 その１ ソース:tetris.js ライブラリー:dq-rtg.js,dq-cancas.js(実際に動かすにはライブラリのコアコンポーネントとjQuery &#8230; <a href="http://plusb.jp/blog/?p=709">続きを読む <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>実装編　その２です。<br />
描画周りやTETRISクラスの説明です。<br />
<a href="http://plusb.jp/blog/?p=699">その１</a><br />
ソース:<a href="http://plusb.jp/dq/samples/js/tetris.js">tetris.js</a><br />
ライブラリー:<a href="http://plusb.jp/dq/js/rtg/dq-rtg.js">dq-rtg.js</a>,<a href="http://plusb.jp/dq/js/dq-canvas.js">dq-cancas.js</a>(実際に動かすにはライブラリのコアコンポーネントとjQueryも必要です）。<br />
<a href="http://plusb.jp/blog/?p=694">できあがり</a><br />
<span id="more-709"></span></p>
<h3>描画</h3>
<h4>イメージの読み込み</h4>
<p>canvasのdrawImageに引き渡すimgオブジェクトですが、描画する前に読み込みが完了している必要があります。<br />
そのため、ゲームエンジンを起動する前に全ての画像の読み込みの完了(loadイベントを使用）をチェックします。</p>
<pre>
var count = 0;
function image_loaded(sender) {
   count++;
   if(count == 4) {
       TETRIS = new DQ.TETRIS(engine);
       TETRIS.initialize();

       //engineはタイマーやキーイベントを管理
       engine.start();
   }
}

stageImg = new <a href="http://plusb.jp/blog/?p=480">DQ.Image</a>(DQ.page(), {
   width: 320,
   height: 320,
   src: "default/images/stage.png",
   onLoaded: image_loaded
});

...
</pre>
<h4>canvasへの描画</h4>
<p>前回も少し触れていますがcanvasへの描画はキャンバスクラスが担当していますので、その仕組みを説明しておきます。<br />
キャンバス内では、指定されたFPSになるようにインターバルタイマーが発生して<code>update</code>メソッドを呼び出されます。<br />
<code>update</code>メソッド内では、背景から順番にイメージを都度描画しています。</p>
<pre>
update: function () {
    if (this.drawing) {
        return;
    }
    this.drawing = true;
    var ctx = this.obj0.getContext("2d"),
        sort_compare = function (o1, o2) {
             return o1.zIndex - o2.zInde;
    }

    //準備
    this._sprites.sort(sort_compare);

    //描画
    !this._bg &#038;&#038; ctx.clearRect(0, 0, this._size.width, this._size.height);

    //背景描画
    this._bg &#038;&#038; this._bg.draw(ctx, this._size);

    //スプライト描画
    for (var i = 0; i < this._sprites.length; i++) {
        this._sprites[i].draw(ctx, this._size);
    }

    //前景描画
    this._fg &#038;&#038; this._fg.draw(ctx, this._size);

    //情報レイヤー描画
    this._ig &#038;&#038; this._ig.draw(ctx, this._size);

    this.drawing = false;
},
</pre>
<h3>TETRISクラス</h3>
<p>TETRISクラスは、分析・設計では[<<場>>ゲーム]として抽出されたクラスです。<br />
ゲームの進行を管理して、ステージやテトリミノなどのインスタンスを保持します。</p>
<h4>初期化</h4>
<p>インスタンスの作成時に背景やステージなどの設定を行います。</p>
<pre>
DQ.TETRIS = function (engine) {
    this._engine = engine;
    this.next = [];
    this._next7 = [];
    //ステージを作成
    this.stage = new DQ.TETRIS.Stage(engine);
}
DQ.TETRIS.prototype = {
  initialize: function () {
    //ステージをスプライトとして追加
    var stage = new DQ.Screen.Canvas.Sprite({
        y: 0,
        x: 0,
        width: 320,
        height: 320,
        animation: false,
        numberOfPause: 0,
        image: stageImg.client[0]
    });
    //背景を設定
    var bg = new DQ.Screen.Canvas.BG({
        width: bgImg.width(),
        height: bgImg._height,
        image: bgImg.client[0]
    });
    this._engine._canvas.push(stage);
    this._engine._canvas.bg(bg);
    ...
  }
}
</pre>
<h4>テトリミノの作成</h4>
<p>次のテトリミノの作成はwikiによれば、おおよそ以下の通りです。<br />
<BLOCKQUOTE>あらかじめ重複しないように７つのテトリミノを決定します。そして、その７つのテトリミノを使い切ると次の７つのテトリミノを決定します。</BLOCKQUOTE><br />
このルールを元に次のテトリミノ決定ルーチンを作成します。<br />
画面に表示する３つのテトリミノを取得するようになっています。</p>
<pre>
{
    _next7: [],
    _genNextTetrimino7: function () {
        var used = [false, false, false, false, false, false, false];
        this._next7 = [];
        for (var i = 0; i < 7; i++) {
            while (true) {
    var candidate = Math.floor(Math.random() * 7);
    if (i &lt; 2 &#038;&#038; (candidate == DQ.TETRIS.TetriminoFactory.DIR.S || 
                  candidate == DQ.TETRIS.TetriminoFactory.DIR.Z)) {
        continue;
    }
    if (used[candidate]) {
        continue;
    }
    used[candidate] = true;
    this._next7.push(candidate);
    break;
            }
        }
    },
    genNextTetrimino: function () {
        //上限 MAX_NEXT分(3つ)だけ、次のテトリミノを作成する
        for (var i = this.next.length; i &lt; DQ.TETRIS.MAX_NEXT; i++) {
            //7つのテトリミノがなければ作成
            this._next7.length == 0 &#038;&#038; this._genNextTetrimino7();

            //テトリミノのインスタンスを作成
            var mino = DQ.TETRIS.TetriminoFactory.create(this._next7.shift());
            mino.setCanvas(this._engine._canvas);

            this.next.push(mino);
        }

        //待機位置へ移動
        for (var i = 0; i &lt; DQ.TETRIS.MAX_NEXT; i++) {
            this.next[i].to(DQ.TETRIS.NextPos[i].x, DQ.TETRIS.NextPos[i].y);
        }
    }
}
</pre>
<h4>落下テトリミノの作成</h4>
<p>ゲームを開始すると、待機しているテトリミノが落下するテトリミノへと移行します。</p>
<pre>
{
    shiftCurrent: function () {
        //待機中のテトリミノの先頭を落下中のテトリミノに移行
        this.current = this.next.shift();

        //ステージの最上部へ移動
        this.current.to(DQ.TETRIS.STAGE_LEFT + DQ.TETRIS.CHIP_SIZE * 3, this.stage.top);
        if (this.hitTest(0, 0)) {
            //開始位置で衝突するなら終了
            this.gameOver();
            return;
        }

        //減った分だけ次のテトリミノを作成します。
        this.genNextTetrimino();

        //ゴーストを作成
        this.ghost &#038;&#038; this.ghost.remove();
        this.ghost = DQ.TETRIS.TetriminoFactory.createGhost(this.current);
        this.ghost.setCanvas(this._engine._canvas);
        //落下中のテトリミノと位置合わせ
        this.ghost.to(DQ.TETRIS.STAGE_LEFT + DQ.TETRIS.CHIP_SIZE * 3, this.stage.top);

        this.moveGhost();
    }
}
</pre>
<h4>落下処理</h4>
<p>無操作の状態で、テトリミノはレベルに応じて落下していきます。<br />
落下する量の計算は次の様になります。</p>
<pre>
{
    calcGDY: function () {
        this.gdy = this.g * DQ.TETRIS.CHIP_SIZE / this.fps;
        //1chip分/framを最大値とする(hitTest()が対応していないので）
        this.gdy = this.gdy &gt; DQ.TETRIS.CHIP_SIZE ? DQ.TETRIS.CHIP_SIZE - .1 : this.gdy;
    }
}
</pre>
<p>TETRISクラスの<code>update()</code>もFPSに従って定期的に呼び出されます。</p>
<pre>
{
    update: function() {
        var dy = this.gdy;
        //当たり判定
        if (this.hitTest(0, dy + DQ.TETRIS.CHIP_SIZE)) {
            //「遊び」に移行
            this.game = DQ.TETRIS.Mode.Delay;
            this._elapse = 0;
            this.stopAccelerate();
            //下に移動
            this.current.move(0, dy);
            //描画上のずれを補正
            this.current.fit();
        } else {
            this.current.move(0, dy);
        }
}
</pre>
<p><a href="http://plusb.jp/blog/?p=760">次はStageクラス他</a></p>
]]></content:encoded>
			<wfw:commentRss>http://plusb.jp/blog/?feed=rss2&#038;p=709</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>テトリスをJavaScriptで作る 実装編(1)</title>
		<link>http://plusb.jp/blog/?p=699</link>
		<comments>http://plusb.jp/blog/?p=699#comments</comments>
		<pubDate>Mon, 18 Jul 2011 08:43:23 +0000</pubDate>
		<dc:creator>M. K.</dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[ゲーム作成]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[Tetris]]></category>

		<guid isPermaLink="false">http://plusb.jp/blog/?p=699</guid>
		<description><![CDATA[設計を元に実装していきます。 ソース:tetris.js ライブラリー:dq-rtg.js,dq-cancas.js(実際に動かすにはライブラリのコアコンポーネントとjQueryも必要です）。 できあがり 状態管理 常に &#8230; <a href="http://plusb.jp/blog/?p=699">続きを読む <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p><a href="http://plusb.jp/blog/?p=682">設計</a>を元に実装していきます。<br />
ソース:<a href="http://plusb.jp/dq/samples/js/tetris.js">tetris.js</a><br />
ライブラリー:<a href="http://plusb.jp/dq/js/rtg/dq-rtg.js">dq-rtg.js</a>,<a href="http://plusb.jp/dq/js/dq-canvas.js">dq-cancas.js</a>(実際に動かすにはライブラリのコアコンポーネントとjQueryも必要です）。<br />
<a href="http://plusb.jp/blog/?p=694">できあがり</a><br />
<span id="more-699"></span></p>
<h3>状態管理</h3>
<p>常にタイマーからの更新要求が飛んでくるので、ゲームの状態を用意して描画を少し管理することにします。</p>
<h4>ゲーム状態</h4>
<p>Start:<br />
 &#8220;Push A Button&#8221; を表示して、ゲームの開始を待っている状態</p>
<p>Play:<br />
 ゲーム進行中、落下中のテトリミノを落としたり、キー操作を画面に反映します。</p>
<p>Pause:<br />
 ゲーム進行中ですが、落下などの進行を中断します。</p>
<p>Erase:<br />
 ラインの削除演出を表示中です。<br />
 テトリミノの落下が抑止されます。</p>
<p>GameOver:<br />
 &#8220;Game Over&#8221; が表示されます。</p>
<h4>ライン削除</h4>
<p>演出の状態を管理します。<br />
ライン削除演出の状態を管理します。</p>
<p>None:<br />
 落下中ではない</p>
<p>Flash:<br />
 落下対象のラインを光らせます。</p>
<p>Erase:<br />
 落下対象のラインが消えます。</p>
<p>Down:<br />
 落下対象のラインを落とします。</p>
<h3>実装モデル</h3>
<p>設計を元に、クラスを作成します。</p>
<p>実装用に作成したクラスは以下の通りです。</p>
<ol>
<li>TETRIS:ゲーム全体を管理</li>
<li>Stage: ステージ上のイベントを処理</li>
<li>Line: 各ラインの状態を管理</li>
<li>Tetrimino: 落下中やNextなどのテトリミノを管理</li>
<li>TetriminoFactory: Tetriminoの作成を管理</li>
</ol>
<p>[TETRIS]+-ステージ[Stage]<br />
[TETRIS]-進行管理[Engine]<br />
[TETRIS]+-current[Tetrimino]<br />
[TETRIS]+-next[Tetrimino]</p>
<p>[Stage]+-[Line]<br />
[Line]+chip[Sprite]</p>
<h4>画像</h4>
<p>下準備として、ステージやチップ（粒）の画像を用意します。<br />
背景<br />
<a href="http://plusb.jp/blog/wp-content/uploads/2011/07/tetris_bg.jpg"><img src="http://plusb.jp/blog/wp-content/uploads/2011/07/tetris_bg.jpg" alt="テトリス背景" title="tetris_bg" width="320" height="320" class="alignnone size-full wp-image-700" /></a><br />
粒<br />
<a href="http://plusb.jp/blog/wp-content/uploads/2011/07/block.png"><img src="http://plusb.jp/blog/wp-content/uploads/2011/07/block.png" alt="粒" title="block" width="14" height="112" class="alignnone size-full wp-image-701" /></a><br />
ステージ<br />
<a href="http://plusb.jp/blog/wp-content/uploads/2011/07/stage.png"><img src="http://plusb.jp/blog/wp-content/uploads/2011/07/stage.png" alt="ステージ" title="stage" width="320" height="320" class="alignnone size-full wp-image-704" /></a><br />
フラッシュ<br />
<a href="http://plusb.jp/blog/wp-content/uploads/2011/07/flash.png"><img src="http://plusb.jp/blog/wp-content/uploads/2011/07/flash.png" alt="削除前のフラッシュ" title="flash" width="140" height="14" class="alignnone size-full wp-image-702" /></a></p>
<h4>テトリミノの作成</h4>
<p>テトリミノは全て4つの粒で構成されています。<br />
それぞれのテトリミノの粒の位置を配列で準備します。<br />
単純なI型は、次の様になります。９０度づつ回転するので種別ごとに４つのデータを持ちます。</p>
<pre>
        [ //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]]
        ],
</pre>
<p>この情報を元にスプライトを作成します。</p>
<pre>
    create: function (type) {
        var chips = types[type][0]; //指定された型の先頭の粒リストを取得
        var sprites = [];
        for (var i = 0; i &lt; 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
                })
            );
        }
        //４つのスプライトを元にグループを作成
        var group = new DQ.Screen.Canvas.SpriteGroup(sprites);
        return new DQ.Tetrimino(type, group);
    }
</pre>
<p>Spriteは座標とイメージを保持していてCanvasクラスから渡されたコンテキストを元に以下の様な感じで描画しています。</p>
<pre>
  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);
        }
    }
</pre>
<p>テトリミノの作成は専用のクラスを用意しておいて、テトリミノ自身からは分離しておきます。<code>createGhost()</code>や<code>rotate()</code>は必要に応じて後で説明します。</p>
<pre>
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 &lt; 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 &lt; 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 &lt; 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;
        }
    }
}
</pre>
<p><a href="http://plusb.jp/blog/?p=709">続く</a></p>
]]></content:encoded>
			<wfw:commentRss>http://plusb.jp/blog/?feed=rss2&#038;p=699</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>テトリスをJavaScriptで作る</title>
		<link>http://plusb.jp/blog/?p=694</link>
		<comments>http://plusb.jp/blog/?p=694#comments</comments>
		<pubDate>Mon, 18 Jul 2011 05:39:38 +0000</pubDate>
		<dc:creator>M. K.</dc:creator>
				<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[ゲーム作成]]></category>
		<category><![CDATA[プログラミング]]></category>
		<category><![CDATA[Tetris]]></category>

		<guid isPermaLink="false">http://plusb.jp/blog/?p=694</guid>
		<description><![CDATA[とりあえず、作りました。 wikiを見た事で、最低レベルのハードルをあげてしまったので時間がかかってしまいました。 実装の解説は後で書きます。 ルールがシンプルなだけあって1000ステップにも到達しなかったのはびっくりで &#8230; <a href="http://plusb.jp/blog/?p=694">続きを読む <span class="meta-nav">&#8594;</span></a>]]></description>
				<content:encoded><![CDATA[<p>とりあえず、作りました。<br />
wikiを見た事で、最低レベルのハードルをあげてしまったので時間がかかってしまいました。<br />
実装の解説は後で書きます。<br />
<span id="more-694"></span><br />
ルールがシンプルなだけあって1000ステップにも到達しなかったのはびっくりです。ただ、もう少しちゃんとした物にするためにはスーパーローテーションには対応する必要がありそうです。</p>
<p>ソース:<a href="http://plusb.jp/dq/samples/js/tetris.js">tetris.js</a></p>
<p><a href="http://plusb.jp/dq/samples/tetris.html">tetris.html</a><br />
フレーム内を一度クリックするとキーが効くようになります。十字キーに対応しています。<br />
Aボタンは&#8217;V'キー<br />
Bボタンは&#8217;C'キー<br />
です。<br />
<iframe src="http://plusb.jp/dq/samples/tetris.html" width="320" height="420" style="overflow: hidden;"></iframe><br />
<a href="http://plusb.jp/blog/?p=682" title="設計編">設計編</a><br />
<a href="http://plusb.jp/blog/?p=699" title="実装編">実装編(1)</a></p>
]]></content:encoded>
			<wfw:commentRss>http://plusb.jp/blog/?feed=rss2&#038;p=694</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
