共通の基本クラスの複数の「コピー」インスタンスが必要なときの構造は?

それで、私はそれぞれのカードがコストダメージ耐久性、そして namedescription 明らかに Card クラスを作成することを考えるでしょう:

class Card {
    int cost
    int damage
    int durability
    string name
    string decription
}

werewolf = Card(5, 10, 120, 'Werewolf', 'Deal double damage during night')
mage = Card(7, 40, 40, 'Mage', 'Deal damage without taking damage himself')

しかし今問題は、これらのインスタンスのそれぞれの複数のコピーが必要です。ゲームのすべてのプレイヤーは、各カードを2枚ずつ持つことができます。つまり、各カードを8枚まで持つことができます。

一部の属性は「静的」、つまり namedescriptioncost など、すべての類似インスタンスと常に同じです。しかし、どのインスタンスも healthdamage に多様性を持っています。 1人の魔術師は、バフ呪文が彼女に唱えられた後により多くのダメージを与えることになるかもしれません、そしてもう1人の魔術師はダメージを受けた後により低いヘルスを持つ。

そのような機能を実装し構造化するための良い方法は何でしょうか。サブクラスか何か、そしてどうやって?

3
nl ru de
サブクラス化を避けることを目的としたプロトタイプパターンのように思えます。
追加された 著者 Jason Young,
この質問に対する答えは、使用している言語によって異なりますので、この情報を追加すると便利です。 (しかし、簡単に言うと、Javascriptなど、プロトタイプベースの継承を提供する言語に非常に適しています。共通の詳細を持つ単一の基本カードを作成し、さまざまな詳細を持つオブジェクトのプロトタイプとして使用します。) 。
追加された 著者 Periata Breatta,
インスタンス化されたカードは変わりますか?それとも、ゲームを通して静的なのか? IEは狼男は常に5のコストがありますか?
追加された 著者 zzr,
@WindRaven一部の属性は静的ですが、一部の属性は変更されることがあります(ヘルスやダメージなど、絶えず変化します)。私はまだマナを変える必要はないが、理論的には「敵のカードを自分の持っていたカードに投げ戻す、それは 1 以上かかる」といったカードがあるかもしれない
追加された 著者 Pallen,

4 答え

特にゲームでは、データを共有するためだけにサブクラス化を使用することはお勧めできません。利点がなくても、より複雑なオブジェクトモデルが得られます。

考えられる戦略の1つは、カードの作成を管理する工場を追加することです。それは複製を自動化します。

あなたの変化する特性に関しては、これをどのように管理することができるかというさまざまな方法があります。ほとんどの場合、変化するプロパティがカードのインスタンス変数として永続化されず、ゲームコンテキストからオンザフライで計算されるのがおそらく最善です。特にステータス効果がキャンセルされた場合(例えば、数ラウンド後)、全てのカードステータスを以前の値にリセットすることは、そうでなければ面倒です。

ゲームユニットの「クラス」が共通の特性を共有するという観察は非常に一般的です。 型オブジェクト を導入することがおそらく最善の解決策です。デフォルトの統計と静的な説明を格納するカードタイプからプロパティを変更して各カードインスタンスを分離します。

class CardType {
  int cost
  int damage
  int durability
  string name
  string description
}

class Card {
  CardType type
  int damage
  int durability
 //perhaps add getters/properties for the fields from the type object here
}

継承の代わりに合成を使用すると、システムに多くの柔軟性が追加されます。ただし、型システムの一部をランタイムオブジェクトに移動し、すべてをより動的にすることは、より複雑になり、したがってより脆弱になることを意味します - 設計においてこれらのさまざまな要素のバランスをとる必要があります。

4
追加された
@ MarkusMeskanen私はHearthStoneをプレイしたことがないので、それがどのように機能するのか正確にはわかりませんが、英雄タイプはカードタイプから独立しているように思えます。カードにさまざまな属性がある場合は、おそらくそれらにクラス階層を使用するのが最善です。あるいは、代わりに動的な言語でこれらの詳細をすべて記述して、さまざまなデータがハッシュテーブルに格納されるようにしたほうがよい場合もあります。これをどのように賢明に行うことができるかは、完全にあなたの要件とユースケースに依存します、一般的な答えはありません。
追加された 著者 amon,
デザインを完成させる前に、Cardの機能がどのように機能するのかを考えてみてください。
追加された 著者 sellandb,
構造が2レベルの深さである場合、これはどのように機能しますか? HearthStone のように、それぞれヒーローがいますこれには health のような固有の属性とそのカードのリストがありますが、 name のような共通の属性もあります。しかし、それから異なる種類のカードがあり、それぞれがユニークな属性とユニークでない属性を持っていますか?
追加された 著者 Pallen,

