Let's continue our discussion of programming Magic the Gathering . Today we will discuss how the object model of a particular map is formed. Since the cards interact with all participants in the system (with players, other cards, etc.), we will also touch on the implementation of the basic card behavior. As before, we will use the .Net ecosystem, although in the future (hint) we will see the use of unmanaged C ++. Also, for examples we will use maps of the 8th and later editions. [ 1 ]public class Card
{
public string Name { get; set; }
public Mana Cost { get; set; }
⋮
//
}


but when you have 3 lives or less, it’s worth not 

but just 
. Therefore, we have a dichotomy - we need both a prototype (the initial value) and the real value “in the game”. And so for each property.//
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 class implements one of the variations of the Decorator pattern, in which one class inherits and aggregates another class at the same time.string.Replace() when reading the card from the database.
)

)
)Mana is suitable for all cases, because she can count the number of one or another mana, and also supports the HasX property if the mane appears
[ 2 ] In fact, there are no problems reading the cost of using the card. As for the cost of using the features, in addition to the mana itself, we have additional properties, such as RequiresTap . We will discuss this further in the post.
The map on the right has a type, or rather three. A type as a string can be written as “Legendary Creature - Wizard”, but since there are maps that actively manipulate types, we will also create a collection that can store a list of types - firstly, for quick searching, secondly, in order to add additional types there.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> . Card types can not be repeated. Having such a set, we can, for example, create a property that checks whether the map is legendary or not.public bool IsLegend
{
get
{
return Types.Where(t => t.Contains( "Legend" )).Any();
}
}
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; }
}
: Draw three cards. | ![]() ![]() : Return Arcanis Omnipotent to its owner's hand. |
|
|
Abilities are not created magically. They are read in text format, and parsed using regular regular expressions. Using mana is also an activated ability. In order to add it to the model, we use a fairly simple delegate.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."
});
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> and everything would be laced. In fact, in the prototype may appear such values as, for example, */* . Of course, in most cases, we just parse the values, but in addition to the fixed values, we have derivatives.
This in turn means that we have Power and Toughness override properties that consider derived values. For example, for a Mortivore map, the structures look like this: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 s.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 ;
}
}

. We will solve this problem when it becomes relevant.Source: https://habr.com/ru/post/73773/
All Articles