今回のアプリ
使用するモデル (服セット)、服の先攻/後攻への割り当て、操作方法 (手動 / 組み込みアルゴリズム / 外部API) を選択し、対局を開始する。
対局開始。
同じ服を着た人で違う服を着た人を挟むと、その人が着ている服も挟んだ人の服になる。
他の服でも対局ができる。
服モデル
今回のアプリで重要なのが、リバーシの要領で服を変える様子を描画するためのもととなる画像データのセット (モデル) である。
服を変える原理
今回のアプリでリバーシのコマとして用いる人の画像は、「顔画像」と「服画像」に分かれている。
描画を行う際は、まず「顔画像」を描画し、その上から「服画像」を描画する。
「顔画像」と「服画像」を描画する位置を完全に分割するのではなく、「顔画像」の上から「服画像」を描画するようにしたことで、より柔軟なモデルの設計が可能となる。
「顔画像」の中の「服画像」を描画する部分を透明にしておくことで、「完全に分割」した状態にすることもできる。
1個のセットに対局を行う2人分の「顔画像」と「服画像」を入れておく。
このうち、どっちの「顔画像」にどっちの「服画像」を組み合わせてもおかしくないよう、うまく画像を作っておく。
今回用意したモデルでは、既存の画像を加工し、首の位置を合わせて「顔画像」と「服画像」に分割した。
モデルの格納形式
今回は、服モデルを格納するファイル形式として、PNGファイルを採用した。
Portable Network Graphics (PNG) Specification (Third Edition)
PNGファイルの中身はチャンクの列からなり、プライベートチャンクを用いてアプリケーション独自の情報を格納できる。
チャンクの名前はASCIIのアルファベット4文字からなり、それぞれの文字が大文字か小文字かによって以下のチャンクの性質を表す。
| 位置 | 大文字の場合 | 小文字の場合 |
|---|---|---|
| 1 | 必須 (無いと画像のデコード不能) | 補助 (無くても画像のデコード可能) |
| 2 | パブリック (W3Cで定義) | プライベート |
| 3 | 予約 (必ず大文字にする) | 無効 |
| 4 | コピー不安全 (画像データに依存) | コピーして安全 |
今回は、以下のチャンクをそれぞれちょうど1個ずつ格納することにより、服モデルを表現する。
チャンクの順番は自由である。
また、モデルが選択されたとき、モデルのデータを格納したPNGファイルそのものを画像として対局設定画面に表示する。
| 人A用チャンク名 | 人B用チャンク名 | 内容 |
|---|---|---|
crNa |
crNb |
服の名前 (UTF-8テキスト) |
crFa |
crFb |
顔画像 |
crCa |
crCb |
服画像 |
crDa |
crDb |
対局画面上部に表示する画像 |
各チャンクに、PNG で規定されている以外のヘッダやフッタはつけず、各データをそのまま格納する。
画像は、PNG などのWebブラウザが対応している形式の画像ファイルのデータをそのまま格納する。
チャンク名の cr は、crothes reversi (服リバーシ) を表す。
3文字目は、それぞれ以下を表す。
-
N:name -
F:face -
C:crothes -
D:display
4文字目で、人Aか人Bかを表す。
モデルファイルの作り方
自分には絵を描くのは難しいので、既存の画像を GIMP で加工してモデルを作成した。
具体的には、CC0 と主張しているサイトから、以下の画像を用いた。
- [フリーイラスト] 学生服姿の中学生のカップル | パブリックドメインQ:著作権フリー画像素材集
- [フリー写真] 拳を握るサラリーマンの全身姿 | パブリックドメインQ:著作権フリー画像素材集
- [フリー写真] 晴れ着姿の女性の全身ショット | パブリックドメインQ:著作権フリー画像素材集
- [フリー写真] 振袖姿で笑顔の女性ポートレイトでアハ体験 | GAHAG | 著作権フリー写真・イラスト素材集 - GAHAG | 著作権フリー写真・イラスト素材集
各画像の作成後、TweakPNG を用いてモデルを構成する各チャンクを追加した。
このツールでプライベートチャンクを追加するには、チャンクのデータの前にチャンク名を書く形式の .chunk ファイルをインポートする。
画像ファイルからこのファイルを作るのには、バイナリエディタ TSXBIN を用いた。
なお、テキストデータを格納するチャンク用の .chunk データは、テキストエディタで作ることができる。
リバーシのコマを置く位置を決めるアルゴリズム
今回は、リバーシのコマを置く位置を決めるための、以下のアルゴリズムを用意した。
ランダム着手
現在置ける位置の中から、均等にランダムで選ぶ。
モンテカルロ法
現在置ける各位置について、それぞれ以下を行う。
- 自分がその位置に置く
- それ以降の対局を互いにランダムに置いて最後まで行う
- 2を何度も繰り返す
対局の結果の合計が一番良かった位置を選ぶ。
ミニマックス法
現在置ける各位置について、再帰的に調査を行う。
事前に決めた深さになったか、決着がついた (両者ともパスになった) 場合は、そのときの盤面の評価を行い、その結果を返す。
それ以外のときは、そのときの手番にとって最も良い手を選んで返す。
最も良い手が複数ある場合は、その中からランダムに選んで返す。
今回は、決着がついた際は単純に各コマの個数を評価とし、そうでないときは盤上の位置ごとに適当に決めたスコアのうち、コマがある場所のものの和を評価とすることにした。
外部からリバーシのコマを置く位置をAPIで取得する
用意したアルゴリズム以外のアルゴリズムも使用できるようにするため、APIを用いて外部のアプリケーションにリバーシのコマを置く位置を決めさせることもできるようにした。
これは、以下のように行う。
この機能を用いる際は、アクセスに用いるURLを対局設定画面で指定する。
リバーシのコマを置く位置を決めてもらう際、指定された URL に、HTTP(S) の POST リクエストを行う。
このときのリクエストボディは application/x-www-form-urlencoded 形式で、以下の情報を送信する。
-
board:現在の盤面。上の行から下の行、同じ行なら左から右の順で、以下の文字で表現する (64文字)-
0:空きマス -
1:先手のコマが置かれたマス -
2:後手のコマが置かれたマス
-
-
you:要求している手番かを1文字で表現する-
1:先手 -
2:後手
-
レスポンスは2文字のプレーンテキストで、
- 1文字目:コマを置く列 (
0(一番左) ~7(一番右)) - 2文字目:コマを置く行 (
0(一番上) ~7(一番下))
を表す。
情報の永続化
ページを閉じても情報が消えないようにするため、今回は2種類のAPIを用いた。
localStorage
localStorage を用いると、文字列をキーとして文字列をWebブラウザに保存できる。
保存した内容は、オリジン (プロトコル・ホスト名・ポート番号の組) ごとに管理される。
読み書きは同期式で行う。
今回は、対局設定の保存に用いる。
対局を開始した際に、その対局の設定をJSONにエンコードして保存する。
IndexedDB
IndexedDB を用いると、幅広いデータ形式のオブジェクト (構造化複製可能なもの) をブラウザに保存できる。
保存したデータは、全て取り出す・キーを指定して取り出す、などの操作ができる。
読み書きはイベントを用いた非同期式で行う。
今回は、ユーザーが追加したモデルデータの保存に用いる。
使わないモデルを読み込まなくていいよう、「モデルの名前とキー」と「モデルデータ」を分けて保存する。
おわりに
最初のモデルになる画像は、今年の5月頃に作った。
しかし、自分はクソ頭が悪く、スケジューリングがクソ下手くそなので、そのまま進捗無く放置してしまっていた。
そこで、こうやってアドベントカレンダーの枠を確保し、そこで発表することにすることで、動く形にすることを狙った。
「1221」と挟んでいるような形になり、リバーシのネタを書くにはいい日だと思ったので、12月21日を選んだ。
結果は成功。
確保した枠の数日前から実装を進め、こうして見事動く形にすることに成功した。
とはいえ、それでも逆に言えば数日前になるまで動かなかったわけであり、本当にクソ頭が悪くてクソ情けない。
今回、自分には絵を描くのは難しいため、既存の画像を加工することでモデルを作成した。
絵が描ける人は、自分で絵を描くことでモデルを作っても面白いかもしれない。
今回は手動で画像ファイルをチャンク化してモデルファイルを組み立てたが、気が向いたら名前や各種画像ファイルを指定し、確定操作をするだけでモデルファイルを作ることができるツールを作りたい。



