web-dev-qa-db-ja.com

ボードゲームをモデリングするためのパターンはありますか?

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

私がこれをしている間、私は多くのボードゲームに共通していると私が思う問題を常に見ています。

(ゲームをプレイするためのAIと、高パフォーマンスに関するパターンは、私には興味がないことに注意してください。)

これまでのところ私のパターンは:

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

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

  • ゲームの状態を表すオブジェクト:プレイヤー、ターンの方向、ボード上のピースのレイアウトなど。

  • ターンシーケンスを管理するステートマシン。たとえば、多くのゲームには小さなプレゲームがあり、各プレーヤーがロールして誰が先に行くかを確認します。それが開始状態です。プレーヤーのターンが始まると、最初に転がり、次に移動し、次に所定の位置で踊らなければなりません。次に他のプレーヤーが自分の鶏の種類を推測し、ポイントを獲得します。

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

編集:最近気付いたことの1つは、ゲームの状態を2つのカテゴリに分割できることです。

  • ゲームアーティファクトの状態。 「10ドル持っている」または「左手が青くなっている」。

  • ゲームシーケンスの状態。 「私はダブルスを2回​​振りました。次の1つは私を刑務所に入れます」。ステートマシンはここで意味をなすかもしれません。

編集:ここで本当に探しているのは、チェスやスクラブルなどのマルチプレイヤーターンベースゲームを実装するベスト方法ですまたは独占。このようなゲームを最初から最後まで作業するだけで作成できると確信していますが、他のデザインパターンと同様に、物事をよりスムーズに進める方法がいくつかあります。それが私が望んでいることです。

91
Jay Bazuzi

これは今気付いた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 :: Applyファミリがゲーム状態を直接変更する唯一のものである)、ゲームを確実にします。状態が無効になることはありません。さらに、コマンドパターンを使用すると、プレーヤーの希望する動きをシリアル化し、ネットワーク経由で送信して、他のプレーヤーのゲーム状態で実行することができます。

結局、かなり洗練されたソリューションを持つことが判明した、このシステムで1つの問題が発生しました。アクションには2つ以上のフェーズがある場合があります。たとえば、プレーヤーはモノポリーの物件に着地し、新しい決定を下さなければならない場合があります。プレーヤーがサイコロを振ってから、プロパティを購入するかどうかを決定するまでのゲームの状態は何ですか?ゲーム状態の「アクションコンテキスト」メンバーをフィーチャーすることで、このような状況を管理しました。アクションコンテキストは通常​​nullであり、ゲームが現在特別な状態にないことを示します。プレーヤーがサイコロを転がし、サイコロの転がりアクションがゲームの状態に適用されると、プレーヤーが所有されていないプロパティに到達したことがわかり、プレーヤーのインデックスを含む新しい「PlayerDecideToPurchaseProperty」アクションコンテキストを作成できます。決定を待っています。 RollDiceアクションが完了するまでに、ゲームの状態は、指定されたプレイヤーがプロパティを購入するかどうかを決定するのを待っていることを表します。 「BuyProperty」アクションと「PassPropertyPurchaseOpportunity」アクションを除いて、他のすべてのアクションのIsLegalメソッドがfalseを返すのは簡単です。これらは、ゲームの状態が「PlayerDecideToPurchaseProperty」アクションコンテキストを持つ場合にのみ有効です。

アクションコンテキストを使用することで、ボードゲームのライフタイムに単一のポイントが存在することは決してありません。その場合、ゲームの状態構造は、その時点でゲームで起こっていることを完全に正確に表しません。これは、ボードゲームシステムの非常に望ましい特性です。 1つの構造だけを調べることで、ゲームで何が起こっているかについて知りたいことがすべて見つかれば、コードを記述しやすくなります。

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

これが簡潔で役に立ちましたら幸いです。

111
Andrew Top

ゲームエンジンの基本構造では、 状態パターン を使用します。ゲームボックスのアイテムは、さまざまなクラスの singletons です。各状態の構造は Strategy Pattern または The Template Method を使用できます。

Factory を使用して、別のシングルトンであるプレーヤーのリストに挿入されるプレーヤーを作成します。 GUIは オブザーバーパターン を使用してゲームエンジンを監視し、 コマンドパターン を使用して作成されたいくつかのコマンドオブジェクトの1つを使用してこれと対話します。オブザーバーとコマンドの使用は パッシブビュー のコンテキストで使用できますが、好みに応じてほぼすべてのMVP/MVCパターンを使用できます。ゲームを保存するとき、現在の状態の memento を取得する必要があります

