.NET

индекс
121,07

Программирование Magic: the Gathering — §1 Мана


Хочется начать посты про программирование Magic: the Gathering (M:tG), и начнем мы пожалу с самого простого – с концепции «маны». Мана – это то, чем оплачиваются все заклинания. Несмотря на то, что с виду маны всего 5 типов, на самом деле все чуть-чуть сложнее. Давайте попробуем разобраться.



Во-первых маны вовсе не 5 типов. Даже если отбросить «сдвоенную» ману (когда можно платить либо одной, либо другой), то все равно есть еще «бесцветная» мана, за которую покупаются артефакты, и которая фигурирует в стоимости многих (большинства) заклинаний. Также – если мы говорим о «цене» а не об оплате, фигурирует мана Х, т.е. ситуация когда цена регламентирована правилами.

Рассмотрим несколько примеров.

Цвета обыкновенные


Базовая мана может быть пяти цветов, и перемешиваться как угодно. Это прямой намек на то, что в любом классе который занимается маной должна фигурировать модель этих пяти цветов. Каждый элемент модели, как это бывает при прямолинейном применении ООП, тянет за собой еще несколько дополнительных. Например, наивный подход к реализации маны может выглядеть вот так:



class Mana
{
  ⋮
  public int Blue { get; set; }
  public bool IsBlue { get { return Blue > 0; } }
  // и так далее
}

Пока мы играем с идеей того, что стоимость и наличие маны в пуле могут содержаться в одной сущности, можно пофантазировать еще чуть-чуть. Например, как получить представление маны в текстовой строке (например WUBRG для Sliver Legion)? Примерно вот так:

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();
  }
}

Это я так иллюстрирую слабость модели. Если бы мы не знали что есть сдвоенная мана (а мы-то знаем), то последующие изменения вызвали бы архитектурный апокалипсис в нашей сущности и всем, с чем она взаимодействовала. Это во-первых.

Во-вторых, писать одно и то же 5+ раз – это плохо. Представьте что вы реализуете метод оплаты определенной мана-стоимости из пула. Если следовать все тому же подходу, вам наверное придется написать что-то подобное:

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");
  }
}

Количество повторений не «зашкаливает», но безусловно раздражает. Напомню, что в C# нет макросов.

Бесцветная мана


Бесцветная мана – это первый намек на то, что каждый тип маны тянет за собой доменно-специфичную логику, которую в принципе сложно предугадать. Например, карта справа – это типичный пример киндер-сюрприза в работе с таким негибким доменом как M:tG. Тем не менее, даже используя все ту же модель (в C#), можно получить несколько дополнительных методов. Например, вот как выглядит свойство «конвертированной стоимости»:



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;
}

Бесцветная мана, в отличии от цветной, понижает степень детерминизма, т.к. мы не можем играть заклинание автоматически если, например, мы заплатили RG за карту стоимостью 1, т.к. непонятно, какого цвета маной нужно расплачиваться.

Гибридная мана


Тут-то все и начинается… ведь до этого момента мы думали что все очень просто, и что можно например взять и предсказуемо распарсить строку 2RG и получить объект типа Mana. А тут раз – и новые правила, причем не только самой механики, но и записи. Ведь как записать символ двойной маны? Скорее всего так: {WB}{WB}{WB}. Вооот, а для этого уже нужен парсер.

Более того, представьте себе что мана расплодилась как сливеры – появилась фиолетовая, пурпурная, и так далее. Легко ли добавить поддержку новой маны в те кусочки кода что я привел выше? Правильно – это нереально сложно. Нужен другой подход.

Прежде чем мы посмотрим на этот самый «другой подход», следует упомянуть о том, что стоимость и оплата – две разные вещи. Точнее они похожи, но стоимость, например может содержать в себе значок X. Например, заплатите XG и получите X жизни. Дабы не плодить сущности, я все же считаю что сущность Mana должна быть одна. Ситуацию с X можно расрулить единично (public bool HasX), а можно чуть-чуть обобщить, так что если вдруг появится карта со стоимостью XY, нам не придется переписывать всю логику. К тому же, бывают ситуации, когда за определенный X можно платить только маной определенного цвета. Это тоже нужно учесть.

Про метапрограммирование


