Undefined

この記事は1年以上前に書かれたものです。現在の状況にそぐわない場合がございますのでご注意ください。

JavaScriptで三目並べゲームをつくる 第2回

三目並べの実装にあたって

クラスモジュールについて

クラスモジュールの役割

前回の記事でご紹介しました通り、このゲームは構成要素(役割)単位でクラスを分け責任範囲を明確化しようと思いました。
構成要素単位で分けたこれらのクラスオブジェクトから作られた各インスタンスオブジェクトが自分の役割をこなし、ときに相互に働きかけ全体で一つのゲームを形づくります。そうすることでスクリプトの管理や機能追加をしやすいようにします。

 クラスモジュールの構造

クラスモジュール構造の全体像

こちらがクラスモジュールの共通テンプレートです。これを使って各クラスを作っています。

/** ------------------------------
 * 名前空間
 ------------------------------ */
var GAME = GAME || {}; // ゲームを格納するグローバル変数

/** ------------------------------
 * クラスモジュール
 ------------------------------ */
GAME.ClassName = (function() { // 即時関数によるクロージャここから
  'use strict'; // 厳格モード

  /* プライベートな静的変数 */
  var privateStatic = 'xxxx';

  /* コンストラクタ */
  var that = function ClassName() {

    /* パブリックプロパティ */
    this.public = 'xxxx';

    // クラスで使用するカスタムイベントをまとめる
    this.ev = {};

    /* DOMアクセス */
    this.xxxxEl = document.querySelector('#js-xxxx');

    /* カスタムイベントの作成 */
    this.ev.eventType = document.createEvent('Event');
    this.ev.eventType.initEvent('className.eventType', false, true);

    /* イベントの登録 */
    this.xxxxEl.addEventListener('className.eventType', this. publicMethod, false);
  };

  /* パブリックメソッド */
  that.prototype = {
    /*
     * publicMethod
     */
    publicMethod: function() {}
  };

  return that; // クロージャの返り値(コンストラクタ関数)
}()); // 即時関数によるクロージャここまで

以下、各行の意図するところをご説明いたします。

名前空間

var GAME = GAME || {}; // ゲームの全てを格納するグローバル変数

4行目:グローバル空間を汚染しないために変数GAMEを名前空間として、この中にゲームの設定オプション(何目並べかとかプレイヤーが何人とか)以外の全データを格納します。
文はJavaScriptのイディオムで、GAMEプロパティが既にある場合はそれを使用し、無い場合は新たにオブジェクト({}←これ)を代入します。

クラスモジュール

GAME.ClassName = (function() {
      :
  /* コンストラクタ */
  var that = function ClassName() { // 即時関数によりこのコンストラクタ関数が返る
      :
  };

  /* パブリックメソッド */
  that.prototype = {
      :
  };

  return that; // クロージャの返り値(コンストラクタ関数)
}());

9〜44行目:クラスモジュールは即時関数になっています。即時関数は関数定義の直後に関数を実行し通常の関数と同様にreturn文により値を返します。このクラスモジュールの場合は43行目のreturn that;によってthatから参照されるコンストラクタ関数ClassNameが返されGAME.ClassNameに参照されます。
ですので、実際にクラスモジュールからnew演算子によってインスタンスオブジェクトが生成されるときコンストラクタ関数ClassNameが実行されることになります。
これはクロージャです。このクラスモジュールの場合はクロージャにより、プライベートな静的変数を作り、クラスモジュール内にprototypeによるパブリックメソッドの定義をまとめています。

プライベートな静的変数

var private = 'xxxx';

13行目:ここで定義した変数は以下の方法でプライベートな静的変数になっています。

  • クラスモジュール内のローカル変数にすることでクラスモジュール外からアクセスできないプライベート変数になります。
  • クラスモジュール全体を囲んだ即時関数によるクロージャによって、クラスモジュールから生成されたインスタンス間で共通に使用される静的な変数になります。

クラスモジュール外からアクセスできないインスタンス間で共通に使用される静的な変数という性質から、変更することのない決まったデータを定義することに使用しています。

コンストラクタ

var that = function ClassName() {
  // コンストラクタ定義
    :
};

16〜33行目:クラスオブジェクトからnew演算子によってインスタンスオブジェクトが生成されたときにこのコンストラクタが処理されます。インスタンスオブジェクトが受け持ちの仕事を始めるための準備をするところです。 new GAME.ClassName([args]);という呼び出しでインスタンスオブジェクトが生成されます。

return that; // クロージャの返り値

42行目:クラスモジュールの一番下で即時関数が実行されたときに定義したコンストラクタを返します。

パブリックプロパティ

/* パブリックプロパティ */
this.public = 'xxxx';

// クラスで使用するカスタムイベントをまとめる
this.ev = {};

/* DOMアクセス */
this.xxxxEl = document.querySelector('#js-xxxx');

19〜25行目:クラスオブジェクトが受け持つ特有の情報は全てここに保持します。this.evは通知設定に使用するカスタムイベントをまとめるための入れ物です。this.xxxxElはクラスオブジェクトに対応する構成要素(DOM要素)を保持します。

イベント

/* カスタムイベントの作成 */
this.ev.eventType = document.createEvent('Event');
this.ev.eventType.initEvent('className.eventType', false, true);

/* イベントの登録 */
this.xxxxEl.addEventListener('className.eventType', this. publicMethod, false);