この site のパターンのいくつかを調べて、それらのいずれかが開始点としてあなたをつかむかどうかを確認することをお勧めします。繰り返しになりますが、ゲームボードの中心はステートマシンです。ほとんどのゲームは、ゲーム/セットアップ前の2つの状態と実際のゲームで表されます。ただし、モデル化するゲームにいくつかの異なるプレイモードがある場合は、より多くの状態が可能です。たとえば、ウォーゲームのAxis&Battlesには、プレイヤーがバトルを解決するために使用できるバトルボードがあります。したがって、ゲーム前、メインボード、バトルボードの3つの状態があり、ゲームはメインボードとバトルボードを継続的に切り替えます。もちろん、ターンシーケンスはステートマシンで表すこともできます。

18
RS Conley

ポリモーフィズムを使用した状態ベースのゲームの設計と実装を終えました。

重要なメソッドが1つあるGamePhaseという基本抽象クラスを使用する

_abstract public GamePhase turn();
_

これが意味することは、すべてのGamePhaseオブジェクトがゲームの現在の状態を保持し、turn()を呼び出すとその現在の状態が確認され、次のGamePhaseが返されます。

各具象GamePhaseには、全体ゲームの状態を保持するコンストラクターがあります。各turn()メソッドには、ゲームルールが少し含まれています。これはルールを広げますが、関連するルールを互いに近づけます。各turn()の最終結果は、次のGamePhaseを作成し、完全な状態で次のフェーズに渡しています。

これにより、turn()が非常に柔軟になります。ゲームによっては、特定の状態がさまざまなタイプのフェーズに分岐することがあります。これは、すべてのゲームフェーズのグラフを形成します。

最高レベルでは、それを駆動するコードは非常に単純です。

_GamePhase state = ...initial phase
while(true) {
    // read the state, do some ui work
    state = state.turn();
}
_

これは非常に便利ですテスト用にゲームの状態/フェーズを簡単に作成できます

質問の2番目の部分に答えるために、これはマルチプレイヤーでどのように機能しますか?ユーザー入力が必要な特定のGamePhases内で、turn()からの呼び出しは、現在の状態/フェーズを指定して、現在のPlayerStrategyを尋ねます。 Strategyは、Playerが行うことができるすべての決定の単なるインターフェースです。この設定では、StrategyをAIで実装することもできます。

また、Andrew Topは言った:

プレーヤーAが持っている金額、プレーヤーBが持っている金額などの順列が原因で、ゲームはおそらく無限に近い状態になる可能性があります...したがって、あなたが望むと確信していますステートマシンに近づかないようにします。

この文は非常に誤解を招くと思いますが、ゲームの状態はさまざまですが、ゲームのフェーズはごくわずかです。彼の例を処理するために、それは私の具体的なGamePhasesのコンストラクターへの整数パラメーターです。

独占

GamePhasesの例は次のとおりです。

  • GameStarts
  • PlayerRolls
  • PlayerLandsOnProperty(FreeParking、GoToJail、Goなど)
  • PlayerTrades
  • PlayerPurchasesProperty
  • プレーヤー購入住宅
  • プレーヤー購入ホテル
  • PlayerPaysRent
  • PlayerBankrupts
  • (すべてのチャンスとコミュニティチェストカード)

そして、ベースGamePhaseのいくつかの状態は次のとおりです。

  • 選手一覧
  • 現在のプレイヤー(ターン)
  • プレイヤーのお金/財産
  • プロパティの家/ホテル
  • プレーヤーの位置

そして、いくつかのフェーズは必要に応じて独自の状態を記録します。たとえば、PlayerRollsはプレーヤーが連続するダブルをロールした回数を記録します。 PlayerRollsフェーズを終了すると、連続したロールは必要なくなります。

多くのフェーズを再利用およびリンクできます。たとえば、GamePhaseCommunityChestAdvanceToGoは、現在の状態で次のフェーズPlayerLandsOnGoを作成し、それを返します。 PlayerLandsOnGoのコンストラクターでは、現在のプレーヤーがGoに移動し、そのお金が$ 200ずつ増加します。

15
Pyrolistical

もちろん、このトピックについては、たくさんの、たくさんの、たくさんの、たくさんの、たくさんの、たくさんのリソースがあります。しかし、私はあなたがオブジェクトを分割する正しい道を進んでいて、それらにそれ自身のイベント/データなどを処理させると思います。

タイルベースのボードゲームを行うとき、他の機能に沿って、ボードアレイと行/列との間をマップするルーチンがあると便利です。 boardarray 5から行/列を取得する方法に苦労したときの(かなり昔の)最初のボードゲームを覚えています。

1  2  3  
4 (5) 6  BoardArray 5 = row 2, col 2
7  8  9  