Сдается мне, что в данной задаче нужно метапрограммирование, хотя бы для того чтобы избежать излишней кододупликации а также обезопасить себя от таких случаев когда, например, вдруг нужно добавить поддержку Observable (скажем, через индивидуальные события) без переписывания каждого свойства класса. C# для таких целей не подходит (даже с учетом того, что есть PostSharp). Нужно что-то, что сможет учесть наши цели, а именно:

  • Поддерживать цветную ману с произвольным количеством цветов. То есть, например, добавление сиреневой маны не должно ломать систему и должно нести за собой только незначительные изменения в коде.
  • Поддерживать гибридную ману. Думаю что строенной и более маны не будет, так что можно просто сделать поддержку сдвоенной маны всех сущестующих цветов. Причем при добавлении нового типа «первичной» маны, дополнительных действий по ее «гибридизации» осуществлять не нужно.
  • Правильно поддерживать бесцветную ману а также операции с ней.
  • Поддерживать стандартную нотацию для маны, т.е. иметь парсер который может собирать объект типа Mana из строки.

Итак, давайте посмотрим как можно поэтапно реализовать все вышеперечисленные свойства на языке который поддерживает метапрограммирование. Конечно же я говорю о языке Boo. (Хотя есть еще Nemerle, но в нем я не силен.)

Цвета обыкновенные (попытка номер 2)


N.b. здесь и далее будут идти выкладки сразу на двух языках – на Boo (то, что мы написали) и на C# (то, что в этом увидел Reflector). Сделано это с целью проиллюстрировать действия макросов и мета-методов, т.к. сам по себе Boo, как вы можете догадаться, не будет прозрачен в этом плане.

Хочется написать «итак, начнем с простого», но простого, увы не будет. Начнем с того, что сделаем два проекта, а именно

  • MagicTheGathering.Entities для сущностей, таких как Mana. Эту сборку в последствии можно ILMergeить с другими сборками написанными на C# или F#.
  • MagicTheGathering.Meta для наших мета-абстракций, которые будут «собирать» наши сущности.

Поля


Начнем пожалуй с поддержки лесов:



[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)

Итак, мы определили конструктор для аттрибута, который вызывается из нашей изначальной сущности. В примере выше, мы добавляем поле в уже существующий класс. Делается это в три приема:

  • Во-первых мы приводим тот элемент к которому применяется аттрибут к типу ClassDefinition
  • Потом, мы создаем поле, используя сплайс (превращаем содержание строки colorName в реальное имя поля) и цитирование (скобки [| и |] превращают наш конструкт в элемент свойства
  • Добавляем собранное свойство к классу.

Давайте теперь сравним исходный и конечный результат:

Boo C#
[ManaType("Green""G""Forest")]
class Mana:
  public def constructor():
    pass

[Serializable]
public class Mana
{
    // Fields
    protected int green;
}


Прямые и производные свойства


Хмм, не это ли то, чего мы хотели? :) Как насчет того, чтобы надстроить над этим полем простое свойство? Элементарно, Ватсон – нужно только поменять определение 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 возвращает нам true если карта зеленая, и false если нет. Это свойство мы встретим еще раз, т.к. оно специфично взаимодействует с гибридными картами. Вот моя первая попытка его реализовать:

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)

Реализовать производное свойство оказалось тоже очень просто. А вот как все это выглядит в переводе на C#:

[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);
        }
    }
}

Взаимодействие


Попробуем автогенерировать суммарную стоимость (converted mana cost). Для этого нужно реализовать бесцветную ману что, собственно, не так уж сложно. А вот как автогенерировать сумму всех цветов маны + бесцветную? Для этого мы применим следующий подход:

  1. Во-первых, мы создадим новый аттрибут, а в нем – список тех свойств, которые нужно суммировать
    class ManaSumAttribute(AbstractAstAttribute):
      static public LandTypes as List = []
      ⋮

  2. Теперь, при создании любого «свойства земли», мы будем записывать название в это статическое свойство:
    public def constructor(colorName as StringLiteralExpression, 
      ⋮
      ManaSumAttribute.LandTypes.Add(self.colorName)

  3. А теперь используем это свойство для создания суммы:
    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)


