Magic the Gatheringプログラミングの議論を続けます。 今日は、特定のマップのオブジェクトモデルがどのように形成されるかについて説明します。 カードはシステムのすべての参加者(プレーヤー、他のカードなど)と対話するため、カードの基本的な動作の実装についても触れます。 前と同じように、.Netエコシステムを使用しますが、将来(ヒント)アンマネージC ++の使用が見られるようになります。 また、例として、第8版以降のマップを使用します[
1 ]
以前の投稿:
§1M:tGエコシステム全体がObserverパターンを実装しており、不快な形であらゆる種類の「データバインディング」について話すのはばかげているでしょう。 したがって、マップ構造の最初のレビュー中に、裸の貧血モデルを作成しようとすることができます。
public class Card
{
public string Name { get; set; }
public Mana Cost { get; set; }
⋮
//
}
残念ながら、マップを変更するには、その初期状態を覚えておく必要があります。 たとえば、
Avatar of Hopeは最初は



ライフが3回以下の場合、費用はかかりません



でもただ


。 したがって、
二分法があります -プロトタイプ(初期値)と「ゲーム内」の実際の値の両方が必要です。 そして各プロパティに対して。
原則として、この機能を、これらの状態を反映する2つの相互接続されたクラスに分割できます。
//
class Card
{
public virtual string Name { get; set; }
⋮
}
// ,
class CardInPlay : Card
{
public override string Name
{
⋮
}
public Card Prototype { get; set; }
// ""
public CardInPlay(Card prototype)
{
Prototype = prototype; //
Name = prototype.Name; // - C# AutoMapper :)
⋮
}
}
CardInPlay
クラスは、Decoratorパターンのバリエーションの1つを実装します。このパターンでは、1つのクラスが同時に別のクラスを継承および集約します。
これらの2つのクラスについてある程度理解したので、マップのさまざまなプロパティとそれらの実装方法を見てみましょう。
マップ名と問題Æ
原則として、カードの名前はすべて明確です-これは線であり、保存され、画面上に描かれ、時には変更することもあります(たとえば、クローン作成時)が、基本的には問題はありません。
しかし、何らかの理由で大文字のAEを使用して文字letterを書きたくないデータベースには問題があります。 これは大きな問題ではありません。データベースからマップを読み込むときに
string.Replace()
を使用するだけです。
カード代
マナについては以前の投稿で説明しました。 コストは標準表記法で記述され、システムによって解析されます。 特にいくつかのコストのバリエーションがあります
- ゼロコスト(
) - 通常費用(


) - 何かの関数としてのコスト(

)
Mana
構造はすべての場合に適しています。 マナの量を数えることができ、マナに現れる場合は
HasX
プロパティもサポートします

。[
2 ]実際、カードの使用コストを読むのに問題はありません。 機会を使用するコストについては、マナ自体に加えて、
RequiresTap
などの追加のプロパティ
RequiresTap
ます。 これについては後の記事で説明します。
カードの種類

右側のカードにはタイプ、つまり3つのタイプがあります。 文字列としてのタイプは「伝説のクリーチャー-ウィザード」と書くことができますが、タイプを
積極的に操作するカードがあるため、タイプのリストを保存できるコレクションを作成します。そこにタイプを追加します。
public string Type
{
get { return type; }
set
{
if (type != value )
{
type = value ;
// create derived types
types.Clear();
string [] parts = type.Split( ' ' , '-' , '–' );
foreach ( var part in parts.Select(p => p.Trim()).Where(p => p.Length > 0))
{
types.Add(part);
}
}
}
}
private ICollection< string > types = new HashSet< string >();
public ICollection< string > Types
{
get
{
return types;
}
set
{
types = value ;
}
}
HashSet<T>
上記
HashSet<T>
使用されています カードの種類を繰り返すことはできません。 このようなセットがあると、たとえば、マップが伝説的かどうかをチェックするプロパティを作成できます。
public bool IsLegend
{
get
{
return Types.Where(t => t.Contains( "Legend" )).Any();
}
}
ルール
Arkanisが画面の近くにぶら下がっている間、それを例として見てみましょう。 Arkanisには2つの活性化された能力(「能力」)があります。 OOPのすべての利点を使用して、再び貧血モデルを作成できます。
public sealed class ActivatedAbility
{
public string Description { get; set; }
public Mana Cost { get; set; }
public bool RequiresTap { get; set; }
public Action<Game, CardInPlay> Effect { get; set; }
}
おそらく既に推測したように、カードには能力のリストがあり、ゲーム自体では、ユーザーはそれらのいずれかを選択できます。
そのため、この機能にはテキストの説明、コスト、マップを有効にするかどうかを示すチェックボックス、およびこの機能の動作を決定するデリゲートがあります。 Arkanisにとって、彼の2つの「能力」は次のようになります。
:カードを3枚引く。 |   :全能のアルカニスをオーナーの手札に戻す。 |
- 説明= 3枚のカードを引く
- コスト=
 - RequiresTap =
