Flutter webでボードゲーム（と対戦用AI）を作った話

※AI と書いておきながら、機械学習に関する話題はこの記事では触れません。

## はじめに

自分のスキルアップおよび遊び相手を創造することを目的として、少し前に Flutter で作ったボードゲームの Web アプリを紹介します。
新たな知見などを共有するものではなく、単に Flutter で私が好きなボードゲームを作りました、という情報発信です。
もしも Flutter でボードゲームを作ろうとしている人がいるならば、そのモチベーション向上に繋がれば幸いです。

筆者はベンチャー企業でマネジメントの仕事をしています。
若手の技術力に恐れ慄きながら、プログラミングの勘所だけは忘れたくないと思い、趣味を兼ねてボードゲームの Web アプリを作りました。

ボードゲームはルールが厳格に決まっているため、いわゆるビジネスロジックを組む際に迷うことがありません。
それゆえ、新たな言語を習得する際の教育題材として適していると考えています。
せっかく環境を作ったので、今はさいきょうの AI を作ろうと強化学習で実験しまくっています。

## ボードゲームの題材

### War Chest（ウォーチェスト）

https://bodoge.hoobby.net/games/war-chest

#### ゲーム作者

トレヴァー・ベンジャミン（Trevor Benjamin）
デビッド・トンプソン（David Thompson）

#### 内容

チェスとカードゲームを組み合わせたような、２人 or ４人対戦ゲーム。
各プレーヤーが使いたい兵種をドラフト方式で選択し、各兵種の特性を駆使して戦う。
敵の兵力と火花を散らしつつ、一定数の拠点を先に制圧した方が勝利する。
バッグ（デッキ）からランダムに引いた手札によって動かせる駒が決まる。
どのタイミングでどの兵種をバッグに追加するか、といった確率の管理がキモとなる。
相手の手札は見えないので、チェスや将棋とは異なり、お互いに見えない情報がある「不完全情報ゲーム」と言える。

## 動機

- コンピューターと気軽に対戦したい。
  - War Chest はバチバチに殺し合うゲームなので、妻とやると毎度喧嘩に発展しかける。
- Flutter web の現状を確認したい。
  - 驚異的に日進月歩な Flutter に追いついていたい。
- AI 関連の知識を身につけたい。
  - 実際に手を動かして勘所を掴んでおきたい。
  - 「AI を育てる」という楽しみを知りたい。

## 使用技術

- Flutter
  - UI の実装に使う。
  - ルールエンジンの実装にも Dart を使う。
- Firebase Hosting
  - ホスティングに使う。
  - 今のところ完全に月額 10 円以下で事足りている。
- Cloud Firestore
  - 棋譜の記録に使う。
  - いつか機械学習のインプットにする。

## ひとまず完成

対人戦の前に AI 相手に練習できるのがいいね、という友人の声もあったりして、名前には「道場」を入れました。

https://warchest-dojo.app/

## 構造

![スクリーンショット 2022-05-06 15.50.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/478047/1a6713ac-db5d-e381-8e6b-d5ce20c3561f.png)

勢いで作り始めたのもあり、ステート管理には BLoC や GetX などのフレームワークなどは使っていません。
いつか導入しなければと思いつつ、ちゃんと動いてるからいいか、となっています。。

ただ各クラスの役割だけはハッキリ分けておこうと思い、それなりにクラスは分割しました。
`ルールに基づいて状態を更新するところ`がルールエンジンでもあり、後述の`dart2js`を使って JS にコンパイルして、`Node.js`などでも遊んだりシミュレーションできたりします。

当然ながら AI 対 AI でも兵種によって勝率に偏りが出たりして、眺めているだけでも楽しいです。
勝手に新しい兵種を作って戦わせてみようかと思ったりもします。

## ソースコード

https://github.com/MilosNaniwa/warchest-dojo

## 苦戦した点 ① 盤面をどう描画する？

- 正方形のマスならまだしも、正六角形のマスって描画できるのか…？
- しかもフラクタル風に…？
- ライブラリはあるっぽいができれば頼りたくない…

### 解決策：数式を使う

- キャンバスのサイズ、マスの総数、配列のインデックスから、中心地の座標を割り出す。
- sin、cos を用いて正六角形を描画する。
- 描画したものを着色し、`onHover`、`onTap`などの処理をつける。
- `Stack`でマスの数だけレイヤーを重ねて盤面を描画する。

## 苦戦した点 ② AI をどう実装する？

- ランダム性がある手札に対してどう行動させるべきか？
- チェスと違って「他の駒に指示を飛ばして攻撃させる」などの選択もあるため、組み合わせ数が尋常ではない…
- いきなり機械学習？知識もデータもないのに無理…
- MIN-MAX 法？無理…（そもそも不完全情報ゲームなので無理？）

### 解決策：妥協する

- 難しい仕様兵種は AI がドラフトできないようにする。
- 行動の優先順位だけ決めて、その時点で最優先の行動を取るのみにする。
- 各目標地点までの最短距離を算出するロジックだけ頑張る。

## 苦戦した点 ③ 他の言語とどう連携する？

- 現在進行形で取り組んでいる課題。
- 機械学習系のライブラリは、圧倒的に Python や JS が充実している。Flutter の生態系を出られればもっと世界が広がりそう。
- 描画は各言語で実装するとして、ルールエンジンだけでも移植できるようにしたい…WebAPI 化はお金がかかりそうなので避けたい…

### 解決策：dart2js を使う

- 参考：dart2js: Dart-to-JavaScript compiler
- Dart で書いたコードを JS に変換してくれる、Google 公式ツール。
- 変換後のモジュールに少し手を加えることで、Dart モジュール ⇄ JS にて値䛾受け渡しが可能。
- 手動で書いた JS よりも、Dart を Google パワーで変換させたモジュールの方がパフォーマンスが高いことが多い（らしい）。

## 今後の展望

- 対人戦を実装する
  - 対戦相手が見つからない人には AI をこっそり割り当てて戦わせたい。
  - が、対人戦なら Morgan Polak 氏が作った [War Chest Online](https://warchestonline.com/) の完成度が高すぎる。。
- AI を強くする
  - MIN-MAX 法？モンテ・カルロ法？深層強化学習？
  - いつか筆者との勝率が５割程度になってくれると嬉しい。
- 快適な UX、迷わない UI を目指す
  - 結局いちいち説明をつけるよりも UI/UX を練り上げた方が皆がハッピーになる。

## やってみて良かったこと

- UI/UX を深く考えるキッカケができた
  - ボードゲームは厳格なルールが定まっているので、「どう表現するか？」を考えることに集中できる。
- 自分の中にある「要求」を形にするスキル、直面した問題を「解決」するスキルが鍛えられた
  - 一見不可能に見えても、やってみれば大したことない。
- 次に勉強したい技術の目標ができた
- 国を問わず、遊んでくれた人が感謝してくれた
  - 海外の掲示板で紹介された。

現在は [brain.js](https://github.com/BrainJS/brain.js) という JS の機械学習ライブラリを使って深層強化学習のフレームワークを作ったり実験したりしているので、機会があればその辺りを書いてみようと思います。
Python がなぜかあまり好きになれず。。