【enchant.js】神経衰弱の作り方 その2【TCard.js】

『神経衰弱の作り方 その2』です。
今回はゲームを繰り返しプレイできるよう、ループを仕込みたいと思います。

1.ループ部分の切り出し

さて。
今回はゲームを繰り返しプレイできるように変更しましょう。
まずはゲームの流れを再び見てみます。
0.カードの準備
1.カードを場に広げる
2.カードの選択
2-1.1枚目のカードを選択
2-2.2枚目のカードを選択
3.判定
3-1.同じ数なら場からのぞいて得点札に
3-2.異なる数ならカードを裏返す
4.終了判定
4-1.場にカードがないなら終了
4-2.場にカードが有るなら2に戻る

このうち『0.カードの準備』はカードのグラフィックが使えるよう、ゲームのassetsに登録する作業でした。この部分は繰り返し行う必要はありません。ゲームの繰り返しの対称となるのは1~4の部分となります。
それでは1~4の部分を切り出してみましょう。

TGame = Class.create(Scene, {
    initialize : function() {
        Scene.call(this);
        // enchant.TCard.setPack(); より後の行をすべてコピー
    },
});

切り出す際は以下の点に注意して下さい。
・Sceneのインスタンス
 game.onload中のSceneのインスタンスはgame.rootSceneでしたが、Sceneクラスに切り出したことでSceneのインスタンスがthisに変わりますのでご注意下さい。game.onload中ではインスタンス名が長かったのでsceneという変数に入れていたので

var scene = this;

と記述すると変更量が少なく楽かと思います。
・assets
 game.assetsはgame変数が有効なスコープ内でしか利用できませんので、ここは長いですが『enchant.Core.instance.assets』と書き換えて下さい。

clear.image = enchant.Core.instance.assets["clear.png"];

それではgame.onloadを以下のように書き換えて実行してみましょう。

    game.onload = function(){
        // カードの画像を assets に登録します
        enchant.TCard.setPack();
        game.replaceScene(new TGame());
    };

問題なく動きますね。

2.ループの準備

もう一度ゲームの流れに注目してみます。
0.カードの準備
1.カードを場に広げる
2.カードの選択
2-1.1枚目のカードを選択
2-2.2枚目のカードを選択
3.判定
3-1.同じ数なら場からのぞいて得点札に
3-2.異なる数ならカードを裏返す
4.終了判定
4-1.場にカードがないなら終了
4-2.場にカードが有るなら2に戻る

『1.カードを場に広げる』は1ゲームに付き1回実行される処理です。言ってみればゲームの初期化部分ですね。それでは『1.カードを場に広げる』を関数という形で切り出してみましょう――と行きたいところですが、実はそうは問屋がおろしません。2~3の処理はゲームとしては中核の部分になりますが、プログラムとしては「こうした振る舞いをしてね」ということを記述する宣言部分となります。つまり、2~3も初期化の一部になるわけなんですね。

ではここでコードを見返してみましょう。
書かれているコードはすべてdeck変数にからむものですよね? lenやmapという変数はdeckのメンバではありませんが、deckに関わることには変わりありません。つまり、1ゲームにつき1つのdeckを使うようにすれば簡単にループが実現できるわけです。

それでは、まずはinitializeの後ろにinitGameという関数を作りましょう。『Scene.call(this);』よりあとをカット&ペーストでinitGameの中に移します。

TGame = Class.create(Scene, {
    initialize : function() {
        Scene.call(this);
    },
    initGame : function() {
        var scene = this;
        // 後略
    }
});

次にゲームクリア時のclear.pngのaddChild先をdeckに変更します。deckはselfで定義されているのでそれを使いましょう。

var clear = new Sprite(270, 48);
clear.image = game.assets["clear.png"];
clear.x = 25;
clear.y = 140;
self.addChild(clear);

お次は画面全体を覆うpadというEntityをclearの後にaddChildし、Sceneにタッチされた旨を通知する処理を追加します。

var pad = new Entity();
pad.width = pad.height = 320;
pad.addEventListener(Event.TOUCH_START, function() {
    scene.dispatchEvent(new Event("touch_clear"));
});
self.addChild(pad);

最後にpadがタッチされた際の処理を追加します。

TGame = Class.create(Scene, {
    initialize : function() {
        Scene.call(this);
        this.addEventListener("touch_clear", this.touchClear );
        this.initGame();
    },
    initGame : function() {
        var scene = this;
        // 略
    },
    touchClear : function() {
        console.log("クリアしました。");
    }
});

これで、クリアした時に画面をタッチすると次のゲームの初期化処理を行う準備が整いました。padの処理はclearにまとめてもよかったのですが、そうすると270*48の領域をタッチしないといけなくなるので画面全体を覆うEntityを使うことにしました。

3.ループの作成

それではtouchClearの中を充実させていきましょう。
まずはSceneの子となっているdeckを開放します。そしてゲームを開始するためのinitGameを呼びます。

touchClear : function() {
    var deck = this.firstChild;
    this.removeChild(deck);
    while (0 < deck.childNodes.length) {
        deck.removeChild(deck.firstChild);
    }
    this.initGame();
}

deckをSceneからremoveChildする他、deckの子についても開放するようにしました。

これでゲームのループが完成し、繰り返し遊べるようになりました。今回の通しのコードはこちらになります。
ですが、正直このコードはあまりお行儀のよいものではありません。何故かと言うと、Spriteの生成と破棄を大量にやっているからです。Spriteは生成すればその分メモリを消費します。使われたメモリはSpriteが不要となった時点で開放してやればいいのですが、その処理はブラウザに依存しておりプログラマーが制御できるものではありません(でしたよね?)。ですので、Sprite等は極力使い回した方が好ましいのです。

そんなわけで、次回はSpriteを使いまわす方向に神経衰弱を改造していきたいと思います。