電脳麻将のプログラム構成(0) ~ 全体編 - koba::blog に続き、今回はJavaScriptのプログラム構成。
電脳麻将 の プログラム は全てJavaScriptで記述しています。プログラムは以下に分類できます。
- 共通モジュール
- 画面系モジュール
- ページ毎モジュール
- 開発用モジュール
- 共通モジュール
- ブラウザに依存しないコードだけで構成される。手牌の操作、シャンテン数計算、和了点計算、局進行、麻雀AI など。
- 画面系モジュール
- 手牌表示、麻雀卓表示、UIなどブラウザに依存したコード。
- ページ毎モジュール
- 各ページのスタートアップルーチン。ページに閉じたロジックも含む。
- 開発用モジュール
- 試験用の局進行ドライバ。牌譜解析・統計用ツール、過去のAI(比較対戦用)など開発のためにだけ使用するモジュール。
ソースファイル一覧
src/js/ 配下の ファイル構成 は以下の通り。
ファイル名 | クラス名 | 説明 |
---|---|---|
entry.js | Majiang | 共通+画面系モジュールのエントリーポイント |
game.js | トップページ用ルーチン | |
autoplay.js | 自動対戦ページ用ルーチン | |
paipu.js | 牌譜ページ用ルーチン | |
paili.js | 牌理ページ用ルーチン | |
hule.js | 和了点計算ページ用ルーチン | |
dapai.js | 何切る解答機ページ用ルーチン | |
paiga.js | 牌画入力ページ用ルーチン | |
majiang.js | Majiang | 共通モジュールのエントリーポイント |
majiang/shoupai.js | Majiang.Shoupai | 手牌を表現するクラス |
majiang/shan.js | Majiang.Shan | 牌山を表現するクラス |
majiang/he.js | Majiang.He | 河(捨て牌)を表現するクラス |
majiang/game.js | Majiang.Game | 局進行を実現するクラス |
majiang/player.js | Majiang.Player | 麻雀AIを実装したクラス |
majiang/suanpai.js | Majiang.Suanpai | 読みのための牌の数え上げを実装したクラス |
majiang/util.js | Majiang.Util | (下記2ルーチンのエントリポイント) |
majiang/util/xiangting.js | Majiang.Util | シャンテン数計算ルーチン |
majiang/util/hule.js | Majiang.Util | 和了点計算ルーチン |
majiang/view.js | Majiang.View | 画面系モジュールのエントリーポイント |
majiang/view/pai.js | Majiang.View | 牌の画像を管理するルーチン |
majiang/view/audio.js | Majiang.View | 音声を管理するルーチン |
majiang/view/shoupai.js | Majiang.View.ShouPai | 手牌を表示するクラス |
majiang/view/shan.js | Majiang.View.Shan | 牌山を表示するクラス(ドラ表示のみ) |
majiang/view/he.js | Majiang.View.He | 河(捨て牌)を表示するクラス |
majiang/view/huledialog.js | Majiang.View.HuleDialog | 和了・流局時のダイアログを表示するクラス |
majiang/view/game.js | Majiang.View.Game | 麻雀卓全体を表示するクラス |
majiang/view/gamectl.js | Majiang.View.GameCtl | ゲームに関するUIを実装するクラス |
majiang/view/player.js | Majiang.View.Player | 対戦時のUIを実装するクラス |
majiang/view/paipufile.js | Majiang.View.PaipuFile | 牌譜一覧の表示とそのUIを実装するクラス |
majiang/view/paipu.js | Majiang.View.Paipu | 牌譜再生のUIを実装するクラス |
majiang/view/stat.js | Majiang.View.Paipu | 牌譜集計・表示ルーチン |
majiang/view/analyzer.js | Majiang.View.Analyzer | 検討モードを表示するクラス |
majiang/dev/game.js | Majiang.Dev.Game | 開発用局進行ドライバ |
majiang/dev/player*.js | Majiang.Player | 過去のAIのバックアップ(比較対戦用) |
majiang/dev/suanpai*.js | Majiang SuanPai | 過去のAIのバックアップ(比較対戦用) |
majiang/dev/analogbase.js | AnaLogBase | 牌譜解析・統計用ツール |
majiang/dev/analog.js | AnaLog | 牌譜解析・統計用ツール |
プログラム・スタイルとビルド方法
ES6(ES2015) で導入された class 構文を使ってモジュール化しています。このため IE11 などの ES6 の構文を理解できない古いブラウザではそのままでは動作しません。ver.1.3 までは Babel をつかってES5に変換していましたが、次の版の ver.1.4 から古いブラウザは切り捨て、Babel の使用をやめるよう修正中です。*1
モジュールは CJS(CommonJS) の require()
で参照しています*2。ブラウザでも動作するよう webpack で変換し、複数のソースプログラムを1つのバンドルにまとめています。ただし、webpack で一般的な「ページごとに1つのバンドル」という方法ではなく、各ページが
- 全ページ共通のバンドル (上記の共通モジュール+画面系モジュール)
- ページ毎のバンドル (上記のページ毎モジュール、1ファイル1バンドル)
の2つのバンドルを読み込むようにしています。これを可能にするのが大域変数 Majiang
です。共通+画面系モジュールのエントリーポイントの entry.js の中で、
global.Majiang = require('./majiang'); // 共通モジュール global.Majiang.View = require('./majiang/view'); // 画面系モジュール
とし、全てのクラスを大域変数 Majiang
にマップしています*3。これによりページ毎モジュールからは上記の表に示した Majiang.Shoupai
などのクラス名で参照できるようになる訳です。
大域変数を使う方法に抵抗がある方もいると思いますが、ページ毎に1バンドルとする一般的な方法では、共通モジュールのコードがページ毎に異なる名前で読み込まれるという無駄な通信が発生します。電脳麻将の共通モジュールは 350KB もあり無視できないサイズです。また、全てのクラスにユニークな名前があることはプログラムの理解にも役立つと考え、この方法を採用しました。
ビルド後のバンドルは www/js/ 配下に配置されます。
ファイル名 | 説明 |
---|---|
www/js/majiang.js | 全ページ共通のバンドル |
www/js/game.js | トップページ用のバンドル |
www/js/autoplay.js | 自動対戦ページ用のバンドル |
www/js/paipu.js | 牌譜ページ用のバンドル |
www/js/paili.js | 牌理ページ用のバンドル |
www/js/hule.js | 和了点計算ページ用のバンドル |
www/js/dapai.js | 何切る解答機ページ用のバンドル |
www/js/paiga.js | 牌画入力ページ用のバンドル |
webpack.config.js は以下となります。
module.exports = { entry: { majiang: './src/js/entry.js', game: './src/js/game.js', autoplay: './src/js/autoplay.js', paipu: './src/js/paipu.js', paili: './src/js/paili.js', hule: './src/js/hule.js', dapai: './src/js/dapai.js', paiga: './src/js/paiga.js', }, output: { path: __dirname + '/www/js/', filename: '[name].js' }, };
テスト方法
ブラウザに依存しない共通モジュールのみ自動試験しています。テストフレームワークには Mocha、カバレッジレポートには Istanbul を使用しています。
局進行を実現するクラス Majiang.Game
のテストに一工夫必要だったため、開発用局進行ドライバ Majiang.Dev.Game
を用意しています。開発用局進行ドライバには牌譜から牌山と摸打を再現して局進行をしたり、一切の非同期処理をせず*4に局進行するなどの機能があります。ver.0.9 まではブラウザ上でしか動作しなかったため、思考アルゴリズムの評価のために麻雀AI同士を対戦させる場合でも非同期で処理が動いていました(そうでないとブラウザが無限ループと判定してしまうため)。ver.1.0 以降はブラウザなしでの動作が可能になったため、このような無駄がなくなり、処理速度が向上しました。また牌譜から局進行を再現することでバグの検証や思考アルゴリズムの検証がやりやすくなっています。
画面系モジュールを動作させるためにはブラウザが必要なため、自動試験は行なっていません。自動試験は可能なようなのですが、表示の乱れなどは目視でしか確認できないため、自動化のメリットは少ないと考えています。