正月休みに作ったゲームフレームワーク

モジュール化の練習に。

年末にゲームを作った。
http://amo12937.github.io/minmax_web/

年末時点では、2014年内に公開することを優先させたため、モジュールとしてはうまく分離できなかった（目指してはいたが…）
年始の4日で、テストとリファクタリングを行い、いくつかの機能をモジュールとして bower に公開したのでここにそれを記しておく。

# クラス図
![スクリーンショット 2015-01-04 21.40.38.png](https://qiita-image-store.s3.amazonaws.com/0/38864/61fd1a00-f38a-db68-5bda-c79218baeb77.png)

1回のゲームは、次の3つのオブジェクト間のメッセージパッシングによって進行する：

- `amo.module.game.game.Game`
    - ゲームの進行役
    - ゲームの開始を受けて、適切なプレイヤーに `play` メッセージを送ることでゲームを進行させる（後述のシーケンス図参照）。
- `amo.module.game.player.Player`
    - プレイヤー
    - 一つの戦略（`player.strategy`）に従って、1回の `play` を行う。
- `amo.minmax.module.board.Board`
    - ゲーム盤
    - 盤面、スコア、現在のターン、選択可能な手の一覧など、ゲーム固有の要素を保持する。

# ゲームのシーケンス図
1回のゲームの進行はこんな感じ。
![スクリーンショット 2015-01-04 21.55.21.png](https://qiita-image-store.s3.amazonaws.com/0/38864/37891b2e-3323-0be1-204e-529e6b2c35a1.png)

# 各種モジュール
## `angular.amo.module.game.game`
https://github.com/amo12937/angular.amo.module.game.game

### game.GameFsm
game の進行をステートマシンで表現している。game の状態はとてもシンプルで、初期状態 `INIT`、プレイ中 `PLAYING`、正常終了 `DONE`、異常終了 `STOPPED` の4種類しかない。

![スクリーンショット 2015-01-04 22.13.15.png](https://qiita-image-store.s3.amazonaws.com/0/38864/be31729b-27d4-dbc6-0b02-4c0edb5aab00.png)

### game.Game
`GameFsm` 自体を直接使うことはなく、`Game` が内部で使用している。使う側は、`Game` をインスタンス化し、`start` メッセージを呼ぶことで状態を遷移させていく。

| method | input | output | description |
|:-------|:------|:-------|:------------|
| start | - | - | ゲームを開始する際に呼び出す。このメソッドは一度だけ呼べば良い。 |
| pause | - | - | このメソッドが呼び出されると、次に resume が呼ばれるまでゲームを中断できる。ゲーム中断中も play は続行されるが、次の player へは処理は渡らず、delegate.notifyFinishedPlaying は呼ばれない。 |
| resume | - | - | 中断中に呼び出されると、ゲームが再開される。play が終了していた場合は delegate.notifyFinishedPlaying が呼び出され、次の player へ処理が渡る。 |
| paused | - | boolean | 中断中の場合に true を返す |
| stop | - | - | ゲームを中止する。 |

### game.Player
（これは、実際には `JavaScript` のコードとしては現れない。`JavaScript` にインターフェースを書く仕組みが無いため。）
`game.Player` は、`Game` から `player` への要求インターフェースである。必要なメソッドは以下のとおり

| method | required | input | output | description |
|:-------|:---------|:------|:-------|:------------|
| play | o | - | finished | 1回の `play` を要求し、その `play` によってゲームが終了したかどうかを返す。output は `boolean` でも良いが、ユーザーの入力を待ったりするために、`promise` を返してもよい。 |

### game.delegate
`Game` は `player` のことを知らないので、次に誰が `play` するかを外部に問い合わせる。

| method | required | input | output | description |
|:-------|:---------|:------|:-------|:------------|
| getNextPlayer | o | - | game.Player | 次に `play` する `player` を返す |

他にも、ゲームの進行にあわせて `notify` 系のメッセージを投げるが、多いので省略。

## `angular.amo.module.game.player`
https://github.com/amo12937/angular.amo.module.game.player
プレイヤー自体は何もせず、単に戦略 `player.strategy` に従って、`play` を行う。

### `player.strategy.Man`
`Man` は、人間の入力に従う戦略である。現在の盤面をもとにユーザーが手を入力し、それを持って 1度の `play` とする。

### `player.strategy.Com.AlphaBeta`
`Com.AlphaBeta` は、アルファベータ法に基づくコンピュータの戦略である。アルファベータ法はミニマックスを改良したもので、ゲーム木の中の探索する必要のない枝を切って、効率化している。

コンピュータの手の選択がゲーム盤に依存していないのは、ゲーム盤が次のようにうまく抽象化されているからである。

### `player.Board`
`player.Board` インターフェースは、プレイヤーがゲーム盤に求める要求インターフェースで、次のメソッドが必要である：

| method | description |
|:-------|:------------|
| `current.turn()` | 現在の手番を表す値を返す |
| `current.value(turn)` | 手番を受け取り、現在の評価値を返す |
| `current.getSelectableList()` | 現在選択可能な手の一覧を返す |
| `select(x)` | 手を表す値を受け取り、その手を受理したかどうかを返す |
| `undo()` | 盤面の状態を一つ前に戻す |
| `isFinished()` | ゲームが終了したかどうかを返す |

- 「手」`x` は、選択可能な一覧を順に `select` していくので、具体的には何なのかを問わない。それは `Object` であっても、`Array` であっても、数値や文字列であっても良い。
- 「手番」`turn` は単に current.value を得るために使われるため、これも具体的に何なのかを問わない。
- `current.value` は、数値を返す必要がある。（本当は順序が定義されたものであれば何でも良いがとしたかったが、内部で `-Infinity` や `Infinity` と比較している。）

## `amo.minmax.module.board`
`minmax` のゲーム盤 を表す。
このゲームにおいて、

- `turn`: 現在の手番
    - 先手、または後手が交互に返される
- `value`: ある手番の評価値
    - 自分のスコアから相手のスコアを引いた差である
    - この値を大きくすることがゲームの目的である
- `selectableList`: 選択可能な手の一覧
    - 初手においては全てのパネル
    - 以降の後手は縦1列
    - 先手は横1列
    - ただし、一度選ばれたパネルは省く
- `isFinished`: 終了条件
    - 選択可能な手が無くなった

となる。`amo.minmax.module.board.Board` で実装されている。


# おしまい：次の一手
仕事では時間やコストの折り合いもあってあまりモジュールに分けての開発というのをやれていなかったが、今回は完全に自分の趣味での開発だったので、やりたいように出来た。まだテストとリファクタリングとモジュール化が徹底できていないので、継続してやっていきたい。

- テスト
- リファクタリング
- モジュール化
- 横展開
    - board を差し替えれば、「二人零和有限確定完全情報ゲーム」系を作ることができるので、オセロや、もう少し簡単な「Kalah」というのを作ってみたい。