28〜32行目:通知を受けるためのカスタムイベントの作成と、通知を受けたときの仕事(イベントリスナ)を指定します。ここではcreateEvent()を使用していますが現在カスタムイベントの作成にはEventコンストラクタが推奨されているようです。ちなみにEventコンストラクタはIE11からの対応になります。

イベント関連メソッド

document.createEvent(type)  イベントオブジェクトを作成。

  • type:イベントタイプを表す任意の文字列
document.initEvent(type,bubbles,cancelable)  イベントオブジェクトを初期化。

  • type:イベントタイプを表す文字列
  • bubbles:バブリングさせるか
  • cancelable:キャンセル可能か
element.dispatchEvent(event)  イベントを通知。(イベントを呼び出す箇所で使用)

  • event:呼び出すイベントオブジェクト
element.addEventListener(type,listener[, useCapture])) イベントリスナーを登録。

  • type;イベントタイプを表す任意の文字列
  • listener:(多くの場合)関数
  • useCapture:キャプチャフェーズを使うか

パブリックメソッド

that.prototype = {
  /*
   * publicMethod
   */
  publicMethod: function() {}
    :
};

36〜41行目:ここにクラスオブジェクトがこなす仕事のための道具(メソッド)を全て設定します。thatはコンストラクタ関数(コンストラクタとして使用している関数)ですのでprototypeプロパティを持ち、prototypeプロパティに指定されたプロパティ(ここではpublicMethod)は、このクラスオブジェクトから生成された全てのインスタンスオブジェクトに共通のメソッドとして継承されます。

グローバル変数GAMEのプロパティ構造

GAME
 ┣ GameControl (進行管理クラスオブジェクト)
 ┣ Player (プレイヤークラスオブジェクト)
 ┣ Board (ゲームボードクラスオブジェクト)
 ┣ Modal (モーダルウィンドウクラスオブジェクト)
 ┣ Info (情報エリアクラスオブジェクト)
 ┣ gameControl (進行管理インスタンスオブジェクト)
 ┃ ┗ players (各プレイヤーインスタンスオブジェクト)
 ┃   ┗ 0
 ┃   ┗ 1
 ┃    :
 ┣ board (ゲームボードインスタンスオブジェクト)
 ┣ modal (モーダルウィンドウインスタンスオブジェクト)
 ┗ info (情報エリアインスタンスオブジェクトオブジェクト)

グローバル変数GAMEに集約したゲームの全情報は上記のような構造で格納されています。
格納されているのは2種類のオブジェクトです。

  1. クラスオブジェクト(大文字ではじまってるプロパティ名のほう)
  2. インスタンスオブジェクト(子文字ではじまってるプロパティ名のほう)

基本的に並列に並んでいます。例外としてPlayerから生成したplayerインスタンスオブジェクトのみgameControlインスタンスオブジェクトのパブリックプロパティplayers内に配列として格納されています。
これはPlayerクラスオブジェクトから生成したplayerインスタンスオブジェクトをゲームの進行管理の一部と考えたためですが、他のオブジェクトと同様並列に配置しても問題はありません。

ゲームの設定オプションと実行

document.addEventListener('DOMContentLoaded', function() {
  'use strict';

  // ゲームの設定
  var options = {
    board: {
      num: 3 // 何目並べか
    },
    players: [{
      name : 'プレイヤー1', // プレイヤー表示名
      color: '#d43c32', // マス目の色
      auto : false       // オートプレイかどうか
    },{
      name : 'プレイヤー2',
      color: '#decd04',
      auto : true
    }]
  };

  // ゲーム実行開始
  console.table(options.players);
  GAME.gameControl = new GAME.GameControl(options);
  GAME.gameControl.scene();

}, false);

ゲームの設定オプションと実行について以下に記します。

ゲームの実行開始のタイミング

1〜25行目:ゲームの実行開始はHTMLの解析及びDOMツリー構築が完了したときに発生するイベントDOMContentLoadedのイベントリスナで実行します。DOMContentLoadedはIE8以下は対応してません。

DOMContentLoaded イベントは、最初のHTMLドキュメントの読み込みと解析が完了した時に発火し、 スタイルシートや画像、サブフレームの読み込みが終わるのを待ちません。 ページが完全に読み込み終わったことを検知するためにのみ、全く異なるイベント ─ load ─ を使用するべきです。

DOMContentLoaded – MDN

ゲームの設定オプション

5〜18行目:ゲームの設定オプションはゲームボードとプレイヤーに対しての2種類です。全て必須項目で設定内容はコード内のコメントの通りです。プレイヤーはplayers配列内のオブジェクトリテラルの数になります。設定オプションを引数として渡しているのは、例えばスタート画面でゲーム設定をできるように機能追加する場合などに作業しやすくなるよう独立させています。

ゲームの実行開始

22〜23行目:最後にゲームを実行開始します。まずGAME.GameControlクラスオブジェクトからGAME.gameControlインスタンスオブジェクトを生成して、GAME.gameControlインスタンスオブジェクトのsceneメソッドを実行してスタート画面を表示しています。最初にGAME.gameControlインスタンスオブジェクトを生成するのは、実行時に内部でGAME.gameControlインスタンスオブジェクトを参照する必要があるためです。


以上になります。お読みいただきありがとうございました!次回は今回のクラスモジュールのテンプレートから進行管理クラスオブジェクトを実装していきたいと思います。

to top