А теперь давайте проверим – добавим поддержку гор (mountain) и посмотрим что эмитируется для свойства ConvertedManaCost. Вот что мы получим:

public int ConvertedManaCost
{
    get
    {
        return ((this.Colorless + this.Green) + this.Red);
    }
}

Как видите, все работает :)

Поддержка гибридной земли


Окей, у нас начало что-то получаться. Я добавляю поддержку всех земель в код и ура, теперь Boo автогенерирует 10 свойств для них, плюс делает сумму. Что еще нужно? Ну, как насчет поддержки гибридной земли. Это конечно посложнее, т.к. нужно брать все пары существующих земель. Но зато это интересно, так почему бы не попробовать?

Принцип такой же как и с генератором суммы – используем статическое поле и заполняем его из аттрибутов разной маны. А потом…. потом совсем сложная вещь. Если коротко – мы ищем все валидные пары маны и для них создаем примерно такие же свойства как и для обычной, «однотипной» маны.

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 в случае гибридной маны. Мы ведь уже не можем держать их в аттрибутах однородной маны, т.к. на тот момент о гибридной мане ничего не известно. Давайте их перенесем в отдельный аттрибут. Итак, нам нужно воспрользоваться и гибридными и одиночными свойствами чтобы понять какого цвета карта.

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®

Вуаля! В коде выше мы находим все типы маны, которые затрагивает данный тип, и сравниваем их с нулем. Это не самый оптимальный способ вычисления свойства IsXxx, но он работает, хотя на уровне Рефлектора получается нехорошее такое месиво.

Строковое представление и парсер


Для каждого простого типа маны у нас есть строковое представление. Это представление позволяет нам как считать строку, так и получить ее. Начнем с простого – получим строковое представление маны, которое мы будет выдавать через 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} создать соответствующий объект. Давайте поделим манапарсер на 3 части – стейтменты разбора базовой маны, гибридной маны, и «всего остального». Итак, базовую ману разобрать не сложно:

// 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);

C гибридной маной нужно повозиться, так чтобы порядок написания маны (RG или GR) не влиял на парсер. Тем не менее, решение не очень сложное:

// 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:

// 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)

Результат этого макроса я приводить не буду, т.к. генерируется много кода.

Заключение


Несмотря на то, что мы не разобрали несколько кейсов, такие например как оплата маной определенного заклинания, я пожалуй прервусь – отчасти потому, что Firefox уже начинает падать от количества символов в текстбоксе. Надеюсь что этот пост проиллюстрировал то, насколько сложно делать расширяемые сущности, и то что порой метапрограммирование – не опционально. Кстати, полный код (не ручаюсь за его корректность на данном этапе) можно посмотреть тут. Boo безжалостен.

Ах, да, что касается нашей сущности, то теперь она выглядит вот так:



[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

На этом действительно все. Comments welcome. ■
+45
18 октября 2009, 14:49
37

комментарии (100)

+2
valyard #
Где взять круглую картинку с цветами в бОльшем разрешении?
+1
Frodo #
присоединяюсь! хочу колесо маны в большом разрешении!
0
Frodo #
нашел в чуть большем: wiki.mtgsalvation.com/images/9/98/Color_wheel.jpg
+1
nayjest #
Подозрительно много кода для такой просто задачи (имхо)
0
stab #
Угу, обычных олдскульных массивов вполне бы хватило.
0
mezastel #
Пример в студию :)
0
stab #
Ну уел, лень писать, точнее дописывать, никаких сложностей в реализации нет, кроме, пожалуй, того, что она в своеборазном C-style получается местами. Если дело принципа, то я конечно могу найти время и дописать :D

Кстати, так и не понял что означает XB, это сколько сколько хочешь B столько и используешь? Из текста не очень понятно, в MtG играл пару раз в юности.
0
mezastel #
Имеется ввиду вот это:

0
stab #
Понятненько. В общем, если допишу, закину в личку веселья ради :)
–1
iley #
Что такое «M:tG»? Выглядит как случайный набор символов.
+1
Frodo #
это коллекционная карточная игра Magic: The gathering. Сокращенно M:tg
–1
fenrillium #
Magic: the Gathering??) Карточная ролевая игра.
0
feci #
magic: the gathering
0
Retarded #
Magic: the Gathering
+9
Retarded #
oh shi~
0
feci #
кстате была и на PC )
0
Marauder2 #
Magic: The Gathering — типа настольная карточная игра с коллекционными картами. ну это если в двух словах.
Более привычное сокращение — MTG.
+3
burivuh #
Кхм, я один не понял о чем это? )
+3
Staind #
Не один. Автор не с того начал. Лучше бы описал для начала план (с чего начнем, какие библиотеки будем использовать, расшифровал бы аббревиатуру M:tG), а то сходу ринулся код писать.
+4
DAiMor #
Судя по всему это привычка многих авторов здесь писать статьи про то, что может понять узкий круг лиц, либо только после того как прочитаешь пол статьи. Я вообще в игры не играю но очень трудно было понять причем тут мана и программирование.
+3
XaBoK #
Нет конечно, надеюсь, что не только сам автор понимает этот пост.
Как бы не хватает пары-тройки абзацев текста в начале:

Возникло желание поделиться своим опытом по реализации идеи игры Magic: the Gathering на C#/ Boo…

Игра представляет собой…

Важны такие нюансы как… Подводные камни…

Ну и в таком духе. А то кажется что это статья из долго играющего цикла и надо срочно искать начало.
+1
mezastel #
Ну начальный пост уже был ранее.
+3
XaBoK #
В таком случае это большое упущение, явно стоило добавить ссылку на первую часть и начать с упоминания с того, что это продолжение темы. Ещё не поздно это исправить ^_^
+1
BekoBou #
Хорошо бы добавить ссылку на игру. Я думаю не все с нею знакомы.

Нпаример — ru.wikipedia.org/wiki/Magic
–2
valyard #
вопрос простой
нафига програмить мтг?
+3
Meroving #
наверно потому что это интересная задача, нет?
0
mezastel #
Эт точно…
0
Usmekhaiouschiysia #
Так и не понял, а где разбор земель дающих any type ману?
0
mezastel #
В следующий раз, наверное.
–2
Usmekhaiouschiysia #
Ух, таким макаром можно запрограммировать фундамент для карт уже лет через пять, и можно будет начать добавлять карты.
–4
Usmekhaiouschiysia #
Ах да, ну и конечно, спасибо за гажение — будьте уверены, я это запомню.
0
Meroving #
право слово, один несчастный минус за комментарий на хабре не стоит таких переживаний :)
–1
Usmekhaiouschiysia #
При чём тут минус? Очевидно же кто, очевидно за что — и всё равно тихонько гадит в карму, вместо того, чтобы ответить.
«ай-яй-яй, меня критикуют — ВРАГ! ДАВИТЬ!»
0
kurokikaze #
Сейчас уже слишком много появилось видов земель, некоторые довольно замороченные :) Снежные, painlands, фильтры etc. Понятно что лучше сначала базовые реализовать, а потом уже расширять.
0
Usmekhaiouschiysia #
Не согласен, с тем, что сперва имеет смысл реализовывать базовые, потом расширять. В этом случае мы получаем постоянное объявление ручками новых переменных с тем чтоб добавить парочку типов… И горе нам, если карта требует в оплату например выкинуть из игры другую карту из руки (force of will), скинуть две карты (forbid) или ещё какое жуткое действие — они же тоже получаются «как бы маной». Или наоборот, то, что Вы перечислили.
0
kurokikaze #
Не, это мы уже в семантику правил влезли. Впрочем, без её реализации нам в любом случае будет сложно сделать все карты.