true - 効果=
(game,card) => card.Owner.DrawCards(3)
| - Description = 全能のアルカニスをオーナーの手札に戻す。
- コスト=
   - RequiresTap =
false - 効果=
(game,card) => game.ReturnCardToOwnersHand(card)
|

能力は魔法のようには作成されません。 テキスト形式で読み取られ、正規表現を使用して解析されます。 マナの使用も活性化された能力です。 モデルに追加するために、かなり単純なデリゲートを使用します。
Action< string > addManaGeneratingAbility =
mana => c.ActivatedAbilities.Add( new ActivatedAbility
{
Cost = 0,
RequiresTap = true ,
Effect = (game, card) =>
game.CurrentPlayer.ManaPool.Add(Mana.Parse(mana)),
Description = "Tap to add " + mana + " to your mana pool."
});
ここで、たとえば
Shivan Oasisのような二重の基盤を実装するには、マップルールで適切なテキストを見つけて、対応する能力を追加するだけです。
Match m = Regex.Match(c.Text,
"{Tap}: Add {(.)} or {(.)} to your mana pool." );
if (m.Success)
{
addManaGeneratingAbility(m.Groups[1].Value);
addManaGeneratingAbility(m.Groups[2].Value);
}
強さと健康
カードの強度とヘルスの数値のみがカードに含まれていれば簡単です。 次に、それらを
Nullable<int>
と、すべてが透かしになります。 実際、プロトタイプには、たとえば
*/*
などの値が表示されます。 もちろん、ほとんどの場合、値を解析するだけですが、固定値に加えて微分値もあります。

これは、派生値を考慮する
Power
および
Toughness
オーバーライドがあることを意味します。 たとえば、
Mortivoreマップの場合、構造は次のようになります。
class Card
{
public Card()
{
⋮
GetPower = (game, card) => card.Power;
GetToughness = (game, card) => card.Toughness;
}
⋮
// */*
public string PowerAndToughness { get; set; }
// ( )
public virtual int Power { get; set; }
public virtual int Toughness { get; set; }
//
public Func<Game, CardInPlay, int > GetPower { get; set; }
public Func<Game, CardInPlay, int > GetToughness { get; set; }
}
次に、マッププロパティを作成するために
Regex
を使用できます。
m = Regex.Match(c.Text, c.Name + "'s power and toughness are each equal to (.+)." );
if (m.Success)
{
switch (m.Groups[1].Value)
{
case "the number of creature cards in all graveyards" :
c.GetPower = c.GetToughness = (game,card) =>
game.Players.Select(p => p.Graveyard.Count(cc => cc.IsCreature)).Sum();
break ;
}
}
おわりに
この投稿では、マップのオブジェクトモデルがどのように見えるかを簡単に説明しました。 私は意図的にすべてのメタプログラムの喜びを「オーバーボード」のままにしました。 デコレータパターンの実装で繰り返し発生するいくつかの側面は時間がかかりすぎることを示唆することしかできません。
自動化するか、Booのような高度な言語を使用する必要があります。
続く!
注釈
- ↑私の知る限り、あるいはデータベースが私に伝える限りでは、第8版はロシア化されていません。 現時点では、マップの実装例はすべて英語で示されていますが、これは、ルールが完全に実装されている場合、それらをロシア語化できないという意味ではありません。 パーサーはまだ英語で書くのに便利です 言葉は傾いていません。
- ↑実際、このオプションはコストが

。 この問題が適切になったら解決します。