1. ホーム
  2. design-patterns

[解決済み] ボードゲームのモデリングパターンを教えてください。[終了しました]

2023-01-31 22:39:31

質問

楽しみながら、息子のお気に入りのボードゲームの1つをソフトウェアとして書こうとしています。 最終的には、その上に WPF UI を構築することを期待していますが、今はゲームとそのルールをモデル化するマシンを構築しているところです。

そうしているうちに、多くのボードゲームに共通すると思われる問題を見続けていますが、おそらく他の人がすでに私よりもうまく解決していることでしょう。

(なお、ゲームをプレイするためのAIや、高性能にまつわるパターンなどは、私にとって興味深いものではありません)。

今のところ私のパターンは

  • ゲームボックス内のエンティティを表すいくつかの不変の型。例えば、サイコロ、チェッカー、カード、ボード、ボード上のスペース、お金など。

  • 各プレイヤーのためのオブジェクトで、プレイヤーのリソース(例:お金、スコア)、名前などが含まれます。

  • ゲームの状態を表すオブジェクト。プレイヤー、誰の手番か、ボード上のピースのレイアウトなど。

  • 手番の順番を管理するステートマシンです。 たとえば、多くのゲームには、誰が最初に行くかを決めるために各プレイヤーがロールする小さなプレゲームがあります。 プレイヤーのターンが始まると、まずロールし、移動し、その場でダンスし、他のプレイヤーが鶏の種類を推測し、そしてポイントを受け取ります。

私が利用できる先行技術はありますか?

EDITです。 最近気づいたことですが、ゲームの状態は2つに分けられると思います。

  • ゲームアーチファクトの状態 . "I have $10" or "my left hand is on blue".

  • ゲームシーケンス状態 . "私は2回ダブルを出したので、次は牢屋に入れる"。 ステートマシンはここで意味をなすかもしれません。

EDITです。 ここで本当に求めているのは ベスト チェスやスクラブル、モノポリーのような多人数参加型のターンベースゲームを実装するための方法です。 しかし、他のデザインパターンと同様に、注意深く研究しなければわからないような、よりスムーズに物事を進めるための方法があるはずです。 それを期待しています。

どのように解決するのか?

これは 2 ヶ月前のスレッドで、今頃気づいたようですが、なんということでしょう。 私は以前、商用でネットワーク接続されたボード ゲームのゲームプレイ フレームワークを設計および開発したことがあります。 それを使って非常に楽しい経験をしました。

プレイヤーAがいくらお金を持っているか、プレイヤーBがいくらお金を持っているか、などの順列があるため、あなたのゲームはおそらく(ほぼ)無限の状態になる可能性があります。 したがって、ステート マシンを使用しないことをお勧めします。

私たちのフレームワークの背後にあるアイデアは、ゲームの状態を、一緒になって完全なゲームの状態を提供するすべてのデータ フィールドを持つ構造体として表すことでした (つまり、ゲームをディスクに保存したい場合は、その構造体を書き出すのです)。

私たちは コマンド パターン を使用して、プレイヤーが行うことのできる有効なゲームアクションをすべて表現しています。 以下はアクションの例です。

class RollDice : public Action
{
  public:
  RollDice(int player);

  virtual void Apply(GameState& gameState) const; // Apply the action to the gamestate, modifying the gamestate
  virtual bool IsLegal(const GameState& gameState) const; // Returns true if this is a legal action
};

つまり、ある動作が有効かどうかを判断するには、その動作を構築して、現在のゲーム状態を渡してIsLegal関数を呼び出すことができることがわかります。 もし有効であれば、そしてプレイヤーがそのアクションを確認したら、Apply関数を呼び出して実際にゲームステートを変更することができます。 ゲームプレイのコードが、合法的なActionの作成と送信によってのみゲームステートを変更できるようにすることで、(言い換えれば、Action::Applyファミリーのメソッドが、ゲームステートを直接変更する唯一のものです)、ゲームステートが決して無効でないことを確実にすることができます。 さらに、コマンドパターンを使うことで、プレイヤーの望む手を直列化し、ネットワーク経由で送信して、他のプレイヤーのゲームステートで実行させることが可能になる。

このシステムには1つの問題があり、それはかなりエレガントな解決策を持つことが判明しました。 行動には 2 つ以上の段階があることがあります。 たとえば、プレイヤーはモノポリーの土地に降り立ち、新しい決定を下す必要があります。 サイコロを振ってから、物件を買うか買わないかを決めるまでの間のゲーム状態はどうなっているのでしょうか。 このような場合、ゲームステートに「アクションコンテキスト(Action Context)」というメンバを設定することで対応しました。 通常、アクションコンテキストはNULLで、ゲームが現在特別な状態でないことを示します。 プレイヤーがサイコロを振って、サイコロを振るアクションがゲームステートに適用されると、プレイヤーは未所有のプロパティに着地したことを認識し、決定を待っているプレイヤーのインデックスを含む新しい"PlayerDecideToPurchaseProperty" アクションコンテキストを作成することができます。 RollDiceアクションが完了するまでに、ゲームステートは、指定されたプレイヤーが物件を購入するかどうか決定するのを現在待っていることを表します。 ゲーム状態が"PlayerDecideToPurchaseProperty"アクションコンテキストを持つときのみ有効な"BuyProperty"と"PassPropertyPurchaseOpportunity"を除いて、他のすべてのアクションのIsLegalメソッドが false を簡単に返すことができるようになりました。

アクション コンテキストを使用することにより、ボード ゲームのライフタイムにおいて、ゲーム ステート構造が、その時点でゲーム内で起こっていることを完全に表現していない点は決してありません。 これは、ボードゲームシステムにとって非常に望ましい特性です。 たった1つの構造体を調べるだけで、ゲームで何が起こっているのかについて知りたいことがすべてわかるのであれば、コードを書くのはずっと簡単でしょう。

さらに、ネットワーク環境にも非常にうまく拡張でき、クライアントがネットワーク経由でホスト マシンにアクションを送信すると、ホストはそのアクションをホストの公式ゲーム ステートに適用し、そのアクションを他のすべてのクライアントにエコーバックして、複製したゲーム ステートに適用させることができます。

これが簡潔で役に立ったことを願っています。