Тот код который здесь — скорее пример. Расширять его до рабочего варианта имхо смысла мало.
0
Usmekhaiouschiysia #
Именно.
+2
spxnezzar #
Ах! вот чего-чего а тематики старого доброго МТГ не ожидал увидеть на хабре. Надобы расчехлить свою коллекцию и пересобрать эки по фану.
+1
spxnezzar #
А где снежная манна? я смотрю у нас в России все пропустили замерзшее изданиие.
0
mezastel #
Снежная мана? Можно ссылочку?
0
Usmekhaiouschiysia #
en.wikipedia.org/wiki/Ice_Age_(Magic:_The_Gathering)
snow permanents
0
Meroving #
думаю это не очень существенно, особенно если учесть, что они отличаются только дополнительным навешенным типом.
0
mezastel #
Добавить поддержку не сложно
0
ZiZZoKo #
Мэджик был хорошим развлечением, особенно, до закрытия клуба «Даймонд» на Китай-городе. :)
+1
mezastel #
У нас в Питере тоже кстате есть клую … ээх, если бы еще было время :)
0
cherepaha #
а Феникс… птичку жалко…
0
mezastel #
что за феникс?
0
cherepaha #
phoenix-club.ru
в 2007 еще был жив, и был гораздо круче Саргоны
0
mezastel #
Понятненько. А что закрылся? Маркетинговой мощи не хватило?
0
cherepaha #
Честно говоря, не в курсе, но очень расстроился, узнав в этом году, что он закрылся.
+1
sunnybear #
он не БЫЛ, он ЕСТЬ :) особенно затейливо играть не один-на-один, а втроем или четвером :)
0
sergeant #
Пользуясь случаем, хочу прорекламировать пару игр схожей тематики — Spectromancer и Astral Masters, за которыми однажды провел пару нескучных недель, передвигая электронные кусочки бумаги, за неимением настоящих :)
0
mezastel #
Глянул на spectomancer — красиво…
+1
spxnezzar #
Можно я не буду выражаться по поводу идеи астрал мастеров? =) Мне тут знакомый ее посоветовал года полтора назад, сказал что он в нее меня сделает. каково же было его разочарование. когда за 20 партий он меня ни разу не смог обыграть. Просто он не знал что есть такая вещь как мтг, в которую у меня стаж уже далеко за…
Сама игра конечно хороша. но только тога когда нет ничего другого.
0
Bobos #
Точно. Играю на даче в жестком оффлайне 8)
0
spxnezzar #
не надоело? в жало играть? мне с живыми противниками через час надоедает =(
0
Meroving #
с живыми противниками? а вы с кем простите играете, с Deep Blue? :))
На самом деле никакой онлайн с реальными живыми турнирами в Саргоне не сравнится, ни по атмосфере, ни по эмоциям.
0
spxnezzar #
Контекст вообще то состоял из Астралмастерс в данной ветке.
Я имел ввиду что даже в онлайне в эту игру надоедает с живыми играть.
А если бы я жил в Санкт-Петербурге, то в сам бы в Саргоне отвисал. Но увы… в нашем Мухосранске нет клубов. И мой DCI ID уже давно был бы исключен из рейтингов, если бы не добрые знакомые из Краснодара, которые раз в пол-года включают меня в какой-нибудь драфт, когда для четности не хватает человека.
+1
mraleph #
у меня есть ощущение, что проще было представить разные типы маны объектами наследниками класса ManaType, тогда Mana превратилась бы по существу в Dictionary<ManaType, Int>… макросы генерирующие толстый интерфейс для класса, выглядят в этом отношении сложновато…
0
mezastel #
А как же с гибридной маной и т.д.?
+1
mraleph #
Кто сказал, что ManaType представляет собой 5 примитивных форм маны (т.е. енум)? Можно сделть ManaType являющийся гибрибдной формой…
0
Meroving #
этот Dictionary<ManaType,int> противно обрабатывать — 100%, я пробовал что-то подобное.
точнее вот так у меня было:
Copy Source  | Copy HTML
  1. type Mana =
  2.     |Red of int
  3.     |Green of int
  4.     |Blue of int
  5.     |White of int
  6.     |Black of int
  7.     |Colorless of int

Copy Source  | Copy HTML
  1. type ManaCost = class
  2.  ...
  3.     val Cost : Mana list
  4.  ...
  5. end

