コード全体で1回限りのフラグとチェックを回避するにはどうすればよいですか?


17

Hearthstoneなどのカードゲームを検討してください。

多種多様なことを行う何百ものカードがあり、そのうちのいくつかは単一のカードにさえユニークです!例えば、プレイヤーのターンをわずか15秒に短縮するカード(Nozdormuと呼ばれる)があります!

このようなさまざまな潜在的な効果がある場合、コード全体でマジックナンバーと1回限りのチェックを回避するにはどうすればよいですか?PlayerTurnTimeクラスの「Check_Nozdormu_In_Play」メソッドをどのように回避できますか?そして、さらに多くの効果を追加するときに、以前はサポートする必要がなかったものをサポートするためにコアシステムをリファクタリングする必要がないように、どのようにコードを編成できますか?


これは本当にパフォーマンスの問題ですか?私は..あなたはほとんど時間がないの近代的なCPUを搭載したものの狂気の量を行うことができ、意味
ヤリKomppa

11
パフォーマンスの問題についてだれが言いましたか?主な問題は、新しいカードを作成するたびにすべてのコードを常に調整する必要があることです。
ジョッキング

2
スクリプト言語を追加し、各カードのスクリプトを作成します。
ジャリコンパ

1
適切な回答をする時間はありませんが、プレイヤーのターンを処理する「PlayerTurnTime」クラスコード内で、例えばNozdormuチェックと15秒の調整を行う代わりに、「Player-TurnTime」クラスをコーディングして[class- ]特定のポイントで外部から提供される関数。その後、Nozdormuカードコード(および同じプランスに影響を与える必要がある他のすべてのカード)は、その調整のための関数を実装し、必要に応じてPlayerTurnTimeクラスにその関数を挿入できます。戦略パターンと依存性注入については、古典的なデザインパターンの本
Peteris

2
ある時点で、関連するコードにアドホックチェックを追加することが最も簡単な解決策であるかどうか疑問に思う必要あります。
user253751

回答:


12

エンティティコンポーネントシステムとイベントメッセージング戦略を検討しましたか?

ステータスエフェクトは、OnCreate()メソッドで永続エフェクトを適用し、OnRemoved()メソッドでエフェクトを期限切れにし、ゲームイベントメッセージにサブスクライブして、何かが発生した場合の反応として発生するエフェクトを適用できる何らかのコンポーネントである必要があります。

効果が永続的に条件付きである場合(Xターンの間持続しますが、特定の状況でのみ適用されます)、さまざまな段階でそれらの条件を確認する必要があります。

次に、ゲームにデフォルトのマジックナンバーがないことを確認します。変更できるものはすべて、例外に使用される変数のハードコードされたデフォルトではなく、データ駆動型変数であることを確認してください。

この方法では、ターンの長さがどうなるかを決して想定していません。これは常に効果が変更される可能性のある常にチェックされる変数であり、有効期限が切れると効果によって後で取り消される可能性があります。マジックナンバーにデフォルト設定する前に例外をチェックすることはありません。


2
「変更可能なものはすべて、例外に使用される変数のハードコードされたデフォルトではなく、データ駆動型変数であることを確認してください。」-ああ、私はそれがかなり好きです。それは大いに役立つと思います!
セーブルドリーマー

「それらの永続的な効果を適用する」ことについて詳しく説明していただけますか?turnStartedにサブスクライブしてからLength値を変更すると、コードがデバッグ不能になり、さらに悪いことに一貫性のない結果が生成されます(同様の効果間で相互作用する場合)。
ウォンドラ

特定の期間を想定するサブスクライバーのみ。慎重にモデル化する必要があります。現在のターンタイムをプレイヤーのターンタイムと異なるようにすると良いかもしれません。PTTをチェックして、新しいターンを作成します。CTTはカードで確認できます。エフェクトが現在の時間を延長する必要がある場合、タイマーUIは、ステートレスである場合、自然にそれに追従するはずです。
ロブストーン

より良い質問に答えるために。ターン時間やそれに基づいたものを保存するものは他にありません。常に確認してください。
ロブストーン

11

RobStoneは正しい軌道に乗っていますが、これはまさにダンジョンホー!を書いたときにやったことなので、詳しく説明したかったのです。

各カードには一連の効果が添付されている必要があります。効果のセットは、効果が何であるか、何をターゲットにするか、どのように、どれくらいの期間を示すことができるように定義されます。たとえば、「敵にダメージを与える」効果は次のようになります。

Effect type: deal damage (enumeration, string, what-have-you)
Effect amount: 20
Source: my weapon
Target: opponent
Effect Cost: 20
Cost Type: Mana

次に、エフェクトが発生したら、汎用ルーチンにエフェクトの処理を処理させます。ばかみたいに、私は巨大なcase / switchステートメントを使用しました。

switch (effect_type)
{
     case DAMAGE:

     break;
}

しかし、はるかに優れた、よりモジュール化された方法は、ポリモーフィズムを使用することです。このデータをすべてラップするEffectクラスを作成し、エフェクトのタイプごとにサブクラスを作成してから、そのクラスにクラス固有のonExecute()メソッドをオーバーライドさせます。

