いものやま。

雑多な知識の寄せ集め

「BirdHead」を遊べるようにしてみた。(その2)

昨日はデッキの実装をした。

今日はモデル全体の設計を書いていきたいと思う。

モデル全体の構成をどうするか?

YWFを作っていたときは、モデル全体の構成をどうすればいいかは、簡単だった。
すなわち、目に見えるボードがあって、プレイヤーがいて、プレイヤーはボードの情報からアクションを選択して、ボードに対してアクションを実行する、と。

ただ、これが「BirdHead」のようなカードゲームになると、ちょっと面倒。
というのも、

  • 目に見える「ボード」というものがない。
  • プレイヤーによって見えている情報が違う。

というのがあるから。

検討案、その1

一つの方法は、ゲームを構成する各要素ごとにオブジェクトを用意して、オブジェクト同士が連携してゲーム全体の情報を保ち、そしてプレイヤーから参照/操作できる情報もコントロールするという方法。

具体的には、

  • デッキ
  • 各自の手札
  • トリックでプレイされたカード
  • すでに使われたカード

といったオブジェクトをそれぞれ用意して、プレイヤーは自分のアクセスできるオブジェクトへの参照を持ち、場合によってはオブジェクトを操作もする、といった感じ。

ただ、こうして要素を分解した場合、各要素が持つ情報の整合性をちゃんと保ち続ける必要がある。
例えば、あるプレイヤーが(プログラミングミスで)うっかり手札を使ったのに使っていないことにしてしまったり、あるいは持ってもいない手札を使ってしまったときに、もしその手札の情報がプレイヤーのprivateでしか管理されていないとかになると、整合性のチェックを行うことが困難になる。

検討案、その2

一方、中央集権的にゲームの情報を一箇所にまとめておいて、プレイヤーはその情報に「節度を守りながら」アクセスしてアクションを選択し、ボードに対してアクションを実行する、という方法も考えられる。
この場合、その中央集権的に情報を管理しているクラスが情報の整合性を保ち続けるようにすればいいだけなので、情報がいろんなオブジェクトに散らばる場合に比べて、プログラミングのミスは起こりにくくなる。(起きたとしても原因が調べやすい)

ただ、こうした場合、今度は「節度を守りながら」アクセスするというのが重要になる。
よくゲームをしているときに、AIがまるで「プレイヤーの手札を知っている」かのように行動して、「おぃおぃ、これ、内部で絶対プレイヤーの手札見てるだろ」と思うことがあるけど、実際に見ているかどうかはともかく、自分でAIを作るときには、本当なら見えるはずもないカードも参照して手を決めるようなことがあってはならないわけで。
けど、中央集権的に情報が集まっているなら、そこを参照できる限り、うっかり見てしまうという可能性がないこともない。
それを防ごうとするなら、プレイヤーが情報へアクセスするときの実装をそれなりに工夫する必要が出てくる。

自分のとった方法

そこで、自分がどのような方法をとったのかというと、基本的にはゲームの情報は中央集権的に管理して、ただし、プレイヤーはその情報に直接アクセスできるわけではなく、その情報から作られた「プレイヤー・ビュー」というオブジェクトにのみアクセス可能とする方法。
「プレイヤー・ビュー」にはそのプレイヤーから見える情報だけが抜粋されることになる。

さらに、プレイヤーはゲーム情報に対して直接操作をするわけではなく、ゲーム情報への操作はコントローラが行うようにする。
こうすることで、プレイヤーがゲーム情報に直接アクセスしてしまうということを防ぐことが出来る。

図にすると、次のような感じ。

f:id:yamaimo0625:20151007145738p:plain

モデル変更の通知

もう一つ。

YWFを作っていたときには、ビューのBoardNodeクラスが内部にモデルのBoardクラスを保持して、ボードに対して何か操作があった場合にはBoardNodeクラスに登録されていたBoardNodeObserverに対して通知を行っていたけれど、実際にはこれはちょっと微妙・・・
本来ならモデルのBoardがオブザーバを管理して、ビューのBoardNodeもそのオブザーバの一つとなり、モデルが変化したときにはモデルからの通知によって表示も更新する、とするのがMVCパターンの定跡。
(YWFの場合、モデルのBoardが不変なオブジェクトだったので、このような作りにする場合、一枚ラッパが必要だった。そのラッパの役割をビューのBoardNodeが果たしたとも言える)

そこで、ゲーム情報のクラスにはオブザーバを登録できるようにして、ゲーム情報に変更があった場合、オブザーバに通知が行くようにする。
このオブザーバは、ビューであったり、コントローラであったり。

先ほどの図に付け足すと、次のような感じになる。

f:id:yamaimo0625:20151007150439p:plain

明日以降は、この図に出てくる各要素を、それぞれ

  • アクション Action列挙型
  • プレイヤー Playerプロトコル
  • オブザーバ GameInfoObserverプロトコル
  • ゲーム情報 GameInfoクラス
  • プレイヤー・ビュー GameInfo.PlayerViewクラス
  • コントローラ GameControllerクラス

として実装していく。

今日はここまで!