OOEulerGetterの技術的な事に関してまとめて置きます. Plugin の作成や新しい実装の参考になればとおもいます. 往々にしてこういう文書のメンテナンスは放置されます. (0.0.5での情報/0.0.7以降で変更の予定あり)
まずユーザインターフェースに依存しない, ゲームそのものを司るclassについて説明します.
ゲームを進めるにあたって, 重要な働きをするのは, 次のclass(もしくはそれを継承したclass)のinstanceです:
ゲームを進行するのは基本的にはRefereeです.
Refereeはゲームの進行に合わせて,
Player, Outsider
のメソッドを呼びゲームを進めます.
Player, Outsiderはサーバ,
Refereeはクライアントとして振る舞います.
大雑把に言うと,
Playerは,
ゲームのプレーヤに相当するオブジェクトで,
各ターンでどの手を打つかを決めます.
Outsiderは,
ログを記録するなどのためのもので,
実質的にはゲームの進行に影響は与えませんが
各ターン毎の情報は報告されます.
RefereeはPlayerクラスのオブジェクトを2つ
(先手, 後手に相当)
と,
Outsiderクラスのオブジェクトを0以上知っています.
ゲームが開始された時点でRefereeは,
Playerのstart_gameを呼び,
使用するBoardおよび先手/後手を伝えます.
また,
Outsiderのstart_gameを呼び,
使用するBoardを伝えます.
もし, 使用するBoardを扱えない場合は,
PlayerはFalse
この流れは,
Refereeのannounce_game_start
として実装されています.
各ターンでは, Refereeは,
先手/後手に合わせ, 対応するPlayerの
playを呼びます.
このメソッドを呼ぶ際に,
現在のBoardが伝えられます.
メソッドが返した値が適切か(ちゃんと置ける場所かなど)を判断した後
適切であればそのcellを置きます.
もし適切でなければRefereeが勝手に決めます.
また, timeoutが0以上の場合は, timeoutまでに値が帰ってこない場合も
Refereeが勝手に決めます.
(現状ではtimeoutはmultiprocessを使って実装しているので,
標準入力を使った入力を伴うPlayerには,
timeoutを設定できません.)
cellを追加した後,
Refereeは
すべてのPlayerとすべてのOutsiderの
playedを呼び出し,
現在のBoardとどのcellが置かれたかを伝えます.
この流れは,
Refereeの
play_turnとannounce_turn_end
として実装されています.
置けるcellが無くなったとき,
Refereeは
すべてのPlayerとすべてのOutsiderの
game_endを呼び出し,
現在のBoardと勝者を伝えます.
この流れは,
Refereeの
announce_game_end
として実装されています.
これら
(announce_game_startから
announce_game_endまでの一連の流れ)
は,
Refereeのstart_gameで呼び出せます.
ゲームに使用される盤に関して重要な役割を担っているclassは次の二つです:
Boardはゲーム盤(と勝敗のルール)を実装したものです. BoardはPolyhedronをメンバとして持っています. Polyhedronは多面体(分割)を実装したもので, 面の情報, 辺の情報, 頂点の情報, どのように描画するべきかという情報を持っています.
Boardは, 各playerのcellを持っており,
各playerの得点を計算することが主な役割です.
get_point
が呼ばれると,
PolyhedoronにEuler数を計算させ,
その値を元にplayerの得点を計算します.
もしコミ(ハンデ)を実装するのであれば,
このメソッドを拡張するだけで, よいと思います.
Polyhedronは,
多面体の実装です.
次の5つのメンバが本質的です:
facesface_as_edgesface_as_verticeslayout_hintlayout_hint_type新しいPolyhedronを使いたい時には, これらを書くだけで基本的には終了です.
facesは, facesの
(ラベル)
の集合frozensetです.
(万一の破壊的なメソッドによる不正を未然に防ぐためfrozensetを使います.)
face_as_edgesは,
各facesの
(ラベル)
に対し, それらに含まれる辺
(のラベル)
の集合
(frozenset)
を対応させるDictionaryです.
face_as_verticesは,
各facesの
(ラベル)
に対し,
それらに含まれる頂点
(のラベル)
の集合
(frozenset)
を対応させるDictionaryです.
面, 辺, 頂点は単なるラベルで構いません
(座標などを気にする必要はありません).
これだけの情報があれば, Euler数を計算するのに必要な情報は揃うからです.
layout_hintとlayout_hint_typeは,
本質的にはゲームとは関係ないのですが,
多面体を表示する際に必要となります.
前述の通り, 多面体の面などの情報はラベルで管理され,
その座標などの情報は無いため,
ユーザインタフェースとして
実際にその多面体を表示するための情報を
layout_hintとlayout_hint_typeで提供します.
デフォルトでは,
layout_hint_typeは
"multihex"です.
layout_hint_typeが
"multihex"のとき
layout_hintは,
faceのラベルのリストのリストNのリストです.
各faceのラベルのリストのリストNは,
多面体の
(ある面の近傍での)
展開図を表します.
各展開図Nは, 次の様な六角形の座標系で
どこにどの面を表示するかという情報を持ちます.
具体的には座標(i,j)に面N[i][j]を表示するべきであるという
情報を表します.
もし空白にしたい場合はNoneとします.
| (0,0) | (0,1) | (0,2) | |||||||
| (1,0) | (1,1) | (1,2) | |||||||
| (2,0) | (2,1) | (2,2) | |||||||
| (3,0) | (3,1) | (3,2) | |||||||
| (4,0) | (4,1) | (4,2) | |||||||
ゲームの各ステップにおいて
PlayerやOutsiderに伝えられる
BoardはRefereeの持っているBoardではなく,
そのコピーです.
破壊的なメソッドによる不正を未然に防ぐためです.
get_copied_boardをオーバライドするときは
その点に注意する必要があります.
ここでは, ユーザインターフェースを含めた方針を簡単に説明します.
ユーザインタフェースでは,
使用するPolyhedron, Board, Playerを選んだ後,
これらの情報を持った, Refereeを作ります.
(また, ログを残す場合やゲームの進行状況をユーザに伝える場合は,
そのためのものもOutsiderとして指定しておきます.)
そのRefereeのstart_gameを呼びゲームを進めます.
これが, 基本的な流れです. 必要に応じてこれを繰り返します.
ゲームの進行状況に応じて,
盤面を表示するためのユーザインターフェースは,
Outsiderとして実装します.
各ターンでの手を決めるためのユーザインターフェースは,
Playerとして実装します.
Event drivenなプログラミングだと少しトリックが必要かもしれません.
コンソール版のUIでは, 多面体, Board (得点の計算方法), Aiについてはプラグインとして追加可能なようにしています. プラグインの書き方について述べます.
polyhedrons,
board_factories,
ai_enginesの中にあるmoduleがプラグインとして使われます.
それぞれのディレクトリに,
適当な名前のディレクトリを作り,
__init__.py
と適当な名前の本体ファイルを置きます.
__init__.pyと本体ファイルの
どちらからもooeulergetterをimportします.
__init__.pyでは
次のメソッドを必ず実装します:
get_descriptionは辞書を返します.
プラグインを選択する際に表示する情報を,
返します.
内容は以下の通りです.
get_optionsは
文字列とget_instanceの引数として渡す値の候補とのタプルのリストです.
文字列は, 引数として渡す値の説明です.
get_instanceは,
引数を一つとります.
PlayerやPolyhedronのpluginであれば,
それらのオブジェクトを返します.
Boardのpluginであれば,
"Polyhedronに対しBoardのオブジェクトを一つ返す関数"
を返します.
以下に簡単に思いつく今後の課題を列挙します. これらは, もとのソースを変更することなく, 必要なclassを継承しオーバライドすることでも実装可能です. 私はつかれたので手を出さないかもしれない.
[TOP Page]