что по сути тоже самое (в чем-то даже проще).
0
mraleph #
И что «противного» случилось?
0
mezastel #
Это жуть как неудобно, на самом деле. Например у вас есть карта которая стоит 7WW, но на 7 меньше если у вас мало жизни. Как посчитать 7WW — 7? Сложновато. Так я хоть все разнес так, что можно реализовать, скажем, operator+ автоматически, без обхода списка.
+1
Meroving #
мне кажется тебе правда немножко не хватило вступления. )) Резковато в бой рванул.
и еще мне кажется, что для первого раза этого слегка многовато. я в принципе был готов к чему-то такому, но и то мой мозг оказался сломан. :)) может воскресенье виновато.
кстати об этом косвенно свидетельствует и то, что пока нет ни одного комментария по теме поста. все про мтг :)
0
mezastel #
Ну, то что про МТГ это тоже неплохо, постараюсь 2й пост сделать поменьше и повизуальней.
НЛО прилетело и опубликовало эту надпись здесь
0
mezastel #
Ну что касается сорцов, то always welcome. Что касается презентационного слоя, то я вообще планировал делать что-то из стека Windows… не знаю есть ли аналог Direct2D на Mono, но писать под OpenGL уж точно не планировалось (в студенчестве объелся этим фреймворком)… а какие еще варианты есть? Конечно всегда можно на Silverlight или Flash…

Пока все совместимо с Моно…
0
spxnezzar #
Не знаю, может я Америку открою, а вы посмотреть реализации на примере M:tG-Online не пробовали?
+1
mezastel #
Их физически нельзя попробовать ибо правила на сервере.
0
ZhuchoG #
GTK умеет рисовать и в виндус и в линукс. Спасибо за статью :)
+1
Shchvova #
Во первых. А как быть со странными манакостами, например по трешолду, грейвярду или фракшинам?
Кстате, а когда платишь колорлесс, какая пойдет в расход? Может на нужный инстант не отстаться, так что процесс не полностью автоматичный.

Да, и ещё, навряд ли есть какая-то надобность программировать Меджик, так как
"..., MAGIC: THE GATHERING are registered trademark of Wizards of the Coast, Inc. MicroProse Software ...", лицензия полностью пропреитарная на игру и правила, т.е. реализация правил в стороннем ПО не приветствуется, тем более уже давно есть www.wizards.com/default.asp?x=magic/magiconline
–1
mezastel #
Во-первых, странные манакосты — это отдельная тема, иногда они маркируются как Х, иногда в правилах, но к теме этого поста это не относится т.к. это семантика правил.

А насчет лицензии, можете написать большое грозное письмо Apprentice, Magic Workstation, Wagic и т.д. и предупредить их что то, что они делают нехорошо. Я не говорил что я прям игру пишу, я просто теоретизирую на тему правил в M:tG. Копирайт на мышление вроде пока не ввели.
0
olegafx #
Маленький оффтоп: если кто-то играет в MTG на Xbox 360 — пишите :)
0
mezastel #
Кстати, не знаете когда Duels появятся для PC?
0
licvidator #
Игра с таким названием вышла на PC в 1998 году, вообще-то :-)
0
mezastel #
Ту я помню, играл :)
+1
baalmor #
ладно, с маной понятно, а продолжения то ждать? В MTG еще много чего интересного есть. Да и подтягивать знания по ООП интереснее в таком приложении. :)
0
alex_blank #
Что-то я сомневаюсь, что вам удастся запрограммировать весь MtG. Разве что ограниченный subset карт…
–1
Delsian #
Главное — сделать непротиворечивый движок, а базу карт наполнять уже можно и коллективно.
0
alex_blank #
ставлю свою шляпу, что непротиворечивый движок сделать не получится, потому что нет общих правил в MtG, карты — это и есть сами правила

вам придется реализовать что-то вроде языка программирования шиворот-навыворот, с тотальным IoC, чтобы программу (правила) можно было динамически расширять прямо в run-time
0
Usmekhaiouschiysia #
Вполне логично добавить базовые правила, их не так уж много. А потом идти не от маны, а от оплаты.
0
alex_blank #
в общем, удачи