ノスタルジー。 ;)

とにかく http://www.gamedev.net/ は情報を得るための良い場所です。 http://www.gamedev.net/reference/

8
Stefan

私がオンラインで見つけることができる資料の多くは、公開された参考文献のリストです。 ゲームデザインパターン のパブリケーションセクションには、PDF記事と論文のバージョンへのリンクがあります。これらの多くは ゲームのデザインパターンのような学術論文のように見えます。 。アマゾンから入手できる本も少なくとも1冊 Patterns in Game Design もあります。

5
Eric Weilnau

Three Rings はLGPLを提供していますJavaライブラリ。NenyaとVilyaはゲーム関連のライブラリです。

もちろん、あなたの質問があなたが持っているかもしれないプラットフォームや言語の制限、あるいはその両方について言及していれば助けになるでしょう。

3
jmucchiello

私はPyrolisticalの答えに同意し、彼のやり方を好む(私は他の答えを抜粋しただけだ)。

偶然にも彼の "GamePhase"ネーミングも使用しました。基本的に、ターンベースのボードゲームの場合は、Pyrolisticalによって言及されているように、GameStateクラスに抽象GamePhaseのオブジェクトを含めます。

ゲームの状態は次のとおりです:

  1. ロール
  2. 動く
  3. 購入する/購入しない
  4. 刑務所

各状態に対して具体的な派生クラスを持つことができます。少なくとも以下のための仮想関数を持っている:

StartPhase();
EndPhase();
Action();

StartPhase()関数では、他のプレーヤーの入力を無効にするなど、状態のすべての初期値を設定できます。

Roll.EndPhase()が呼び出されたら、GamePhaseポインタが次の状態に設定されていることを確認してください。

phase = new MovePhase();
phase.StartPhase();

このMovePhase :: StartPhase()では、たとえば、アクティブプレーヤーの残りの移動を、前のフェーズでロールした量に設定します。

この設計が整ったら、ロールフェーズ内の「3 x double = jail」問題を整理できます。 RollPhaseクラスは、独自の状態を処理できます。例えば

GameState state; //Set in constructor.
Die die;         // Only relevant to the roll phase.
int doublesRemainingBeforeJail;
StartPhase()
{
    die = new Die();
    doublesRemainingBeforeJail = 3;
}

Action()
{
    if(doublesRemainingBeforeJail<=0)
    {
       state.phase = new JailPhase(); // JailPhase::StartPhase(){set moves to 0};            
       state.phase.StartPhase();
       return;
    }

    int die1 = die.Roll();
    int die2 = die.Roll();

    if(die1 == die2)
    {
       --doublesRemainingBeforeJail;
       state.activePlayer.AddMovesRemaining(die1 + die2);
       Action(); //Roll again.
    }

    state.activePlayer.AddMovesRemaining(die1 + die2);
    this.EndPhase(); // Continue to moving phase. Player has X moves remaining.
}

私がPyrolisticalと異なるのは、プレイヤーがコミュニティのチェストなどに着地したときを含め、すべてのフェーズがあるはずです。これはすべてMovePhaseで処理します。これは、シーケンシャルフェーズが多すぎると、プレーヤーが「ガイドされすぎ」ていると感じる可能性が高いためです。たとえば、プレイヤーが物件のみを購入し、ホテルのみを購入し、次に家のみを購入できるフェーズがある場合、それは自由がないようなものです。これらすべてのパーツを1つのBuyPhaseにスラムして、プレイヤーが自由に購入できるようにします。 BuyPhaseクラスは、どの購入が合法かを簡単に十分に処理できます。

最後に、ゲームボードについて説明します。 2D配列は問題ありませんが、タイルグラフを使用することをお勧めします(タイルはボード上の位置です)。独占の場合、それはむしろ二重にリンクされたリストになるでしょう。次に、すべてのタイルに:

  1. previousTile
  2. nextTile

したがって、次のようなことを行う方がはるかに簡単です。

While(movesRemaining>0)
  AdvanceTo(currentTile.nextTile);

AdvanceTo関数は、ステップバイステップのアニメーションまたは好きなものをステップごとに処理できます。もちろん、残りの動きも減らします。

GUIのオブザーバーパターンに関するRS Conleyのアドバイスは優れています。

以前はあまり投稿していません。これが誰かを助けることを願っています。

2
Reasurria

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

質問が言語またはプラットフォーム固有ではない場合。次に、状態、メメント、コマンドなどのAOPパターンを検討することをお勧めします。

AOPに対する.NETの回答は何ですか?

http://www.chessbin.com などのクールなWebサイトも見つけてください。

1
zotherstupidguy