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つのメンバが本質的です:
faces
face_as_edges
face_as_vertices
layout_hint
layout_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]