私は問題の特定の領域についての経験がありませんが、うまくいけば少なくとも少し理にかなっているいくつかのアイデアを共有させてください。

Regardless of the problem domain, a good principle of software design is to encapsulate what varies.

不変であるすべてのカードの基本的な特性のセットと確かに不変である別の特性のセットがあるようです。

注:これは、おそらくこの特定のドメインにおけるCardの抽象化が十分に明確ではないことを示しています。おそらくあなたはまだより適切な抽象概念を見つける必要があります。ボックスの外側を考えるか、問題の分野をよりよく理解すると、不変の特性を持つ抽象化は可変のものを持つ抽象化と同じではないという結論におそらく達するでしょう(例:Card vs Game vs MoveまたはDraw)

不変の特性はカード自体に固有のものであるが、可変の特性はカードがプレイされたゲームの特定のインスタンス(すなわち、ゲームイベント、ゲームの移動、カードの引き分けなど)に相関すると思われる。

たとえば、私はWarewolfカードのヘルスが5であることを知っていますが、ゲームのすべてのMove/Event/Drawを記録する場合、イベントストリームを再適用することによって、特定のWarewolfカードの現在のヘルスを再計算できます。

class Game {
    List moves;
}

移動できる場所

class Move {
  Player player;
  Card card;
}

あなたがカードをプレイするたびに、あなたはあなたのリストに移動イベントを追加します。世界の現在の状態を知りたいのであれば、移動リストを再適用することで予測を導き出すことができます。パフォーマンス上の理由から、ゲームが終了したらそれを破棄して最初からやり直すことができるという利点があり、 Game 内の他のデータ構造で最新の予測を保持することを決定することができます。

Map

ゲームは完全に不変のデータ構造を使用して完全に機能的な方法で考えることができ、恐らくドローやカードの形で一連のイベントとして表現され、イベントが起こるにつれて新しい世界の状況は定義済み。

私は何か意味がありますか?

1
追加された

I wouldn't make subclasses if only the data differs. Store initial copies somewhere, such as a static Map, then have the constructor take a CardType and initialize the Card with values from the initial copy.

1
追加された
@MarkusMeskanenそれでは、サブクラスを使用します。データが異なるだけではありません。
追加された 著者 Chris Norton,
構造が2レベルの深さである場合、これはどのように機能しますか? HearthStone のように、それぞれヒーローがいますこれには health のような固有の属性とそのカードのリストがありますが、 name のような共通の属性もあります。しかし、それから異なる種類のカードがあり、それぞれがユニークな属性とユニークでない属性を持っていますか?
追加された 著者 Pallen,

すべてのカードの値を固定の基本値として扱うことをお勧めします。その後、バフとデバフ用に別々のクラスを作成し、有効なダメージと健康値を計算できます。

class Stats {
    final int damage;//final = cannot be changed
    final int durability;
}

class Card {
     final String name;
     final String description;
     final Stats stats;
}

interface Effect {
    public Stats apply(Stats stats);
}

class Injury implements Effect {
    public Stats apply(Stats stats) {
        return new Stats(stats.getDamage(), stats.getDurability() - 10);
    }
}

class DamageBuff implements Effect {
    public Stats apply(Stats stats) {
        return new Stats(stats.getDamage() * 2, stats.getDurability());
    }
}

public void example() {
    Card card = ...;
    List effects = ...;

    Stats stats = card.getStats();
    for (Effect effect : effects) {
        stats = effect.apply(stats);
    }

    int effectiveDamage = stats.getDamage();
}

これは単なる例です。あなたのゲームは何らかの追加のロジック(例えば順序付け、例えばある効果が他の効果の前に適用されることの確認など)を必要とするかもしれませんが、私はまだ一般的な原則が使用できると思います。

0
追加された