я когда-то занимался этой задачей, и забил, когда понял, что реализация MtG тянет на диссертацию, как минимум
0
Usmekhaiouschiysia #
В рамках пары блоков всё не так уж страшно, а дальше просто объём программирования вырастает в геометрической прогрессии, так что в одиночку программировать ЭТО по-моему абсолютно невозможно.
Ну да скоро столкнусь с этим, вероятно, снова — один из проектов клонирует кое-какие части MTG.
0
mezastel #
Мне кажется интересно разобраться в этой теме хотя бы чтобы понимать как в принципе можно реализовать любое правило
+1
Meroving #
не переживайте, автор это понимает лучше других. и он к этому готов :)
0
kurokikaze #
У MtGO получилось. Если не брать в расчёт всякие Unhinged / Unglued, то вполне можно. Тем более специально к выходу MtGO механику подправили для более простой реализации в софте — слои расписали, например.
+1
shirofaii #
Magic интересен тем, что никогда не знаешь когда и какую свинью он тебе подложит.
Вот, например: Mishra's Workshop
Думаю таких подстав много :)
0
Usmekhaiouschiysia #
Да, есть ещё земельки добавляющие ману только для каста абилитей (отлично работали с ребелями).
0
Delsian #
Начинал играть еще 4ой редакцией (и бумажной, и компьютерной), там на диске с игрой была офигительная бродилка, уже не помню названия, но суть в том, что гуляешь по каким-то территориям, выполняешь квесты, сражаешься с монстрами — и получаешь новые карты в колоду.
Если бы такое в виде малтиплеера реализовали — я бы подписался. У меня на M:tG-Online есть купленный аккаунт, но там играть совершенно не прет, не хватает какой-то изюминки.
PS: Залезть, что ли, на пыльный чердак и найти свои коробки с картами? :)
0
mezastel #
Ахх… Shandalar… да собственно игра была прикольная, и самое главное там AI действительно умел играть, что удивительно. Хотя одна особенность всех этих игр с AI в том, что когда подбираешь идеальную колоду, тебя уже не остановить…

У меня тоже на MTG Online акк есть, но там мне неинтересно то, что нужно постоянно тратить деньги на карты. По крайней мере на своем домашнем симуляторе я практикую бережливость и экономичность :)
0
Polco #
Попробуйте www.yugioh-online.net/top/english/index.html, 2-я по популярности TCG в мире но в России почти не распространена. Возможно менее сбалансированная / продуманная чем МТГ но Имхо на много более фановая.

П.с Когда будите регистрировать Акаунт не указывайте сваю страну как РФ, а то ничего купить не получится )
0
mezastel #
0
Zordhauer #
А в следующей статье мы попытаемся запрограммировать окружающую реальность +)
0
Angelina_Joulie #
Как-то всё очень запутано.

А почему нельзя использовать битовую алгебру для этого?

public enum ManaColor: int
{
Undefined = 0,
Red = 2,
Blue = 4,
Green = 8
}

public class Mana
{
public int Color { get; set; }
}

var m = new Mana { Color = ManaColor.Red | ManaColor.Blue }

Зачем городить весь этот огород?
Или я что-то упустила?
0
Angelina_Joulie #
Colorless забыла.
0
mezastel #
Тут простые подходы повсеместно не работают. Хардкодить количество земель, например, опрометчиво — вот появилась земля Snow/Frost, а я даже и не знал об этом.
0
Angelina_Joulie #
А с каких это пор int — это хардкод?

что будет если я сделаю следующее:
var a = 10;
var b = (ManaColor)a;

какое значение будет в b?

А если нужно избавиться от enum'a, по используйте себе адаптер и всё тут:
public static class ManaColorDescriptor
{
    public IManaColorDescription GetDescription(int manaColor)
    {
       //Можно в XML задезть, можно в базу, а можно и у сервиса спросить. Что душе угодно.
      //А умные дяди и тёти используют provider oriented design для этого.
        switch(manaColor)
        {
            сase 2:
               return new DefaultColorDescription { Color = "Red", Description = "Red Mana" };
            default:
               return new DefaultColorDescription { Color = "Новыя мана", Description = "А кто будет конфиги править или ещё чего делать? Чё это за  мана вообще?" };
        }
    }
}
0
mezastel #
Каждый определенный цвет маны порождает вместе с собой определенный набор дополнительной информации, которую очень напряжно реализовать с «ручным» подходом к проблеме. Например, мана участвует в формировании двойной маны, в подсчете общей суммы (converted mana cost) всей маны, в расчетах удовлетворения мана-стоимости а также — это тоже важно — в процессе разбора текстового описания и формирования соответствий в стиле black=swamp.
0
Angelina_Joulie #
Прости, но я ни чего не поняла.
Давай в личке подолжим?

Только зарегистрированные пользователи могут оставлять комментарии. Войдите, пожалуйста.