
以前の投稿: §1
M:tGエコシステム全体がObserverパターンを実装しており、不快な形であらゆる種類の「データバインディング」について話すのはばかげているでしょう。 したがって、マップ構造の最初のレビュー中に、裸の貧血モデルを作成しようとすることができます。
public class Card
{
public string Name { get; set; }
public Mana Cost { get; set; }
⋮
//
}
残念ながら、マップを変更するには、その初期状態を覚えておく必要があります。 たとえば、 Avatar of Hopeは最初は








原則として、この機能を、これらの状態を反映する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
プロパティもサポートします

RequiresTap
などの追加のプロパティ
RequiresTap
ます。 これについては後の記事で説明します。
カードの種類

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つの「能力」は次のようになります。
![]() | ![]() ![]() ![]() |
|
|

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のような高度な言語を使用する必要があります。
続く!