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


93

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

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

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

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

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

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

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

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

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

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

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

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

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


3
あなたはある種のホーキーポーキー、モノポリー、シャレードマッシュアップを構築していますか?
Anthony Mastrean 2008

Monopolyの3つのdoubleルールのように、状態に依存するすべてのルール(err ...)にステートマシンが必要になります。より完全な回答を投稿しますが、これを行った経験はありません。私はそれについてさらに理解することができました。
-MSN、

回答:


115

これは今気付いた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つの構造だけを調べることで、ゲームで何が起こっているかについて知りたいことがすべて見つかれば、コードを記述しやすくなります。

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

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


4
簡潔ではないと思いますが、役に立ちました!賛成。
ジェイバズジ09

それが役に立ててうれしい...どの部分が簡潔ではなかったのですか?説明の編集をさせていただきます。
Andrew Top

私は今ターンベースのゲームを構築しており、この投稿は本当に役に立ちました!
Kiv

Mementoは元に戻すために使用するパターンであることを読みました...元に戻すためのMemento対コマンドパターン、あなたの考え
plz

これは、これまでStackoverflowで読んだ最良の答えです。ありがとう!
パピポ

18

ゲームエンジンの基本構造は、状態パターンを使用します。ゲームボックスのアイテムは、さまざまなクラスのシングルトンです。各状態の構造には、戦略パターンまたはテンプレートメソッドを使用できます。

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

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


15

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

GamePhase1つの重要なメソッドを持つ、呼び出された基本抽象クラスを使用する

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番目の部分に答えるために、これはマルチプレイヤーでどのように機能しますか?一定の範囲内GamePhaseのユーザー入力を必要とsの、からの呼び出しはturn()、現在の求めるだろうPlayer彼らのStrategy与えられた現在の状態/位相を。 Strategyは、可能なすべての意思決定のインターフェースにすぎませPlayerん。このセットアップではStrategy、AIを使用して実装することもできます。

また、Andrew Topは言った:

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

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

独占

一部GamePhaseのsの例は次のとおりです。

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

そしてベースのいくつかの州GamePhaseは次のとおりです:

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

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

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


8

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

タイルベースのボードゲームを行う場合、他の機能に沿って、ボードアレイと行/列との間をマップするルーチンがあると便利です。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/


なぜ2次元配列を使用しないのですか?その後、コンパイラがこれを処理します。
Jay Bazuzi、2008

私の言い訳は、これがずっと前のことだったということです。;)
Stefan

1
gamedevには大量の機能がありますが、探しているものがあまりわかりませんでした。
ジェイバズジ09

どの言語を使用しましたか?
zotherstupidguy 09/10/23

Basic、Basica、QB、QuickBasicなど。;)
ステファン、

5

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


3

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

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


「最終的には、WPF UIを構築する予定です」-.NETを意味します。少なくとも、私の知る限りでは。
マークアレン

知らないアルファベットスープ。
jmucchiello 2008

ええ、私は.NETをやっていますが、私の質問は言語やプラットフォーム固有ではありません。
Jay Bazuzi、2008

2

私は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のアドバイスは優れています。

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


1

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

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

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

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

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.