class Effect
{
    Object source;
    int amount;

    public void onExecute(Object target)
    {
          // Do nothing
    }
}

class DamageEffect extends Effect
{
    public void onExecute(Object target)
    {
          target.health -= amount;
    }
}

したがって、基本的なEffectクラスがあり、次にonExecute()メソッドを持つDamageEffectクラスがあります。そのため、処理コードでは先に進みます。

Effect effect = card.getActiveEffect();

effect.onExecute();

何が関係しているかを知る方法は、Vector / Array / linked list / etcを作成することです。任意のオブジェクト(プレイフィールド/「ゲーム」を含む)にアタッチされたアクティブエフェクト(タイプEffect、ベースクラス)オブジェクトを実行し、実行させます。効果がオブジェクトにアタッチされていない場合、効果はありません。

Effect effect;

for (int o = 0; o < objects.length; o++)
{
    for (int e = 0; e < objects[o].effects.length; e++)
    {
         effect = objects[o].effects[e];

         effect.onExecute();
    }
}

これはまさに私がそれをやった方法です。ここでの利点は、基本的にデータ駆動型のシステムがあり、効果ごとにかなり簡単にロジックを調整できることです。通常、エフェクトの実行ロジックでいくつかの条件チェックを行う必要がありますが、これらのチェックは問題のエフェクトのためだけであるため、より一貫性があります。
マナブレイク

1

いくつかの提案を提供します。それらのいくつかは互いに矛盾しています。しかし、おそらくいくつかは便利です。

リストとフラグを検討する

世界中で繰り返し、各アイテムのフラグをチェックして、フラグを立てるかどうかを決定できます。または、フラグ機能を実行する必要があるアイテムのみのリストを保持することもできます。

リストと列挙を検討する

ブール値フィールドをアイテムクラスisAThisおよびisAThatに追加し続けることができます。または、{“ isAThis”、“ isAThat”}または{IS_A_THIS、IS_A_THAT}のような文字列または列挙要素のリストを作成できます。そうすれば、フィールドを追加せずに列挙(または文字列定数)に新しいものを追加できます。フィールドの追加に本当に問題があるわけではありません...

関数ポインターを考慮する

フラグまたは列挙のリストの代わりに、異なるコンテキストでそのアイテムに対して実行するアクションのリストを含めることができます。(実体のような…)

オブジェクトを考慮する

一部の人々は、データ駆動、スクリプト、またはコンポーネントエンティティのアプローチを好んでいます。しかし、昔ながらのオブジェクト階層も検討する価値があります。基本クラスは、「このカードをターンフェーズBでプレイする」などのアクションを受け入れる必要があります。その後、各種類のカードが必要に応じてオーバーライドおよび応答できます。おそらくプレーヤーオブジェクトとゲームオブジェクトも存在するため、ゲームはif(player-> isAllowedToPlay()){do the play…}のようなことを実行できます。

デバッグ機能を検討する

フラグフィールドの山の良い点は、すべてのアイテムの状態を同じ方法で調べて印刷できることです。状態が異なるタイプ、コンポーネントのバッグ、または関数ポインターで表される場合、または異なるリストにある場合、アイテムのフィールドを見るだけでは不十分な場合があります。すべてのトレードオフです。

最終的に、リファクタリング:単体テストを検討する

アーキテクチャをどれだけ一般化しても、それがカバーしていないものを想像することができます。次に、リファクタリングする必要があります。たぶん少し、多分たくさん。

これをより安全にする方法は、単体テストの本体を使用することです。そうすることで、下にあるものを再配置したとしても(多くの場合!)、既存の機能が引き続き機能することを確信できます。通常、各ユニットテストは次のようになります。

void test1()
{
   Game game;
   game.addThis();
   game.setupThat(); // use primary or backdoor API to get game to known state

   game.playCard(something something).

   int x = game.getSomeInternalState;
   assertEquals(“did it do what we wanted?”, x, 23); // fail if x isn’t 23
}

ご覧のとおり、ゲーム(またはプレイヤー、カード、&c)でのトップレベルAPI呼び出しを安定させることは、単体テスト戦略の鍵です。


0

各カードを個別に考えるのではなく、効果のカテゴリの観点から考え始めると、カードにはこれらのカテゴリが1つ以上含まれます。たとえば、1ターンの時間を計算するには、プレイ中のすべてのカードをループして、そのカテゴリを含む各カードの「ターン期間の操作」カテゴリを確認します。その後、各カードは、決定したルールに基づいてターン時間をインクリメントまたは上書きします。

これは基本的にミニコンポーネントシステムであり、各「カード」オブジェクトは単にエフェクトコンポーネントの束のコンテナです。


カード-そして将来のカードも-ほぼ何でもできるので、各カードにはスクリプトが含まれることが期待されます。それでも私はかなり確信して、これが本当のパフォーマンス上の問題ではありません..ですよ
ヤリKomppa

4
主なコメントによると:誰も(あなた以外)パフォーマンスの問題について何も言っていません。代替手段としてのフルスクリプトについては、回答で詳しく説明します。
ジョッキング
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.