
Basic mana can be five colors, and mix as you like. This is a direct hint that in any class that deals with mana there should be a model of these five colors. Each element of the model, as is the case with the straightforward application of OOP, pulls a few more extras. For example, a naive approach to implementing mana might look like this:class Mana
{
⋮
public int Blue { get; set; }
public bool IsBlue { get { return Blue > 0; } }
//
}
public string ShortString
{
get
{
StringBuilder sb = new StringBuilder();
if (Colorless > 0) sb.Append(Colorless);
if (Red > 0) sb.Append( 'R' .Repeat(Red));
if (Green > 0) sb.Append( 'G' .Repeat(Green));
if (Blue > 0) sb.Append( 'U' .Repeat(Blue));
if (White > 0) sb.Append( 'W' .Repeat(White));
if (Black > 0) sb.Append( 'B' .Repeat(Black));
if (HasX) sb.Append( "X" );
return sb.ToString();
}
}
public void PayFor(Mana cost)
{
if (cost.Red > 0) Red -= cost.Red;
if (cost.Blue > 0) Blue -= cost.Blue;
if (cost.Green > 0) Green -= cost.Green;
if (cost.Black > 0) Black -= cost.Black;
if (cost.White > 0) White -= cost.White;
int remaining = cost.Colorless;
while (remaining > 0)
{
if (Red > 0) { --Red; --remaining; continue ; }
if (Blue > 0) { --Blue; --remaining; continue ; }
if (Black > 0) { --Black; --remaining; continue ; }
if (Green > 0) { --Green; --remaining; continue ; }
if (White > 0) { --White; --remaining; continue ; }
if (Colorless > 0) { --Colorless; --remaining; continue ; }
Debug.Fail( "Should not be here" );
}
}
Colorless mana is the first hint that each type of mana draws upon itself domain-specific logic, which in principle is difficult to predict. For example, the card on the right is a typical example of a kinder surprise when working with an inflexible domain like M: tG. However, even using the same model (in C #), you can get several additional methods. For example, here’s what the “converted value” property looks like:public int ConvertedManaCost
{
get
{
return Red + Blue + Green + Black + White + Colorless;
}
}
public bool EnoughToPay(Mana cost)
{
if (Red < cost.Red || Green < cost.Green || White < cost.White ||
Blue < cost.Blue || Black < cost.Black)
return false ;
// can we pay the colourless price?
return ((Red - cost.Red) + (Green - cost.Green) + (White - cost.White) +
(Blue - cost.Blue) + (Black - cost.Black) + Colorless) >= cost.Colorless;
}
This is where it all begins ... because up to this point we thought that everything is very simple, and that you can for example take and predictably 2RG line and get an object like Mana . And here again - and the new rules, not only the mechanics, but also the records. After all, how to write a double mana symbol? Most likely this way: {WB}{WB}{WB} . Vooot, and for this you need a parser.X icon. For example, pay XG and get X life. In order not to produce entities, I still think that the essence of Mana should be one. The situation with X can be razrulit single ( public bool HasX ), but you can summarize a little, so if suddenly there is a card with a value of XY , we do not have to rewrite all the logic. In addition, there are situations when you can pay for a certain X only with mana of a certain color. This also needs to be taken into account.Mana object from a string.MagicTheGathering.Entities for entities such as Mana . This assembly can ILMerge with other assemblies written in C # or F #.MagicTheGathering.Meta for our meta-abstractions that will "collect" our entities.[ManaType( "Green" , "G" , "Forest" )]
class Mana:
public def constructor ():
pass
class ManaTypeAttribute(AbstractAstAttribute):
colorName as string
colorAbbreviation as string
landName as string
public def constructor (colorName as StringLiteralExpression,
colorAbbreviation as StringLiteralExpression, landName as StringLiteralExpression):
self .colorName = colorName.Value
self .colorAbbreviation = colorAbbreviation.Value
self .landName = landName.Value
public override def Apply(node as Node):
AddField(node)
private def AddField(node as Node):
c = node as ClassDefinition
f = [|
$(colorName.ToLower()) as Int32
|]
c.Members.Add(f)
ClassDefinitioncolorName into the real name of the field) and a quote (the brackets [| and |] turn our construct into an element of the property| Boo | C # |
[ManaType( "Green" , "G" , "Forest" )] | [Serializable] |
AddField() :private def AddField(node as Node):
c = node as ClassDefinition
r = ReferenceExpression(colorName)
f = [|
[Property($r)]
$(colorName.ToLower()) as Int32
|]
c.Members.Add(f)
IsGreen return us true if the map is green and false if not. This property we will meet again, because It interacts specifically with hybrid maps. Here is my first attempt to implement it:private def AddIndicatorProperty(node as Node):
c = node as ClassDefinition
r = ReferenceExpression(colorName)
f = [|
$( "Is" + colorName) as bool:
get :
return ($r > 0);
|]
c.Members.Add(f)
[Serializable]
public class Mana
{
// Fields
protected int green;
// Properties
public int Green
{
get
{
return this .green;
}
set
{
this .green = value ;
}
}
public bool IsGreen
{
get
{
return ( this .Green > 0);
}
}
}
class ManaSumAttribute(AbstractAstAttribute):
static public LandTypes as List = []
⋮
public def constructor (colorName as StringLiteralExpression,
⋮
ManaSumAttribute.LandTypes.Add( self .colorName)
class ManaSumAttribute(AbstractAstAttribute):
⋮
public override def Apply(node as Node):
c = node as ClassDefinition
root = [| Colorless |] as Expression
for i in range(LandTypes.Count):
root = BinaryExpression(BinaryOperatorType.Addition,
root, ReferenceExpression(LandTypes[i] as string))
p = [|
public ConvertedManaCost:
get :
return $root
|]
c.Members.Add(p)
ConvertedManaCost property. Here is what we get:public int ConvertedManaCost
{
get
{
return (( this .Colorless + this .Green) + this .Red);
}
}
class HybridManaAttribute(AbstractAstAttribute):
static public LandTypes as List = []
public override def Apply(node as Node):
mergedTypes as List = []
for i in range(LandTypes.Count):
for j in range(LandTypes.Count):
unless (mergedTypes.Contains(string.Concat(LandTypes[i], LandTypes[j])) or
mergedTypes.Contains(string.Concat(LandTypes[j], LandTypes[i])) or
i == j):
mergedTypes.Add(string.Concat(LandTypes[i], LandTypes[j]))
// each merged type becomes a field+property pair
c = node as ClassDefinition
for n in range(mergedTypes.Count):
name = mergedTypes[n] as string
r = ReferenceExpression(name)
f = [|
[Property($r)]
$(name.ToLower()) as int
|]
c.Members.Add(f)
IsGreen in the case of hybrid mana. After all, we can no longer keep them in the attributes of uniform mana, because At that time, nothing was known about the hybrid mana. Let's move them to a separate attribute. So, we need to use both hybrid and single properties in order to understand the color of the map.class ManaIndicatorsAttribute(AbstractAstAttribute):
public override def Apply(node as Node):
c = node as ClassDefinition
for i in range(ManaSumAttribute.LandTypes.Count):
basic = ManaSumAttribute.LandTypes[i] as string
hybridLands as List = []
for j in range(HybridManaAttribute.HybridLandTypes.Count):
hybrid = HybridManaAttribute.HybridLandTypes[j] as string
if (hybrid.Contains(basic)):
hybridLands.Add(hybrid)
rbasic = ReferenceExpression(basic.ToLower())
b = Block();
b1 = [| return true if $rbasic > 0 |]
b.Statements.Add(b1)
for k in range(hybridLands.Count):
rhybrid = ReferenceExpression((hybridLands[k] as string).ToLower())
b2 = [| return true if $rhybrid > 0 |]
b.Statements.Add(b2)
r = [|
$( "Is" + basic):
get :
$b;
|]
c.Members.Add(r)
IsXxx property, but it works, although at the Reflector level such a mess is obtained.ToString() :class ManaStringAttribute(AbstractAstAttribute):
public override def Apply(node as Node):
b = Block()
b1 = [|
sb.Append(colorless) if colorless > 0
|]
b.Statements.Add(b1)
for i in range(ManaTypeAttribute.LandTypes.Count):
land = ReferenceExpression((ManaTypeAttribute.LandTypes[i] as string ).ToLower())
abbr = StringLiteralExpression(ManaTypeAttribute.LandAbbreviations[i] as string )
b2 = [|
sb.Append($abbr) if $land > 0;
|]
b.Statements.Add(b2)
for j in range(HybridManaAttribute.HybridLandTypes.Count):
land = ReferenceExpression((HybridManaAttribute.HybridLandTypes[j] as string ).ToLower())
abbr = StringLiteralExpression( "{" +
(HybridManaAttribute.HybridLandAbbreviations[j] as string ) + "}" )
b3 = [|
sb.Append($abbr) if $land > 0;
|]
b.Statements.Add(b3)
b3 = [|
sb.Append( "X" ) if hasX
|]
m = [|
public override def ToString():
sb = StringBuilder();
$b
return sb.ToString()
|]
c = node as ClassDefinition
c.Members.Add(m)
2GG{RW} line. Let's divide the manaparser into 3 parts - the analysis of base mana, hybrid mana, and "everything else". So, basic mana is not difficult to disassemble:// basic land cases are in a separate block
basicLandCases = Block()
for i in range(ManaTypeAttribute.LandTypes.Count):
name = ManaTypeAttribute.LandTypes[i] as string
abbr = ManaTypeAttribute.LandAbbreviations[i] as string
rAbbr = CharLiteralExpression( char .ToUpper(abbr[0]))
rName = ReferenceExpression(name)
case = [|
if ( char .ToUpper(spec[i]) == $rAbbr):
m.$rName = m.$rName + 1
continue
|]
basicLandCases.Statements.Add(case);
RG or GR ) does not affect the parser. However, the solution is not very complicated:// hybrid land cases are in a much smarter block
hybridLandCases = Block()
for i in range(HybridManaAttribute.HybridLandTypes.Count):
name = HybridManaAttribute.HybridLandTypes[i] as string
abbr = HybridManaAttribute.HybridLandAbbreviations[i] as string
// build an appreviation literal
abbr1 = StringLiteralExpression(abbr)
abbr2 = StringLiteralExpression(abbr[1].ToString() + abbr[0].ToString())
case = [|
if (s == $abbr1 or s == $abbr2):
m.$name = m.$name + 1
continue
|]
hybridLandCases.Statements.Add(case)
X symbol:// the method itself
method = [|
public static def Parse(spec as string) as Mana:
sb = StringBuilder()
cb = StringBuilder() // composite builder
inHybrid = false // set when processing hybrid mana
m = Mana()
for i in range(spec.Length):
if (inHybrid):
cb.Append(spec[i])
continue
if ( char .IsDigit(spec[i])):
sb.Append(spec[i])
continue ;
if (spec[i] == '{' ):
inHybrid = true
continue
if (spec[i] == '}' ):
raise ArgumentException( "Closing } without opening" ) if not inHybrid
inHybrid = false
s = cb.ToString().ToUpper()
raise ArgumentException( "Only two-element hybrids supported" ) if s.Length != 2
$hybridLandCases
raise ArgumentException( "Hybrid mana " + s + " is not supported" )
$basicLandCases
if ( char .ToUpper(spec[i]) == 'X' ):
m.HasX = true
continue ;
|]
// add it
c = node as ClassDefinition
c.Members.Add(method)
Despite the fact that we haven’t disassembled several cases, such as paying a mana for a certain spell, I’ll probably stop - partly because Firefox is already starting to fall on the number of characters in the textbox. I hope that this post has illustrated how difficult it is to make extensible entities, and the fact that sometimes metaprogramming is not optional. By the way, the full code (I can not vouch for its correctness at this stage) can be found here . Boo is ruthless.[ManaType( "Green" , "G" , "Forest" )]
[ManaType( "Red" , "R" , "Mountain" )]
[ManaType( "Blue" , "U" , "Island" )]
[ManaType( "Black" , "B" , "Swamp" )]
[ManaType( "White" , "W" , "Plains" )]
[ManaSum]
[HybridMana]
[ManaIndicators]
[ManaString]
[ManaParser]
class Mana:
[Property(Colorless)]
colorless as int
[Property(HasX)]
hasX as bool
Source: https://habr.com/ru/post/72721/
All Articles