MageKing17

oracle.py

Jul 29th, 2015
644
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 116.76 KB | None | 0 0
  1. #!/usr/bin/python
  2. from __future__ import with_statement
  3. import string, re
  4.  
  5. __all__ = ["create_card_code", "add_oracle_info"]
  6.  
  7. def add_oracle_info(card, text):
  8.     info = parse_card_oracle(text)
  9.     supertypes = info['supertypes']
  10.     if isinstance(supertypes, list):
  11.         card.supertypes = ' '.join(supertypes)
  12.     types = info["types"]
  13.     if isinstance(types, list): types = ' '.join(types)
  14.     card.types = types
  15.     subtypes = info['subtypes']
  16.     if subtypes:
  17.         card.subtypes = ' '.join(subtypes)
  18.     if 'power' in info:
  19.         card.power = info["power"]
  20.         card.toughness = info["toughness"]
  21.     elif "loyalty" in info:
  22.         card.loyalty = info["loyalty"]
  23.     cost = info['cost']
  24.     if cost:
  25.         card.cost = info['cost']
  26.     card.textbox = "\n".join(info['text'])
  27.  
  28. def create_card_code(text):
  29.     return carddict_to_code(parse_card_oracle(text))
  30.  
  31. # Convert card oracle text into a dictionary containing card fields
  32. def parse_card_oracle(oracle_text):
  33.     if "|" in oracle_text: oracle_text = oracle_text.split("|")
  34.     else: oracle_text = oracle_text.split("\n")
  35.     lines_iter = iter(oracle_text)
  36.     supertypes = ""
  37.     subtypes = ""
  38.     name = lines_iter.next()
  39.     cost = lines_iter.next()
  40.  
  41.     if set(cost).difference(set("1234567890XYZWUBRGPxyzwubrgp(){}/ ")):
  42.         # This card doesn't have a cost
  43.         types = cost
  44.         cost = ""
  45.     else:
  46.         cost = cost.replace(" ", '').upper().replace("P}", "/P}").replace("//P}", "/P}")
  47.         types = lines_iter.next()
  48.     if " -- " in types: types = types.split(" -- ")
  49.     else: types = types.split(" - ")
  50.     if len(types) == 2: subtypes = set(types[1].split())
  51.     types = types[0].split()
  52.     supertypes = list(set(types).intersection(set(("Basic", "Snow", "Legendary", "World"))))
  53.     types = list(set(types).difference(set(("Basic", "Snow", "Legendary", "World"))))
  54.  
  55.     if "Creature" in types:
  56.         pt = lines_iter.next().split("/")
  57.     if "Planeswalker" in types:
  58.         loyalty = lines_iter.next()
  59.         if loyalty.endswith((" loyalty", " loyalty.")):
  60.             loyalty = loyalty.split(" ")[0]
  61.  
  62.     text = []
  63.     for l in lines_iter:
  64.         text.append(l)
  65.  
  66.     text = parse_text(text)
  67.  
  68.     for i, line in enumerate(text):
  69.         text[i] = line.replace(name, "~").replace("named ~", "named %s"%name).replace("{untap}", "{Q}")
  70.  
  71.     card = {"name": name, "cost": cost, "supertypes": supertypes, "types": types, "subtypes": subtypes, "text": text}
  72.  
  73.     if "Creature" in types:
  74.         try:
  75.             card["power"], card["toughness"] = map(conv_pt, pt)
  76.         except:
  77.             print repr(card)
  78.             raise
  79.     if "Planeswalker" in types:
  80.         try:
  81.             card["loyalty"] = int(loyalty)
  82.         except:
  83.             print repr(card)
  84.             raise
  85.  
  86.     return card
  87.  
  88. def parse_text(text):
  89.     new_text = []
  90.     for txt in text:
  91.         pos = -1 #txt.find("Choose one -")
  92.         if pos != -1:
  93.             choices = txt.split(" - ")[1].split(";")
  94.             temp = []
  95.             for c in choices:
  96.                 newtemp = c.split(" or")
  97.                 if len(newtemp) == 1: newtemp = newtemp[0]
  98.                 else: newtemp = newtemp[1].strip()
  99.                 temp.append(newtemp)
  100.             new_text.extend(temp)
  101.         else:
  102.             new_text.append(txt)
  103.  
  104.     # Now get rid of parentheses
  105.     removal = []
  106.     for i,t in enumerate(new_text):
  107.         m = re.match('(.*) (\(.*\))',t)
  108.         if m: new_text[i] = m.groups()[0]
  109.         elif t.startswith("("):
  110.             removal.append(i - len(removal))
  111.     for idx in removal:
  112.         del new_text[idx]
  113.     return new_text
  114.  
  115. def conv_pt(pt):
  116.     if set(pt).difference(set("1234567890-+*")): pt = 0
  117.     else:
  118.         try: pt = eval(pt.replace("*","0"))
  119.         except Exception, err:
  120.             print "%s (%s: %s)"%(pt, err.__class__.__name__, err)
  121.             pt = 0
  122.     return pt
  123.  
  124.  
  125. # Convert dictionary containing card definition into sample Incantus template
  126. def carddict_to_code(card):
  127.     attributes = ["name", "cost", "supertypes", "types", "subtypes", "power", "toughness", "loyalty", "text"]
  128.     characteristics = set(["types", "supertypes", "subtypes"])
  129.  
  130.     name = card["name"]
  131.     lines = []
  132.  
  133.     #if card["text"][0] in ("Land", "Common", "Uncommon", "Rare", "Mythic Rare"): card["text"] = card["text"][1:] # Stupid hack for something going screwy on magiccards.info
  134.  
  135.     for k in attributes:
  136.         val = card.get(k, None)
  137.         if val:
  138.             if k in characteristics:
  139.                 if type(val) == list or type(val) == set: val = ", ".join(val)
  140.                 lines.append("%s = %s"%(k, val))
  141.             else:
  142.                 lines.append("%s = %s"%(k, repr(val)))
  143.  
  144.     lines.append('')
  145.     # Now the rest of the file
  146.     cardtypes = card["types"]
  147.     if "Instant" in cardtypes:
  148.         lines.append(nonperm%"instant")
  149.     elif "Sorcery" in  cardtypes:
  150.         lines.append(nonperm%"sorcery")
  151.     # Otherwise we have permanents
  152.     else:
  153.         #skip = []
  154.         #if "Enchantment" in cardtypes and "Aura" in card["subtypes"]:
  155.         #    lines.append("abilities.add(enchant(# XXX target type))\n")
  156.         #    skip = [i for i, line in enumerate(card["text"]) if line.startswith("Enchant ")]
  157.         lines.append("#################################")
  158.         lines.append("")
  159.  
  160.         # Parse the text box here
  161.         for i, txt in enumerate(card["text"]):
  162.             #if i in skip: continue # Silly hack until we can treat enchant like a real keyword.
  163.             # Check for keywords
  164.             global it
  165.             it = None
  166.             abilities = parse_abilities(txt, card, i)
  167.             #if txt.startswith("When") or txt.startswith("At") or "- When" in txt or "- At" in txt: # Chroma - When ~ comes into play...
  168.             #    lines.append(triggered%i)
  169.             #elif "mana pool" in txt:
  170.             #    lines.append(mana%(i, parse_cost(txt.split(":")[0], name)))
  171.             #elif ":" in txt:
  172.             #    lines.append(activated%(i, parse_cost(txt.split(":")[0], name)))
  173.             #elif "enters the battlefield" in txt:
  174.             #    lines.append(comes_into_play%i)
  175.             #elif " get " in txt or " have " in txt:
  176.             #    lines.append(static_tracking%i)
  177.             #elif "Enchanted" in txt or "Equipped" in txt:
  178.             #    lines.append(attached%i)
  179.             #elif keywords: # To prevent "equipped" from matching "equip"
  180.             #    lines.extend(keywords)
  181.             lines.extend(abilities)
  182.             #else:
  183.             #    #lines.append("# Another ability here - can't determine the type\n")
  184.             #    # If it doesn't match any of the above, it must be a static ability (by definition).
  185.             #    lines.append(static%i)
  186.     return '\n'.join(lines)
  187.  
  188. common_abilities = dict([("~ enters the battlefield tapped.", "abilities.add(enters_battlefield_tapped())\n"),
  189.                          ("~ is unblockable.", "abilities.add(this_card_is_unblockable())\n"),
  190.                          ("~ is indestructible.", "abilities.add(this_card_is_indestructible())\n"),
  191.                          ("~ attacks each turn if able.", "abilities.add(this_card_must_attack())\n"),
  192.                          ("~ can't block.", "abilities.add(this_card_cant_block())\n"),
  193.                          ("~ doesn't untap during your untap step.", "abilities.add(this_card_doesnt_untap())\n"),
  194.                          ])
  195.  
  196. def parse_abilities(txt, card, i):
  197.     if not txt: return []
  198.     if txt in common_abilities:
  199.         return [common_abilities[txt]]
  200.     else:
  201.         if re.match(r"^(\w+) -{1,2} (.+)", txt): # Strip out ability words.
  202.             txt = re.match(r"^(\w+) -{1,2} (.+)", txt).group(2)
  203.         if txt.startswith(("When", "At")):
  204.             return [parse_triggered(txt, card, i)]
  205.         else: # I tried this as a regexp, but it took too long to parse non-activated abilities.
  206.             inquote = False
  207.             for char in txt:
  208.                 if char == "\"":
  209.                     inquote = not inquote
  210.                 elif char == ":" and not inquote:
  211.                     break
  212.             else:
  213.                 return [parse_static(txt, card, i)]
  214.             return [parse_activated(txt, card, i)]
  215.  
  216. def parse_triggered(txt, card, i):
  217.     condition = None
  218.     trigger = None
  219.     effect = None
  220.     end_trigger = 0
  221.     global it
  222.     if txt.split(" ")[0] in ("When", "Whenever"):
  223.         # logic for "action" triggers ("whenever <object> deals combat damage", "when <object> is put into a graveyard from the battlefield", "whenever a player loses the game")
  224.         if txt.startswith("When ~ enters the battlefield, "):
  225.             end_trigger = 31
  226.             trigger = 'EnterTrigger("battlefield", source_match)'
  227.             it = "~"
  228.         elif txt.startswith("When ~ leaves the battlefield, "):
  229.             end_trigger = 31
  230.             trigger = 'LeaveTrigger("battlefield", source_match)'
  231.             it = "~"
  232.         elif re.match(r"^When(ever)? ((equipped|enchanted|fortified) (permanent|creature|artifact|enchantment|land|planeswalker)) leaves the battlefield, ", txt):
  233.             match = re.match(r"^When(ever)? ((equipped|enchanted|fortified) (permanent|creature|artifact|enchantment|land|planeswalker)) leaves the battlefield, ", txt)
  234.             end_trigger = len(match.group(0))
  235.             trigger = 'LeaveTrigger("battlefield", attached_match)'
  236.             it = match.group(2)
  237.         elif txt.startswith(("When ~ is put into a graveyard from the battlefield, ", "When ~ dies, ")):
  238.             end_trigger = 13 if txt.split(" ")[2] == "dies," else 53
  239.             trigger = 'EnterFromTrigger("graveyard", "battlefield", source_match)'
  240.             it = "~"
  241.         elif re.match(r"^When(ever)? (equipped|enchanted) creature dies, ", txt):
  242.             match = re.match(r"^When(ever)? ((equipped|enchanted) creature) dies, ", txt)
  243.             end_trigger = len(match.group(0))
  244.             trigger = 'EnterFromTrigger("graveyard", "battlefield", attached_match, player="any")'
  245.             it = match.group(2)
  246.         elif txt.startswith(("Whenever another creature enters the battlefield, ", "When another creature enters the battlefield, ")):
  247.             end_trigger = 50 if txt.startswith("Whenever") else 46
  248.             trigger = 'EnterTrigger("battlefield", condition, player="any")'
  249.             condition = 'def condition(source, card): return not card == source and isCreature(card)'
  250.         elif txt.startswith(("Whenever a creature dealt damage by ~ this turn dies, ", "When a creature dealt damage by ~ this turn dies, ")):
  251.             end_trigger = 54 if txt.startswith("Whenever") else 50
  252.             trigger = 'EnterFromTrigger("graveyard", "battlefield", condition, player="any")'
  253.             condition = 'def condition(source, card): return isCreature(card) and damage_tracker.dealt(source, card)'
  254.         elif re.match(r"^When(ever)? a creature dealt damage by ((equipped|enchanted|fortified) (permanent|artifact|creature|enchantment|land|planeswalker)) this turn dies, ", txt):
  255.             match = re.match(r"^When(ever)? a creature dealt damage by ((equipped|enchanted|fortified) (permanent|artifact|creature|enchantment|land|planeswalker)) this turn dies, ", txt)
  256.             end_trigger = len(match.group(0))
  257.             trigger = 'EnterFromTrigger("graveyard", "battlefield", condition, player="any")'
  258.             condition = 'def condition(source, card): return source.attached_to and isCreature(card) and damage_tracker.dealt(source.attached_to, card)'
  259.         elif txt.startswith(("Whenever ~ deals damage, ", "When ~ deals damage, ")):
  260.             end_trigger = 25 if txt.startswith("Whenever") else 21
  261.             trigger = 'DealsDamageTrigger(sender_match)'
  262.             it = "~"
  263.         elif re.match(r"^When(ever)? ((enchanted|equipped|fortified) (permanent|artifact|creature|enchantment|land|planeswalker)) deals damage, ", txt):
  264.             match = re.match(r"^When(ever)? ((enchanted|equipped|fortified) (permanent|artifact|creature|enchantment|land|planeswalker)) deals damage, ", txt)
  265.             end_trigger = len(match.group(0))
  266.             trigger = 'DealsDamageTrigger(condition)'
  267.             condition = 'def condition(source, sender): return sender == source.attached_to'
  268.             it = match.group(2)
  269.         elif txt.startswith(("Whenever ~ is dealt damage, ", "When ~ is dealt damage, ")):
  270.             end_trigger = 28 if txt.startswith("Whenever") else 24
  271.             trigger = 'ReceivesDamageTrigger(sender_match)'
  272.             it = "~"
  273.         elif txt.startswith(("Whenever ~ is dealt combat damage, ", "When ~ is dealt combat damage, ")):
  274.             end_trigger = 35 if txt.startswith("Whenever") else 31
  275.             trigger = 'ReceivesDamageTrigger(condition)'
  276.             condition = 'def condition(source, sender, combat): sender == source and combat'
  277.             it = "~"
  278.         elif re.match(r"^When(ever)? ((enchanted|equipped) (creature|planeswalker)) is dealt( combat)? damage, ", txt):
  279.             match = re.match(r"^When(ever)? ((enchanted|equipped) (creature|planeswalker)) is dealt( combat)? damage, ", txt)
  280.             end_trigger = len(match.group(0))
  281.             trigger = 'ReceivesDamageTrigger(condition)'
  282.             tmp = ("", "")
  283.             if match.group(5):
  284.                 tmp = (", combat", " and combat")
  285.             condition = 'def condition(source, sender%s): sender == source.attached_to%s'%tmp
  286.             it = match.group(2)
  287.         elif txt.startswith(("Whenever ~ deals damage to a player, ", "When ~ deals damage to a player, ")):
  288.             end_trigger = 37 if txt.startswith("Whenever") else 33
  289.             condition = 'def condition(source, sender, to): return sender == source and isPlayer(to)'
  290.             trigger = 'DealsDamageToTrigger(condition)'
  291.             it = "~"
  292.         elif re.match(r"^When(ever)? ((enchanted|equipped|fortified) (permanent|artifact|creature|enchantment|land|planeswalker)) deals damage to a player, ", txt):
  293.             match = re.match(r"^When(ever)? ((enchanted|equipped|fortified) (permanent|artifact|creature|enchantment|land|planeswalker)) deals damage to a player, ", txt)
  294.             end_trigger = len(match.group(0))
  295.             trigger = 'DealsDamageToTrigger(condition)'
  296.             condition = 'def condition(source, sender, to): return sender == source.attached_to and isPlayer(to)'
  297.             it = match.group(2)
  298.         elif txt.startswith(("Whenever ~ deals combat damage to a player, ", "When ~ deals combat damage to a player, ")):
  299.             end_trigger = 44 if txt.startswith("Whenever") else 40
  300.             condition = 'def condition(source, sender, to, combat): return sender == source and isPlayer(to) and combat'
  301.             trigger = 'DealsDamageToTrigger(condition)'
  302.             it = "~"
  303.         elif txt.startswith(("Whenever ~ deals combat damage, ", "When ~ deals combat damage, ")):
  304.             end_trigger = 32 if txt.startswith("Whenever") else 28
  305.             condition = 'def condition(source, sender, to, combat): return sender == source and combat'
  306.             trigger = 'DealsDamageToTrigger(condition)'
  307.             it = "~"
  308.         elif re.match(r"^When(ever)? ((enchanted|equipped) creature) deals combat damage( to a player)?, ", txt):
  309.             match = re.match(r"^When(ever)? ((enchanted|equipped) creature) deals combat damage( to a player)?, ", txt)
  310.             end_trigger = len(match.group(0))
  311.             trigger = 'DealsDamageToTrigger(condition)'
  312.             condition = 'def condition(source, sender, to, combat): return sender == source.attached_to%s and combat'%(" and isPlayer(to)" if match.group(4) else "")
  313.             it = match.group(2)
  314.         elif txt.startswith(("Whenever ~ attacks, ", "When ~ attacks, ")):
  315.             end_trigger = 20 if txt.startswith("Whenever") else 16
  316.             trigger = 'Trigger(AttackerDeclaredEvent(), sender_match)'
  317.             it = "~"
  318.         elif txt.startswith(("Whenever ~ becomes blocked, ", "When ~ becomes blocked, ")):
  319.             end_trigger = 28 if txt.startswith("Whenever") else 24
  320.             trigger = 'Trigger(AttackerBlockedEvent(), sender_match)'
  321.             it = "~"
  322.         elif txt.startswith(("Whenever ~ becomes blocked by a creature, ", "When ~ becomes blocked by a creature, ")):
  323.             end_trigger = 42 if txt.startswith("Whenever") else 38
  324.             condition = 'def condition(source, attacker): return attacker == source'
  325.             trigger = 'Trigger(BlockerDeclaredEvent(), condition)'
  326.         elif re.match(r"^When(ever)? ((enchanted|equipped) creature) (attacks|becomes blocked|becomes blocked by a creature), ", txt):
  327.             match = re.match(r"^When(ever)? ((enchanted|equipped) creature) (attacks|becomes blocked|becomes blocked by a creature), ", txt)
  328.             end_trigger = len(match.group(0))
  329.             if match.group(4) == "attacks": trigger = "Trigger(AttackerDeclaredEvent(), condition)"
  330.             elif match.group(4) == "becomes blocked": trigger = "Trigger(AttackerBlockedEvent(), condition)"
  331.             else: trigger = "Trigger(BlockerDeclaredEvent(), condition)"
  332.             if match.group(4) == "becomes blocked by a creature":
  333.                 condition = "def condition(source, attacker): return attacker == source.attached_to"
  334.             else:
  335.                 condition = "def condition(source, sender): sender == source.attached_to"
  336.                 it = match.group(2)
  337.         elif txt.startswith(("Whenever ~ attacks and isn't blocked, ", "When ~ attacks and isn't blocked, ")):
  338.             end_trigger = 38 if txt.startswith("Whenever") else 34
  339.             condition = 'def condition(source, combat_assignment): return source.attacking and not combat_assignment[source]'
  340.             trigger = 'Trigger(DeclareBlockersEvent(), condition)'
  341.             it = "~"
  342.         elif re.match(r"^When(ever)? ((enchanted|equipped) creature) attacks and isn't blocked, ", txt):
  343.             match = re.match(r"^When(ever)? ((enchanted|equipped) creature) attacks and isn't blocked, ", txt)
  344.             end_trigger = len(match.group(0))
  345.             condition = 'def condition(source, combat_assignment): return source.attached_to and source.attached_to.attacking and not combat_assignment[source.attached_to]'
  346.             trigger = 'Trigger(DeclareBlockersEvent(), condition)'
  347.             it = match.group(2)
  348.         elif txt.startswith(("Whenever ~ blocks, ", "When ~ blocks, ")):
  349.             end_trigger = 19 if txt.startswith("Whenever") else 15
  350.             trigger = 'Trigger(BlockerDeclaredEvent(), sender_match)'
  351.             it = "~"
  352.         elif txt.startswith(("Whenever ~ blocks a creature, ", "When ~ blocks a creature, ")):
  353.             end_trigger = 30 if txt.startswith("Whenever") else 26
  354.             trigger = 'Trigger(BlockerDeclaredEvent(), sender_match)'
  355.             it = "~"
  356.         elif re.match(r"^When(ever)? ((enchanted|equipped) creature) blocks( a creature)?, ", txt):
  357.             match = re.match(r"^When(ever)? ((enchanted|equipped) creature) blocks( a creature)?, ", txt)
  358.             end_trigger = len(match.group(0))
  359.             condition = 'def condition(source, sender): return sender == source.attached_to'
  360.             trigger = 'Trigger(BlockerDeclaredEvent(), condition)'
  361.             it = match.group(2)
  362.         elif txt.startswith(("Whenever ~ becomes the target of a spell or ability, ", "When ~ becomes the target of a spell or ability, ")):
  363.             end_trigger = 53 if txt.startswith("Whenever") else 49
  364.             trigger = 'Trigger(TargetedByEvent(), sender_match)'
  365.             it = "~"
  366.         elif re.match(r"^When(ever)? ((enchanted|equipped|fortified) (permanent|artifact|creature|enchantment|land|planeswalker)) becomes the target of a spell or ability, ", txt):
  367.             match = re.match(r"^When(ever)? ((enchanted|equipped|fortified) (permanent|artifact|creature|enchantment|land|planeswalker)) becomes the target of a spell or ability, ", txt)
  368.             end_trigger = len(match.group(0))
  369.             trigger = 'Trigger(TargetedByEvent(), condition)'
  370.             condition = 'def condition(source, sender): return sender == source.attached_to'
  371.             it = match.group(2)
  372.         elif txt.startswith(("Whenever you discard a card, ", "When you discard a card, ")):
  373.             end_trigger = 29 if txt.startswith("Whenever") else 25
  374.             condition = 'def condition(source, sender): return sender == source.controller'
  375.             trigger = 'Trigger(DiscardCardEvent(), condition)'
  376.         elif txt.startswith(("Whenever an opponent discards a card, ", "When an opponent discards a card, ")):
  377.             end_trigger = 38 if txt.startswith("Whenever") else 34
  378.             condition = 'def condition(source, sender): return sender in source.controller.opponents'
  379.             trigger = 'Trigger(DiscardCardEvent(), condition)'
  380.         elif txt.startswith(("Whenever a player discards a card, ", "When you discard a card, ")):
  381.             end_trigger = 35 if txt.startswith("Whenever") else 31
  382.             trigger = 'Trigger(DiscardCardEvent())'
  383.         elif re.match(r"When(ever)? (you|a player) casts? an? (white|blue|black|red|green|artifact|creature|enchantment|planeswalker|tribal|permanent|instant|sorcery|instant or sorcery|colorless|monocolored|multicolored) spell, ", txt):
  384.             match = re.match(r"When(ever)? (you|a player) casts? an? (white|blue|black|red|green|artifact|creature|enchantment|planeswalker|tribal|permanent|instant|sorcery|instant or sorcery|colorless|monocolored|multicolored) spell, ", txt)
  385.             end_trigger = len(match.group(0))
  386.             who, type = match.group(2, 3)
  387.             trigger = "Trigger(SpellPlayedEvent(), condition)"
  388.             condition = "def condition(source, sender, spell): return "
  389.             if who == "you":
  390.                 condition += "sender == source.controller and "
  391.             if type in ("white", "blue", "black", "red", "green"):
  392.                 condition += "spell.color == %s"%(type.capitalize())
  393.             elif type in ("artifact", "creature", "enchantment", "planeswalker", "tribal", "instant", "sorcery"):
  394.                 condition += "spell.types == %s"%(type.capitalize())
  395.             elif type == "instant or sorcery":
  396.                 condition += "spell.types == Instant or spell.types == Sorcery"
  397.             elif type == "permanent":
  398.                 condition += "spell.types.intersects(set([Artifact, Enchantment, Creature, Land, Planeswalker]))"
  399.             elif type == "colorless":
  400.                 condition += "len(spell.color) == 0"
  401.             elif type == "monocolored":
  402.                 condition += "len(spell.color) == 1"
  403.             elif type == "multicolored":
  404.                 condition += "len(spell.color) > 1"
  405.     elif txt.split(" ")[0] == "At":
  406.         # logic for "time" triggers ("at the beginning of your upkeep", "at the beginning of the end step", "at the beginning of your precombat main phase")
  407.         if txt.startswith("At the beginning of your upkeep, "):
  408.             end_trigger = 33
  409.             trigger = 'YourUpkeepTrigger()'
  410.         elif txt.startswith("At the beginning of your end step, "):
  411.             end_trigger = 35
  412.             trigger = 'PhaseTrigger(EndTurnStepEvent(), controller_match)'
  413.         elif txt.startswith("At the beginning of each upkeep, "):
  414.             end_trigger = 33
  415.             trigger = 'PhaseTrigger(UpkeepStepEvent())'
  416.         elif txt.startswith(("At the beginning of each end step, ", "At the beginning of the end step, ")):
  417.             end_trigger = (35 if "each" in txt else 34)
  418.             trigger = 'PhaseTrigger(EndTurnStepEvent())'
  419.         elif txt.startswith("At the beginning of your precombat main phase, "):
  420.             end_trigger = 47
  421.             trigger = 'PhaseTrigger(MainPhase1Event(), controller_match)'
  422.         elif txt.startswith("At the beginning of your postcombat main phase, "):
  423.             end_trigger = 48
  424.             trigger = 'PhaseTrigger(MainPhase2Event(), controller_match)'
  425.     effect = txt[end_trigger:]
  426.     match = re.match(r"choose (one|two|three|four|five|six|seven|eight|nine|ten) -{1,2} (.+)", effect)
  427.     if match:
  428.         num = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(match.group(1)) + 1
  429.         options = [item[0].upper() + item[1:] + ("" if item.endswith(".") else ".") for item in match.group(2).split("; or ")]
  430.         effects = []
  431.         for option in options:
  432.             if it is not None: option = re.sub(r"\bit\b", it, option)
  433.             effects.append(parse_effect(option, card, triggered=True))
  434.         text = ""
  435.         for i, effect in enumerate(effects):
  436.             if effect:
  437.                 if len(effect) == 4:
  438.                     vars, target, extra, effect = effect
  439.                 else:
  440.                     vars, target, effect = effect
  441.                     extra = None
  442.             else:
  443.                 vars, target, extra, effect = None, None, None, ""
  444.             if vars: vars = ", %s"%(", ".join(vars))
  445.             else: vars = ""
  446.             if isinstance(target, (list, tuple)) and len(target) == 1: target = target[0]
  447.             if not target: target = "target = yield NoTarget()"
  448.             elif isinstance(target, (list, tuple)): target = "targets = yield Target(%s)"%("), Target(".join(target))
  449.             else: target = "target = yield Target(%s)"%target
  450.             text +=     "%sdef mode_%i(controller, source%s):\n"%(("    " if i > 0 else ""), i+1, vars)
  451.             text +=     "        '''%s'''\n"%options[i]
  452.             text +=     "        %s\n"%target
  453.             if extra:
  454.                 text += "        %s\n"%extra
  455.             text +=     "        %s\n"%effect
  456.             text +=     "        yield\n"
  457.         text += "    effects = modal_triggered_effects(%s, choose=%i)\n"%(", ".join(["mode_%i"%mode for mode in range(1, i+2)]), num)
  458.         effect = text
  459.     else:
  460.         if it is not None: effect = re.sub(r"\bit\b", it, effect)
  461.         effect = parse_effect(effect, card, triggered = True)
  462.     if effect and not isinstance(effect, str):
  463.         if len(effect) == 4: vars, target, extra, effect = effect
  464.         else:
  465.             vars, target, effect = effect
  466.             extra = None
  467.         if vars: vars = ", %s"%(", ".join(vars))
  468.         else: vars = ""
  469.         if isinstance(target, (list, tuple)) and len(target) == 1: target = target[0]
  470.         if not target: target = "target = yield NoTarget()"
  471.         elif isinstance(target, (list, tuple)): target = "targets = yield Target(%s)"%("), Target(".join(target))
  472.         else: target = "target = yield Target(%s)"%target
  473.         temp = "def effects(controller, source%s):\n"%vars
  474.         temp += "        %s\n"%target
  475.         if extra:
  476.             temp += "        %s\n"%extra
  477.         temp += "        %s\n"%effect
  478.         temp += "        yield"
  479.         effect = temp
  480.     if trigger is None:
  481.         return triggered%i
  482.     else:
  483.         if not effect:
  484.             effect = '''def effects(controller, source):
  485.        target = yield NoTarget()
  486.        
  487.        yield'''
  488.         text =      "@triggered(txt=text[%i])\n"%i
  489.         text +=     "def ability():\n"
  490.         if condition:
  491.             text += "    %s\n"%condition
  492.         text +=     "    %s\n"%effect
  493.         text +=     "    return %s, effects\n"%trigger
  494.         text +=     "abilities.add(ability)\n"
  495.         return text
  496.  
  497. def parse_activated(txt, card, i):
  498.     limit = None
  499.     target = None
  500.     effect = None
  501.     extra = None
  502.     cost, txt = txt.split(": ", 1)
  503.     cost = parse_cost(cost, card)
  504.     if ". Activate this ability" in txt:
  505.         limit = txt.index(". Activate this ability") + 1
  506.         effect = txt[:limit]
  507.         limit = parse_limit(txt[limit+1:], card)
  508.         txt = effect
  509.         effect = None
  510.  
  511.     match = re.match(r"Choose (one|two|three|four|five|six|seven|eight|nine|ten) -{1,2} (.+)", txt)
  512.     if match:
  513.         num = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(match.group(1)) + 1
  514.         options = [item[0].upper() + item[1:] + ("" if item.endswith(".") else ".") for item in match.group(2).split("; or ")]
  515.         effects = []
  516.         for option in options:
  517.             effects.append(parse_effect(option, card))
  518.         if limit: limit = ", limit=%s"%limit
  519.         else: limit = ""
  520.         text =  "@activated(txt=text[%i]%s)\n"%(i, limit)
  521.         text += "def ability():\n"
  522.         for i, effect in enumerate(effects):
  523.             if effect:
  524.                 if len(effect) == 3:
  525.                     target, extra, effect = effect
  526.                 else:
  527.                     target, effect = effect
  528.                     extra = None
  529.             else:
  530.                 target, extra, effect = None, None, ""
  531.             if isinstance(target, (list, tuple)) and len(target) == 1: target = target[0]
  532.             if not target: target = "target = yield NoTarget()"
  533.             elif isinstance(target, (list, tuple)): target = "targets = yield Target(%s)"%("), Target(".join(target))
  534.             else: target = "target = yield Target(%s)"%target
  535.             text +=     "    def mode_%i(controller, source):\n"%(i+1)
  536.             text +=     "        '''%s'''\n"%options[i]
  537.             text +=     "        cost = yield NoCost()\n"
  538.             text +=     "        %s\n"%target
  539.             if extra:
  540.                 text += "        %s\n"%extra
  541.             text +=     "        %s\n"%effect
  542.             text +=     "        yield\n"
  543.         text += "    @modal_effects(%s, choose=%i)\n"%(", ".join(["mode_%i"%mode for mode in range(1, i+2)]), num)
  544.         text += "    def effects(controller, source):\n"
  545.         text += "        cost = yield %s\n"%cost
  546.         text += "    return effects\n"
  547.         text += "abilities.add(ability)\n"
  548.         return text
  549.     else:
  550.         effect = parse_effect(txt, card)
  551.     if effect is None and limit is None:
  552.         if "mana pool" in txt: return mana%(i, cost)
  553.         else: return activated%(i, cost)
  554.     elif effect is None:
  555.         if "mana pool" in txt: return mana_limit%(i, limit, cost)
  556.         else: return activated_limit%(i, limit, cost)
  557.     else:
  558.         if len(effect) == 3:
  559.             target, extra, effect = effect
  560.         else:
  561.             target, effect = effect
  562.             extra = None
  563.         if isinstance(target, (list, tuple)) and len(target) == 1: target = target[0]
  564.         if not target: target = "target = yield NoTarget()"
  565.         elif isinstance(target, (list, tuple)): target = "targets = yield Target(%s)"%("), Target(".join(target))
  566.         else: target = "target = yield Target(%s)"%target
  567.         if limit: limit = ", limit=%s"%limit
  568.         else: limit = ""
  569.         text =      "@%s(txt=text[%i]%s)\n"%(("mana" if (target.endswith("NoTarget()") and "add_mana" in effect) else "activated"), i, limit)
  570.         text +=     "def ability():\n"
  571.         text +=     "    def effects(controller, source):\n"
  572.         text +=     "        cost = yield %s\n"%cost
  573.         text +=     "        %s\n"%target
  574.         if extra:
  575.             text += "        %s\n"%extra
  576.         text +=     "        %s\n"%effect
  577.         text +=     "        yield\n"
  578.         text +=     "    return effects\n"
  579.         text +=     "abilities.add(ability)\n"
  580.     return text
  581.  
  582. def parse_static(txt, card, i):
  583.     effect = None
  584.     condition = None
  585.     after_condition = 0
  586.     static_type = "static"
  587.     keywords = parse_keywords(txt, card)
  588.     tracking = parse_tracking(txt, card, i)
  589.     if keywords:
  590.         return '\n'.join(keywords)
  591.     elif "enters the battlefield" in txt or "enter the battlefield" in txt:
  592.         return parse_etb(txt, card, i)
  593.     elif tracking:
  594.         return tracking
  595.     if " as long as " in txt:
  596.         # So the condition and effect parse normally.
  597.         txt = "As long as " + txt[txt.index(" as long as ") + 12:-1] + ", " + txt[:txt.index(" as long as ")] + "."
  598.     if txt.startswith("As long as "):
  599.         condition = parse_condition(txt[:txt.index(", ")+2], card)
  600.         after_condition = txt.index(", ")+2
  601.     global it
  602.     if it is not None: txt = txt[:after_condition] + re.sub(r"\bit\b", it, txt[after_condition:])
  603.     if txt[after_condition:].startswith(("Equipped ", "equipped ", "Enchanted ", "enchanted ", "Fortified ", "fortified ")):
  604.         effect = parse_attached_effect(txt[after_condition:], card)
  605.         static_type = "attached"
  606.     else:
  607.         effect = parse_static_effect(txt[after_condition:], card)
  608.     if isinstance(effect, tuple): extra, effect = effect
  609.     else: extra = None
  610.     if effect is None and condition is None:
  611.         return (static if static_type == "static" else attached)%i
  612.     else:
  613.         if not effect: effect = "# XXX"
  614.         text =      "@%s(txt=text[%i])\n"%(static_type, i)
  615.         text +=     "def ability():\n"
  616.         if condition:
  617.             text += "    def condition(source):\n"
  618.             text += "        %s\n"%condition
  619.         text +=     "    def effects(source):\n"
  620.         if static_type == "attached":
  621.             text += "        card = source.attached_to\n"
  622.         if extra:
  623.             text += "        %s\n"%extra
  624.         text +=     "        yield %s\n"%effect
  625.         text +=     "    return %scondition, effects\n"%("" if condition else "no_")
  626.         text +=     "abilities.add(ability)\n"
  627.         return text
  628.  
  629. def parse_etb(txt, card, i):
  630.     extra = None
  631.     before = None
  632.     effect = None
  633.     match = re.match(r"^~ enters the battlefield( tapped)? with (an?|two|three|four|five|six|seven|eight|nine|ten) (([+-]\d+)/([+-]\d+)|\w+) counter(s?) on it.", txt)
  634.     if match:
  635.         effect = "" if not match.group(1) else "self.tapped = True\n        "
  636.         num = match.group(2)
  637.         counter = match.group(3)
  638.         if counter[0] in "+-":
  639.             power, toughness = match.group(4, 5)
  640.             counter = "PowerToughnessCounter(%i, %i)"%(int(power), int(toughness))
  641.         else:
  642.             counter = repr(counter)
  643.         if num in ("a", "an"):
  644.             effect += "self.add_counters(%s)"%counter
  645.         else:
  646.             num = ["two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 2
  647.             effect += "self.add_counters(%s, number=%i)"%(counter, num)
  648.     match = re.match(r"^You may have ~ enter the battlefield as a copy of any creature on the battlefield.", txt)
  649.     if match:
  650.         extra = "cards = []"
  651.         before = '''cards[:] = self.controller.choose_from_zone(cardtype=isCreature, action="clone", required=False, all=True)
  652.        if cards: self.clone(cards[0])
  653.        return True'''
  654.         effect = "if cards: self.clone(cards[0])"
  655.     if effect is None:
  656.         return comes_into_play%i
  657.     else:
  658.         text =      "@enters_battlefield(txt=text[%i])\n"%i
  659.         text +=     "def ability():\n"
  660.         if extra:
  661.             text += "    %s\n"%extra
  662.         if before:
  663.             text += "    def before(self):\n"
  664.             text += "        %s\n"%before
  665.         text +=     "    def enterBattlefield(self):\n"
  666.         text +=     "        \"\"\"%s\"\"\"\n"%txt
  667.         text +=     "        %s\n"%effect
  668.         text +=     "    return %sbefore, enterBattlefield\n"%("" if before else "no_")
  669.         text +=     "abilities.add(ability)\n"
  670.         return text
  671.  
  672. def parse_tracking_condition(other, what, who, card):
  673.     condition = "not card == source" if other else ""
  674.     events = []
  675.     for word in what.split(" "):
  676.         comp = parse_plural(word.lower(), card)
  677.         if comp in ("white", "blue", "black", "red", "green"):
  678.             if condition: condition += " and "
  679.             condition += "card.color == " + comp.capitalize()
  680.             if not "ColorModifiedEvent()" in events: events.append("ColorModifiedEvent()")
  681.         elif comp in ("nonwhite", "nonblue", "nonblack", "nonred", "nongreen"):
  682.             if condition: condition += " and "
  683.             condition += "not card.color == " + comp[3:].capitalize()
  684.             if not "ColorModifiedEvent()" in events: events.append("ColorModifiedEvent()")
  685.         elif comp in ("artifact", "creature", "enchantment", "land", "planeswalker", "tribal", "token"):
  686.             if condition: condition += " and "
  687.             condition += "is" + comp.capitalize() + "(card)"
  688.             if not "TypesModifiedEvent()" in events: events.append("TypesModifiedEvent()")
  689.         elif comp in ("nonartifact", "noncreature", "nonenchantment", "nonland", "nonplaneswalker", "nontribal", "nontoken"):
  690.             if condition: condition += " and "
  691.             condition += "not is" + comp[3:].capitalize() + "(card)"
  692.             if not "TypesModifiedEvent()" in events: events.append("TypesModifiedEvent()")
  693.         elif comp.startswith("non"):
  694.             if condition: condition += " and "
  695.             condition += "not card.subtypes == " + comp[3:].capitalize().replace("-", "").replace("'", "")
  696.             if not "SubtypesModifiedEvent()" in events: events.append("SubtypesModifiedEvent()")
  697.         elif word[0].isupper():
  698.             if condition: condition += " and "
  699.             condition += "card.subtypes == " + comp.capitalize().replace("-", "").replace("'", "")
  700.             if not "SubtypesModifiedEvent()" in events: events.append("SubtypesModifiedEvent()")
  701.     if who:
  702.         if condition: condition += " and "
  703.         condition += "card.controller == source.controller"
  704.     condition = "return " + condition
  705.     if len(events) == 1: events = events[0]
  706.     else: events = "(%s)"%(", ".join(events))
  707.     return condition, events
  708.  
  709. def parse_tracking(txt, card, i):
  710.     effect = None
  711.     condition = None
  712.     conditional = None
  713.     extra = None
  714.     events = None
  715.     if " as long as " in txt:
  716.         # So the condition and effect parse normally.
  717.         txt = "As long as " + txt[txt.index(" as long as ") + 12:-1] + ", " + txt[:txt.index(" as long as ")] + "."
  718.     if txt.startswith("As long as "):
  719.         conditional = parse_condition(txt[:txt.index(", ")+2], card)
  720.         txt = txt[txt.index(", ")+2:]
  721.     match = re.match(r"(Other )?(.+?)( you control)? get ([+-]\d+)/([+-]\d+)\.$", txt)
  722.     if match:
  723.         other, what, who, power, toughness = match.group(1, 2, 3, 4, 5)
  724.         power = int(power)
  725.         toughness = int(toughness)
  726.         condition, events = parse_tracking_condition(other, what, who, card)
  727.         effect = "card.augment_power_toughness(%i, %i)"%(power, toughness)
  728.     match = re.match(r"(Other )?(.+?)( you control)? have (.+?)\.$", txt)
  729.     if match:
  730.         other, what, who, abilities = match.group(1, 2, 3, 4)
  731.         if " and " in abilities:
  732.             if ", " in abilities:
  733.                 lst = abilities.split(", ")
  734.                 lst[-1] = lst[-1].replace("and ", "")
  735.             else:
  736.                 lst = abilities.split(" and ")
  737.             abilities = []
  738.             for item in lst: abilities.append("card.abilities.add(%s())"%(item.replace(" ", "_").lower()))
  739.             abilities = ", ".join(abilities)
  740.         else:
  741.             abilities = "card.abilities.add(%s())"%(abilities.replace(" ", "_").lower())
  742.         condition, events = parse_tracking_condition(other, what, who, card)
  743.         effect = abilities
  744.     match = re.match(r"(Other )?(.+?)( you control)? get ([+-]\d+)/([+-]\d+) and have (.+?)\.$", txt)
  745.     if match:
  746.         other, what, who, power, toughness, abilities = match.group(1, 2, 3, 4, 5, 6)
  747.         power = int(power)
  748.         toughness = int(toughness)
  749.         if " and " in abilities:
  750.             if ", " in abilities:
  751.                 lst = abilities.split(", ")
  752.                 lst[-1] = lst[-1].replace("and ", "")
  753.             else:
  754.                 lst = abilities.split(" and ")
  755.             abilities = []
  756.             for item in lst: abilities.append("card.abilities.add(%s())"%(item.replace(" ", "_").lower()))
  757.             abilities = ", ".join(abilities)
  758.         else:
  759.             abilities = "card.abilities.add(%s())"%(abilities.replace(" ", "_").lower())
  760.         condition, events = parse_tracking_condition(other, what, who, card)
  761.         effect = "card.augment_power_toughness(%i, %i), %s"%(power, toughness, abilities)
  762.     if effect:
  763.         text =      "@static_tracking%s(txt=text[%i], events=%s)\n"%(("_conditional" if conditional else ""), i, events)
  764.         text +=     "def ability():\n"
  765.         text +=     "    def condition(source, card):\n"
  766.         text +=     "        %s\n"%condition
  767.         if conditional:
  768.             text += "    def conditional(source):\n"
  769.             text += "        %s\n"%conditional
  770.         text +=     "    def effects(source, card):\n"
  771.         if extra:
  772.             text += "        %s\n"%extra
  773.         text +=     "        yield %s\n"%effect
  774.         text +=     "    return condition, %seffects\n"%("conditional, " if conditional else "")
  775.         text +=     "abilities.add(ability)\n"
  776.         return text
  777.     else:
  778.         return None
  779.  
  780. def parse_limit(txt, card):
  781.     if txt == "Activate this ability only during your upkeep.":
  782.         return "UpkeepLimit()"
  783.     elif txt == "Activate this ability only during your turn.":
  784.         return "TurnLimit()"
  785.     elif txt == "Activate this ability only any time you could cast a sorcery.":
  786.         return "sorcery_limit"
  787.     elif txt == "Activate this ability only once each turn.":
  788.         return "CountLimit(1)"
  789.     elif txt == "Activate this ability only if you have no cards in hand.":
  790.         return "ConditionalLimit(lambda source: len(source.controller.hand) == 0)"
  791.     match = re.match(r"^Activate this ability no more than (twice|(three|four|five|six|seven|eight|nine|ten) times) each turn\.$", txt)
  792.     if match:
  793.         num = match.group(1)
  794.         num = ["twice", "three times", "four times", "five times", "six times", "seven times", "eight times", "nine times", "ten times"].index(num) + 2
  795.         return "CountLimit(%i)"%num
  796.     match = re.match(r"^Activate this ability only if you control (one|two|three|four|five|six|seven|eight|nine|ten) or (more|fewer) (.+)\.$", txt)
  797.     if match:
  798.         num, dir, type = match.group(1, 2, 3)
  799.         num = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 1
  800.         type = parse_target(parse_plural(type, card), card)
  801.         if dir == "more":
  802.             dir = ">="
  803.         else:
  804.             dir = "<="
  805.         return "ConditionalLimit(lambda source: len(source.controller.battlefield.get(%s)) %s %i)"%(type, dir, num)
  806.     match = re.match(r"^Activate this ability only if (there are|you have) (one|two|three|four|five|six|seven|eight|nine|ten) or (more|fewer) cards in your (hand|graveyard)\.$", txt)
  807.     if match:
  808.         num, dir, zone = match.group(2, 3, 4)
  809.         num = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 1
  810.         if dir == "more":
  811.             dir = ">="
  812.         else:
  813.             dir = "<="
  814.         return "ConditionalLimit(lambda source: len(source.controller.%s) %s %i)"%(zone, dir, num)
  815.     match = re.match(r"^Activate this ability only if you have exactly (one|two|three|four|five|six|seven|eight|nine|ten) cards in your hand\.$", txt)
  816.     if match:
  817.         num = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(match.group(1)) + 1
  818.         return "ConditionalLimit(lambda source: len(source.controller.hand) == %i)"%num
  819.     match = re.match(r"^Activate this ability only if ~'s (power|toughness) is (\d+) or (greater|less)\.$", txt)
  820.     if match:
  821.         stat, num, dir = match.group(1, 2, 3)
  822.         num = int(num)
  823.         if dir == "greater": dir = ">="
  824.         else: dir = "<="
  825.         return "ConditionalLimit(lambda source: source.%s %s %i)"%(stat, dir, num)
  826.     match = re.match(r"^Activate this ability only if you have (\d+) or (more|less) life\.$", txt)
  827.     if match:
  828.         num, dir = match.group(1, 2)
  829.         num = int(num)
  830.         if dir == "more": dir = ">="
  831.         else: dir = "<="
  832.         return "ConditionalLimit(lambda source: source.controller.life %s %i)"%(dir, num)
  833.     match = re.match(r"^Activate this ability only if a((ny)? player|ny? opponent) has (\d+) or (more|less) life\.$", txt)
  834.     if match:
  835.         type, num, dir = match.group(1, 3, 4)
  836.         num = int(num)
  837.         if dir == "more": dir = ">="
  838.         else: dir = "<="
  839.         if type.endswith("player"): type = "Keeper.players"
  840.         else: type = "source.controller.opponents"
  841.         return "ConditionalLimit(lambda source: any((True for player in %s if player.life %s %i)))"%(type, dir, num)
  842.     return "# XXX"
  843.  
  844. def parse_target(txt, card):
  845.     return {
  846.             "card": "isCard",
  847.             "creature card": "isCreatureCard",
  848.             "artifact card": "isArtifactCard",
  849.             "enchantment card": "isEnchantmentCard",
  850.             "land card": "isLandCard",
  851.             "planeswalker card": "isPlaneswalkerCard",
  852.             "instant card": "isInstantCard",
  853.             "sorcery card": "isSorceryCard",
  854.             "creature": "isCreature",
  855.             "creature you control": "isCreature.with_condition(lambda c: c.controller == controller)",
  856.             "artifact": "isArtifact",
  857.             "artifact you control": "isArtifact.with_condition(lambda a: a.controller == controller)",
  858.             "artifact creature": "isArtifactCreature",
  859.             "noncreature artifact": "isNonCreatureArtifact",
  860.             "player": "isPlayer",
  861.             "opponent": "OpponentMatch(controller)",
  862.             "creature or player": "isCreatureOrPlayer",
  863.             "land": "isLand",
  864.             "land you control": "isLand.with_condition(lambda l: l.controller == controller)",
  865.             "permanent": "isPermanent",
  866.             "permanent you control": "isPermanent.with_condition(lambda p: p.controller == controller)",
  867.             "enchantment": "isEnchantment",
  868.             "enchantment you control": "isEnchantment.with_condition(lambda e: e.controller == controller)",
  869.             "noncreature permanent": "isPermanent.with_condition(lambda p: not p.types == Creature)",
  870.             "nonland permanent": "isNonLand",
  871.             "blocking creature": "isCreature.with_condition(lambda c: c.blocking)",
  872.             "attacking creature": "isCreature.with_condition(lambda c: c.attacking)",
  873.             "attacking or blocking creature": "isCreature.with_condition(lambda c: c.attacking or c.blocking)",
  874.             "creature blocked by ~": "isCreature.with_condition(lambda c: c.attacking and c.blocked and source in c.blockers)",
  875.             "creature blocking or blocked by ~": "isCreature.with_condition(lambda c: (c.blocking and source in c.blockers) or (source.blocking and c in source.blockers))",
  876.             "tapped creature": "isCreature.with_condition(lambda c: c.tapped)",
  877.             "untapped creature": "isCreature.with_condition(lambda c: not c.tapped)",
  878.             "white creature": "isCreature.with_condition(lambda c: c.color == White)",
  879.             "blue creature": "isCreature.with_condition(lambda c: c.color == Blue)",
  880.             "black creature": "isCreature.with_condition(lambda c: c.color == Black)",
  881.             "red creature": "isCreature.with_condition(lambda c: c.color == Red)",
  882.             "green creature": "isCreature.with_condition(lambda c: c.color == Green)",
  883.             "white permanent": "isPermanent.with_condition(lambda p: p.color == White)",
  884.             "blue permanent": "isPermanent.with_condition(lambda p: p.color == Blue)",
  885.             "black permanent": "isPermanent.with_condition(lambda p: p.color == Black)",
  886.             "red permanent": "isPermanent.with_condition(lambda p: p.color == Red)",
  887.             "green permanent": "isPermanent.with_condition(lambda p: p.color == Green)",
  888.             "nonwhite creature": "isCreature.with_condition(lambda c: not c.color == White)",
  889.             "nonblue creature": "isCreature.with_condition(lambda c: not c.color == Blue)",
  890.             "nonblack creature": "isCreature.with_condition(lambda c: not c.color == Black)",
  891.             "nonred creature": "isCreature.with_condition(lambda c: not c.color == Red)",
  892.             "nongreen creature": "isCreature.with_condition(lambda c: not c.color == Green)",
  893.             "nonwhite permanent": "isPermanent.with_condition(lambda p: not p.color == White)",
  894.             "nonblue permanent": "isPermanent.with_condition(lambda p: not p.color == Blue)",
  895.             "nonblack permanent": "isPermanent.with_condition(lambda p: not p.color == Black)",
  896.             "nonred permanent": "isPermanent.with_condition(lambda p: not p.color == Red)",
  897.             "nongreen permanent": "isPermanent.with_condition(lambda p: not p.color == Green)",
  898.            }.get(txt, "# XXX")
  899.  
  900. def parse_plural(txt, card):
  901.     plurals = {
  902.                "artifacts": "artifact",
  903.                "creatures": "creature",
  904.                "enchantments": "enchantment",
  905.                "instants": "instant",
  906.                "lands": "land",
  907.                "planeswalkers": "planeswalker",
  908.                "sorceries": "sorcery",
  909.                "tribals": "tribal",
  910.                "cards": "card",
  911.                "permanents": "permanent",
  912.                "advisors": "advisor",
  913.                "allies": "ally",
  914.                "angels": "angel",
  915.                "anteaters": "anteater",
  916.                #"antelope": "antelope",
  917.                "apes": "ape",
  918.                "archers": "archer",
  919.                "archons": "archon",
  920.                "artificers": "artificer",
  921.                "assassins": "assassin",
  922.                "assembly-workers": "assembly-worker",
  923.                "atogs": "atog",
  924.                "aurochs": "auroch",
  925.                "avatars": "avatar",
  926.                "badgers": "badger",
  927.                "barbarians": "barbarian",
  928.                "basilisks": "basilisk",
  929.                "bats": "bat",
  930.                "bears": "bear",
  931.                "beasts": "beast",
  932.                "beebles": "beeble",
  933.                "berserkers": "berserker",
  934.                "birds": "bird",
  935.                "blinkmoths": "blinkmoth",
  936.                "boars": "boar",
  937.                "bringers": "bringer",
  938.                "brushwaggs": "brushwagg",
  939.                "camarids": "camarid",
  940.                "camels": "camel",
  941.                #"caribou": "caribou",
  942.                "carriers": "carrier",
  943.                "cats": "cat",
  944.                "centaurs": "centaur",
  945.                "cephalids": "cephalid",
  946.                "chimeras": "chimera",
  947.                "citizens": "citizen",
  948.                "clerics": "cleric",
  949.                "cockatrices": "cockatrice",
  950.                "constructs": "construct",
  951.                "cowards": "coward",
  952.                "crabs": "crab",
  953.                "crocodiles": "crocodile",
  954.                #"cyclops": "cyclops",
  955.                #"dauthi": "dauthi",
  956.                "demons": "demon",
  957.                "deserters": "deserter",
  958.                "devils": "devil",
  959.                "djinns": "djinn",
  960.                "dragons": "dragon",
  961.                "drakes": "drake",
  962.                "dreadnoughts": "dreadnought",
  963.                "drones": "drone",
  964.                "druids": "druid",
  965.                "dyrads": "dryad",
  966.                "dwarves": "dwarf",
  967.                "efreets": "efreet",
  968.                "eggs": "egg",
  969.                "elders": "elder",
  970.                "elementals": "elemental",
  971.                "elephants": "elephant",
  972.                "elves": "elf",
  973.                #"elk": "elk",
  974.                "eyes": "eye",
  975.                "faeries": "faerie",
  976.                "ferrets": "ferret",
  977.                "fishes": "fish",
  978.                "flagbearers": "flagbearer",
  979.                "foxes": "fox",
  980.                "frogs": "frog",
  981.                "fungi": "fungus",
  982.                "gargoyles": "gargoyle",
  983.                "germs": "germ",
  984.                "giants": "giant",
  985.                "gnomes": "gnome",
  986.                "goats": "goat",
  987.                "goblins": "goblin",
  988.                "golems": "golem",
  989.                "gorgons": "gorgon",
  990.                "graveborns": "graveborn",
  991.                "gremlins": "gremlin",
  992.                "griffins": "griffin",
  993.                "hags": "hag",
  994.                "harpies": "harpy",
  995.                "hellions": "hellion",
  996.                "hippos": "hippo",
  997.                "homarids": "homarid",
  998.                "homunculi": "homunculus",
  999.                "horrors": "horror",
  1000.                "horses": "horse",
  1001.                "hounds": "hound",
  1002.                "humans": "human",
  1003.                "hydras": "hydra",
  1004.                "hyenas": "hyena",
  1005.                "illusions": "illusion",
  1006.                "imps": "imp",
  1007.                "incarnations": "incarnation",
  1008.                "insects": "insect",
  1009.                "jellyfishes": "jellyfish",
  1010.                "juggernauts": "juggernaut",
  1011.                "kavus": "kavu",
  1012.                "kirins": "kirin",
  1013.                "kithkins": "kithkin",
  1014.                "knights": "knight",
  1015.                "kobolds": "kobold",
  1016.                "kors": "kor",
  1017.                "krakens": "kraken",
  1018.                "lammasi": "lammasu",
  1019.                "leeches": "leech",
  1020.                "leviathans": "leviathan",
  1021.                "lhurgoyfs": "lhurgoyf",
  1022.                "licids": "licid",
  1023.                "lizards": "lizard",
  1024.                "manticores": "manticore",
  1025.                "masticores": "masticore",
  1026.                "mercenaries": "mercenary",
  1027.                #"merfolk": "merfolk",
  1028.                "metathrans": "metathran",
  1029.                "minions": "minion",
  1030.                "minotaurs": "minotaur",
  1031.                "mongers": "monger",
  1032.                "mongeese": "mongoose",
  1033.                "monks": "monk",
  1034.                #"moonfolk": "moonfolk",
  1035.                "mutants": "mutant",
  1036.                #"myr": "myr",
  1037.                "mystics": "mystic",
  1038.                "nautili": "nautilus",
  1039.                #"nephilim": "nephilim",
  1040.                "nightmares": "nightmare",
  1041.                "nightstalkers": "nightstalker",
  1042.                "ninjas": "ninja",
  1043.                "noggles": "noggle",
  1044.                "nomads": "nomad",
  1045.                "octopi": "octopus",
  1046.                "ogres": "ogre",
  1047.                "oozes": "ooze",
  1048.                "orbs": "orb",
  1049.                "orcs": "orc",
  1050.                "orggs": "orgg",
  1051.                "ouphes": "ouphe",
  1052.                "oxen": "ox",
  1053.                "oysters": "oyster",
  1054.                "pegasi": "pegasus",
  1055.                "pentavites": "pentavite",
  1056.                "pests": "pest",
  1057.                "phelddagrifs": "phelddagrif",
  1058.                #"phoenix": "phoenix",
  1059.                "pinchers": "pincher",
  1060.                "pirates": "pirate",
  1061.                "plants": "plant",
  1062.                "prisms": "prism",
  1063.                "rabbits": "rabbit",
  1064.                "rats": "rat",
  1065.                "rebels": "rebel",
  1066.                "reflections": "reflection",
  1067.                "rhinos": "rhino",
  1068.                "riggers": "rigger",
  1069.                "rogues": "rogue",
  1070.                "salamanders": "salamander",
  1071.                #"samurai": "samurai",
  1072.                #"sand": "sand",
  1073.                "saprolings": "saproling",
  1074.                "satyrs": "satyr",
  1075.                "scarecrows": "scarecrow",
  1076.                "scorpions": "scorpion",
  1077.                "scouts": "scout",
  1078.                "serfs": "serf",
  1079.                "serpents": "serpent",
  1080.                "shades": "shade",
  1081.                "shamans": "shaman",
  1082.                "shapeshifters": "shapeshifter",
  1083.                #"sheep": "sheep",
  1084.                "skeletons": "skeleton",
  1085.                "sliths": "slith",
  1086.                "slivers": "sliver",
  1087.                "slugs": "slug",
  1088.                "snakes": "snake",
  1089.                "soldiers": "soldier",
  1090.                #"soltari": "soltari",
  1091.                #"spawn": "spawn",
  1092.                "specters": "specter",
  1093.                "spellshapers": "spellshaper",
  1094.                "sphinxes": "sphinx",
  1095.                "spiders": "spider",
  1096.                "spikes": "spike",
  1097.                "spirits": "spirit",
  1098.                "splinters": "splinter",
  1099.                "sponges": "sponge",
  1100.                "squids": "squid",
  1101.                "squirrels": "squirrel",
  1102.                "starfishes": "starfish",
  1103.                "survivors": "survivor",
  1104.                "tetravites": "tetravite",
  1105.                "thalakoses": "thalakos",
  1106.                "thopters": "thopter",
  1107.                "thrulls": "thrull",
  1108.                #"treefolk": "treefolk",
  1109.                "triskelavites": "triskelavite",
  1110.                "trolls": "troll",
  1111.                "turtles": "turtle",
  1112.                "unicorns": "unicorn",
  1113.                "vampires": "vampire",
  1114.                #"vedalken": "vedalken",
  1115.                #"viashino": "viashino",
  1116.                "volvers": "volver",
  1117.                "walls": "wall",
  1118.                "warriors": "warrior",
  1119.                "weirds": "weird",
  1120.                "whales": "whale",
  1121.                "wizards": "wizard",
  1122.                "wolves": "wolf",
  1123.                "wolverines": "wolverine",
  1124.                "wombats": "wombat",
  1125.                "worms": "worm",
  1126.                "wraiths": "wraith",
  1127.                "wurms": "wurm",
  1128.                "yetis": "yeti",
  1129.                "zombies": "zombie",
  1130.                #"zubera": "zubera",
  1131.                "contraptions": "contraption",
  1132.                #"equipment": "equipment",
  1133.                "fortifications": "fortification",
  1134.                "auras": "aura",
  1135.                "shrines": "shrine",
  1136.                #"arcanes": "arcane",
  1137.                "traps": "trap",
  1138.                "deserts": "desert",
  1139.                "forests": "forest",
  1140.                "islands": "island",
  1141.                "lairs": "lair",
  1142.                "locuses": "locus",
  1143.                "mines": "mine",
  1144.                "mountains": "mountain",
  1145.                #"plains": "plains",
  1146.                "power-plants": "power-plant",
  1147.                "swamps": "swamp",
  1148.                "towers": "tower",
  1149.                #"urza's": "urza's",
  1150.               }
  1151.     for word in plurals:
  1152.         if word in txt: txt = txt.replace(word, plurals[word])
  1153.     return txt
  1154.  
  1155. def parse_condition(txt, card):
  1156.     global it
  1157.     if txt == "As long as you have no cards in hand, ":
  1158.         return "return len(source.controller.hand) == 0"
  1159.     match = re.match(r"^As long as you control (an?|two|three|four|five|six|seven|eight|nine|ten)( or (more|fewer))? (.+), ", txt)
  1160.     if match:
  1161.         num, dir, type = match.group(1, 3, 4)
  1162.         type = parse_target(parse_plural(type, card), card)
  1163.         if num == "an": num = "a"
  1164.         if not dir: dir = "more"
  1165.         num = ["a", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 1
  1166.         if dir == "more":
  1167.             dir = ">="
  1168.         else:
  1169.             dir = "<="
  1170.         return "return len(source.controller.battlefield.get(%s)) %s %i"%(type, dir, num)
  1171.     match = re.match(r"^As long as (one|two|three|four|five|six|seven|eight|nine|ten) or (more|fewer) card(s are| is) in your graveyard, ", txt)
  1172.     if match:
  1173.         num, dir = match.group(1, 2)
  1174.         num = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 1
  1175.         if dir == "more":
  1176.             dir = ">="
  1177.         else:
  1178.             dir = "<="
  1179.         return "return len(source.controller.graveyard) %s %i"%(dir, num)
  1180.     match = re.match(r"^As long as you have (one|two|three|four|five|six|seven|eight|nine|ten) or (more|fewer) card(s) in hand, ", txt)
  1181.     if match:
  1182.         num, dir = match.group(1, 2)
  1183.         num = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 1
  1184.         if dir == "more":
  1185.             dir = ">="
  1186.         else:
  1187.             dir = "<="
  1188.         return "return len(source.controller.hand) %s %i"%(dir, num)
  1189.     match = re.match(r"^As long as you have (\d+) or (more|less) life, ", txt)
  1190.     if match:
  1191.         num, dir = match.group(1, 2)
  1192.         num = int(num)
  1193.         if dir == "more": dir = ">="
  1194.         else: dir = "<="
  1195.         return "return source.controller.life %s %i"%(dir, num)
  1196.     match = re.match(r"^As long as a((ny?) player|ny? opponent) has (\d+) or (more|less) life, ", txt)
  1197.     if match:
  1198.         type, num, dir = match.group(1, 3, 4)
  1199.         num = int(num)
  1200.         if dir == "more": dir = ">="
  1201.         else: dir = "<="
  1202.         if type.endswith("player"): type = "Keeper.players"
  1203.         else: type = "source.controller.opponents"
  1204.         return """for player in %s:
  1205.            if player.life %s %i: return True
  1206.        else: return False"""%(type, dir, num)
  1207.     match = re.match(r"^As long as (~|(enchanted|equipped|fortified) (permanent|artifact|creature|enchantment|land|planeswalker)) is (white|blue|black|red|green), ", txt)
  1208.     if match:
  1209.         who, color = match.group(1, 4)
  1210.         it = who
  1211.         if who == "~":
  1212.             return "source.color == %s"%(color.capitalize())
  1213.         else:
  1214.             return "return source.attached_to and source.attached_to.color == %s"%(color.capitalize())
  1215.     return "return True"
  1216.  
  1217. def parse_effect(txt, card, triggered=False):
  1218.     txt = re.sub(r", then (?!shuffle)(\w)", lambda match: ". " + match.group(1).upper(), txt)
  1219.     txt = re.sub(r", then shuffle your library.", r". Then shuffle your library.", txt)
  1220.     if ". " in txt:
  1221.         txt = txt.split(". ")
  1222.         txt = [(item + "." if not i == len(txt) - 1 else item) for i, item in enumerate(txt)]
  1223.         while "Then shuffle your library." in txt: txt.remove("Then shuffle your library.")
  1224.         i = 0
  1225.         while i < len(txt):
  1226.             element = txt[i]
  1227.             if element in ("It can't be regenerated.", "They can't be regenerated."):
  1228.                 txt[i-1] += " "+element
  1229.                 del txt[i]
  1230.                 continue
  1231.             i += 1
  1232.         if len(txt) == 1:
  1233.             txt = txt[0]
  1234.         else:
  1235.             results = [parse_effect(item, card, triggered) for item in txt]
  1236.             results = [(item if item else ((None, None, None) if triggered else (None, None))) for item in results]
  1237.             if triggered:
  1238.                 vars, targets, effects = zip(*results)
  1239.                 vars = [item for item in vars if item]
  1240.             else:
  1241.                 targets, effects = zip(*results)
  1242.             targets = [item for item in targets if item]
  1243.             effects = "\n        yield\n        ".join([(item if item else "") for item in effects])
  1244.             if len(targets) > 1:
  1245.                 temp = effects.split("target")
  1246.                 effects = ""
  1247.                 index = 0
  1248.                 for section in temp[:-1]:
  1249.                     effects += section + "targets[%i]"%index
  1250.                     index += 1
  1251.                 effects += temp[-1]
  1252.             if triggered:
  1253.                 return vars, targets, effects
  1254.             else:
  1255.                 return targets, effects
  1256.     vars = None
  1257.     target = None
  1258.     effect = None
  1259.     match = re.match(r"^~ deals (\d+) damage to you\.$", txt)
  1260.     if match:
  1261.         effect = "source.deal_damage(controller, %i)"%int(match.group(1))
  1262.     match = re.match(r"^~ deals (\d+) damage to target (.+)\.$", txt)
  1263.     if match:
  1264.         target = parse_target(match.group(2), card)
  1265.         effect = "source.deal_damage(target, %i)"%int(match.group(1))
  1266.     match = re.match(r"^~ deals damage equal to (its (power|toughness)|the number of (.+?) (on the battlefield|you control)) to (you|target (.+))\.$", txt)
  1267.     if match:
  1268.         val, who = match.group(1, 5)
  1269.         if who == "you":
  1270.             who = "controller"
  1271.         else:
  1272.             target = parse_target(match.group(6), card)
  1273.             who = "target"
  1274.         if val.startswith("its "):
  1275.             val = match.group(2)
  1276.             if val == "power": val = "source.power"
  1277.             elif val == "toughness": val = "source.toughness"
  1278.         elif val.startswith("the number of "):
  1279.             val = parse_target(parse_plural(match.group(3), card), card)
  1280.             scope = match.group(4)
  1281.             if scope == "on the battlefield": scope = ", all=True"
  1282.             else: scope = ""
  1283.             val = "len(controller.battlefield.get(%s%s))"%(val, scope)
  1284.         effect = "source.deal_damage(%s, %s)"%(who, val)
  1285.     match = re.match(r"^[Pp]revent the next (\d+) damage that would be dealt to (~|target (.+?)|equipped creature|enchanted creature|enchanted planeswalker|enchanted player)( this turn)?\.$", txt)
  1286.     if match:
  1287.         num, who, temp = match.group(1, 2, 4)
  1288.         num = int(num)
  1289.         if who == "~":
  1290.             who = "source"
  1291.         elif who.startswith("target "):
  1292.             who = "target"
  1293.             target = parse_target(match.group(3), card)
  1294.         else:
  1295.             who = "source.attached_to"
  1296.         effect = "prevent_damage(%s, %i)"%(who, num)
  1297.         if temp:
  1298.             effect = "until_end_of_turn(%s)"%effect
  1299.         if who == "source.attached_to":
  1300.             effect = "if %s: %s"%(who, effect)
  1301.     match = re.match(r"^([Yy]ou )?[Dd]raw (a|two|three|four|five|six|seven|eight|nine|ten) card(s)?\.$", txt)
  1302.     if match:
  1303.         num = match.group(2)
  1304.         if num == "a":
  1305.             effect = "controller.draw()"
  1306.         else:
  1307.             num = ["two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 2
  1308.             effect = "controller.draw(%i)"%num
  1309.     match = re.match(r"^[Yy]ou may draw (a|two|three|four|five|six|seven|eight|nine|ten) card(s)?\.$", txt)
  1310.     if match:
  1311.         num = match.group(1)
  1312.         if num == "a":
  1313.             effect = "controller.draw()"
  1314.         else:
  1315.             num = ["two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 2
  1316.             effect = "controller.draw(%i)"%num
  1317.         effect = "if controller.you_may('draw %s card%s'): %s"%(match.group(1), match.group(2) if match.group(2) else "", effect)
  1318.     match = re.match(r"^[Tt]arget (player|opponent) draws? (a|two|three|four|five|six|seven|eight|nine|ten) cards?\.$", txt)
  1319.     if match:
  1320.         who, num = match.group(1, 2)
  1321.         if num == "a":
  1322.             effect = "target.draw()"
  1323.         else:
  1324.             num = ["two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 2
  1325.             effect = "target.draw(%i)"%num
  1326.         if who == "opponent":
  1327.             target = "OpponentMatch(controller)"
  1328.         else: target = "isPlayer"
  1329.     match = re.match(r"^([Tt]arget (player|opponent) puts|([Yy]ou )?[Pp]ut) the top( (two|three|four|five|six|seven|eight|nine|ten|eleven|twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|nineteen|twenty))? card(s)? of his or her library into his or her graveyard?\.$", txt)
  1330.     if match:
  1331.         who, which, num = match.group(1, 2, 5)
  1332.         if num: num = ["two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve", "thirteen", "fourteen", "fifteen", "sixteen", "seventeen", "eighteen", "nineteen", "twenty"].index(num) + 2
  1333.         else: num = 1
  1334.         if who.startswith(("T", "t")):
  1335.             who = "target"
  1336.             target = parse_target(which, card)
  1337.         else: who = "controller"
  1338.         if num == 1:
  1339.             effect = "card = %s.library.top()"%who
  1340.             effect += "\n        if card:\n            "
  1341.         else:
  1342.             effect = "for card in %s.library.top(%d):\n            "%(who, num)
  1343.         effect += "card.move_to('graveyard')"
  1344.     match = re.match(r"^([Yy]ou |[Tt]arget (player|opponent) )?([Gg]ain|[Ll]ose)(s)? (\d+) life\.$", txt)
  1345.     if match:
  1346.         who, dir, num = match.group(1, 3, 5)
  1347.         num = int(num)
  1348.         if dir in ("Gain", "gain"):
  1349.             dir = "+"
  1350.         else:
  1351.             dir = "-"
  1352.         if not who or who in ("You ", "you "):
  1353.             effect = "controller"
  1354.         else:
  1355.             target = parse_target(match.group(2), card)
  1356.             effect = "target"
  1357.         effect += ".life %s= %i"%(dir, num)
  1358.     match = re.match(r"^[Yy]ou may (gain|lose) (\d+) life\.$", txt)
  1359.     if match:
  1360.         dir, num = match.group(1, 2)
  1361.         num = int(num)
  1362.         if dir == "gain": dir = "+"
  1363.         else: dir = "-"
  1364.         effect = 'if controller.you_may("%s %i life"): controller.life %s= %i'%(match.group(1), num, dir, num)
  1365.     match = re.match(r"^[Ee]ach (player|opponent) (gains|loses) (\d+) life\.$", txt)
  1366.     if match:
  1367.         who, dir, num = match.group(1, 2, 3)
  1368.         num = int(num)
  1369.         if dir == "gain":
  1370.             dir = "+"
  1371.         else:
  1372.             dir = "-"
  1373.         if who == "player":
  1374.             effect = "for player in keeper.players"
  1375.         elif who == "opponent":
  1376.             effect = "for player in controller.opponents"
  1377.         effect += ": player.life %s= %i"%(dir, num)
  1378.     match = re.match(r"^([Yy]our|[Tt]arget (player|opponent)'s) life total becomes (\d+)\.$", txt)
  1379.     if match:
  1380.         who, num = match.group(1, 3)
  1381.         num = int(num)
  1382.         if who in ("Your", "your"):
  1383.             effect = "controller"
  1384.         else:
  1385.             target = parse_target(match.group(2), card)
  1386.             effect = "target"
  1387.         effect += ".life = %i"%num
  1388.     match = re.match(r"^Look at the top card of (your|target (player|opponent)'s) library, then you may put that card into (your|that player's) graveyard\.$", txt)
  1389.     if match:
  1390.         who = match.group(1)
  1391.         if who == "target player's":
  1392.             target = "isPlayer"
  1393.             who = "target"
  1394.         elif who == "target opponent's":
  1395.             target = "OpponentMatch(controller)"
  1396.             who = "target"
  1397.         else:
  1398.             who = "controller"
  1399.         effect =              "topcard = %s.library.top()\n"%who
  1400.         effect +=     "        if topcard:\n"
  1401.         effect +=     "            controller.look_at((topcard,))\n"
  1402.         if who == "controller":
  1403.             effect += "            if controller.you_may('put %s into your graveyard'%topcard.name):\n"
  1404.         else:
  1405.             effect += "            if controller.you_may('put %s into %s's graveyard'%(topcard.name, topcard.owner)):\n"
  1406.         effect += "                topcard.move_to('graveyard')"
  1407.     match = re.match(r"^(~|[Tt]arget (.+?)|([Ee]quipped|[Ee]nchanted|[Ff]ortified) (permanent|artifact|creature|land|enchantment|planeswalker)) gains (.+?) until end of turn\.$", txt)
  1408.     if match:
  1409.         effect = ""
  1410.         if match.group(1) == "~":
  1411.             who = "source"
  1412.         elif match.group(1).startswith(("Target ", "target ")):
  1413.             target = parse_target(match.group(2), card)
  1414.             who = "target"
  1415.         else: who = "source.attached_to"
  1416.         what = match.group(5)
  1417.         if what.startswith("your choice of "):
  1418.             what = what[15:]
  1419.             if ", " in what:
  1420.                 lst = what.split(", ")
  1421.                 lst[-1] = lst[-1].replace("or ", "")
  1422.             else:
  1423.                 lst = what.split(" or ")
  1424.             effect = "choice = controller.make_selection(%r, prompt='Choose an ability')\n        "%lst
  1425.             what = "%s.abilities.add(eval(choice.replace(' ', '_').lower())())"%who
  1426.         elif " and " in what:
  1427.             if ", " in what:
  1428.                 lst = what.split(", ")
  1429.                 lst[-1] = lst[-1].replace("and ", "")
  1430.             else:
  1431.                 lst = what.split(" and ")
  1432.             what = []
  1433.             for item in lst: what.append("%s.abilities.add(%s())"%(who, item.replace(" ", "_").lower()))
  1434.             what = ", ".join(what)
  1435.         else:
  1436.             what = "%s.abilities.add(%s())"%(who, what.replace(" ", "_").lower())
  1437.         effect += "until_end_of_turn(%s)"%what
  1438.         if who == "source.attached_to":
  1439.             effect = "if %s: %s"%(who, effect)
  1440.     match = re.match(r"^(~|[Tt]arget (.+?)|([Ee]quipped|[Ee]nchanted|[Ff]ortified) (permanent|artifact|creature|land|enchantment|planeswalker)) loses (.+?) until end of turn\.$", txt)
  1441.     if match:
  1442.         who, what = match.group(1, 5)
  1443.         if who == "~":
  1444.             who = "source"
  1445.         elif who.startswith(("Target ", "target ")):
  1446.             target = parse_target(match.group(2), card)
  1447.             who = "target"
  1448.         else: who = "source.attached_to"
  1449.         effect = "until_end_of_turn(%s.abilites.remove(%r))"%(who, what.lower())
  1450.         if who == "source.attached_to":
  1451.             effect = "if %s: %s"%(who, effect)
  1452.     match = re.match(r"^(~|[Tt]arget (.+?)|([Ee]quipped|[Ee]nchanted|[Ff]ortified) (permanent|artifact|creature|land|enchantment|planeswalker)) gains or loses (.+?) until end of turn\.$", txt)
  1453.     if match:
  1454.         who, what = match.group(1, 5)
  1455.         if who == "~":
  1456.             who = "source"
  1457.         elif who.startswith(("Target ", "target ")):
  1458.             target = parse_target(match.group(2), card)
  1459.             who = "target"
  1460.         else: who = "source.attached_to"
  1461.         ability_name = what.lower()
  1462.         ability = ability_name.replace(" ", "_")
  1463.         effect = """if %r not in %s.abilities:
  1464.            if controller.you_may("have %%s gain %s"%%(%s.name)):
  1465.                until_end_of_turn(%s.abilities.add(%s()))
  1466.            else:
  1467.                until_end_of_turn(%s.abilities.remove(%r))
  1468.        else:
  1469.            if controller.you_may("have %%s lose %s"%%(%s.name)):
  1470.                until_end_of_turn(%s.abilities.remove(%r))
  1471.            else:
  1472.                until_end_of_turn(%s.abilities.add(%s()))"""%(ability_name, who, ability_name, who, who, ability, effect, ability_name, ability_name, who, who, ability_name, who, ability)
  1473.         if who == "source.attached_to":
  1474.             new = effect.split("\n")
  1475.             effect = "if %s:\n"%who
  1476.             for line in new:
  1477.                 effect += "    %s\n"%line
  1478.             effect = effect[:-1]
  1479.     match = re.match(r"^~ gets ([+-]\d+)/([+-]\d+) until end of turn\.$", txt)
  1480.     if match:
  1481.         power, toughness = match.group(1, 2)
  1482.         effect = "until_end_of_turn(source.augment_power_toughness(%i, %i))"%(int(power), int(toughness))
  1483.     match = re.match(r"^[Tt]arget (.+) gets ([+-]\d+)/([+-]\d+) until end of turn\.$", txt)
  1484.     if match:
  1485.         target, power, toughness = match.group(1, 2, 3)
  1486.         target = parse_target(target, card)
  1487.         effect = "until_end_of_turn(target.augment_power_toughness(%i, %i))"%(int(power), int(toughness))
  1488.     match = re.match(r"^[Ee](nchanted|quipped) creature gets ([+-]\d+)/([+-]\d+) until end of turn\.$", txt)
  1489.     if match:
  1490.         power, toughness = match.group(2, 3)
  1491.         effect = "if source.attached_to: until_end_of_turn(source.attached_to.augment_power_toughness(%i, %i))"%(int(power), int(toughness))
  1492.     match = re.match(r"^(~|[Tt]arget (.+?)|[Ee]nchanted creature|[Ee]quipped creature) gets ([+-]\d+)/([+-]\d+) and gains (.+?) until end of turn\.$", txt)
  1493.     if match:
  1494.         effect = ""
  1495.         who, power, toughness, what = match.group(1, 3, 4, 5)
  1496.         if who == "~":
  1497.             who = "source"
  1498.         elif who.startswith(("Target ", "target ")):
  1499.             who = "target"
  1500.             target = parse_target(match.group(2), card)
  1501.         else:
  1502.             who = "source.attached_to"
  1503.         if what.startswith("your choice of "):
  1504.             what = what[15:]
  1505.             if ", " in what:
  1506.                 lst = what.split(", ")
  1507.                 lst[-1] = lst[-1].replace("or ", "")
  1508.             else:
  1509.                 lst = what.split(" or ")
  1510.             effect = "choice = controller.make_selection(%r, prompt='Choose an ability')\n        "%lst
  1511.             what = "%s.abilities.add(eval(choice.replace(' ', '_').lower())())"%who
  1512.         elif " and " in what:
  1513.             if ", " in what:
  1514.                 lst = what.split(", ")
  1515.                 lst[-1] = lst[-1].replace("and ", "")
  1516.             else:
  1517.                 lst = what.split(" and ")
  1518.             what = []
  1519.             for item in lst: what.append("%s.abilities.add(%s())"%(who, item.replace(" ", "_").lower()))
  1520.             what = ", ".join(what)
  1521.         else:
  1522.             what = "%s.abilities.add(%s())"%(who, what.replace(" ", "_").lower())
  1523.         effect += "until_end_of_turn(%s.augment_power_toughness(%i, %i), %s)"%(who, int(power), int(toughness), what)
  1524.         if who == "source.attached_to":
  1525.             effect = "if %s: %s"%(who, effect)
  1526.     match = re.match(r"^(~|[Tt]arget (.+)|[Ee]nchanted creature|[Ee]quipped creature) becomes (\d+)/(\d+) until end of turn\.$", txt)
  1527.     if match:
  1528.         who, power, toughness = match.group(1, 3, 4)
  1529.         power, toughness = int(power), int(toughness)
  1530.         if who == "~":
  1531.             effect = "source"
  1532.         elif who.startswith(("Target ", "target ")):
  1533.             target = parse_target(match.group(2), card)
  1534.             effect = "target"
  1535.         else: who = "source.attached_to"
  1536.         effect = "until_end_of_turn(%s.set_power_toughness(%i, %i))"%(effect, power, toughness)
  1537.         if who == "source.attached_to":
  1538.             effect = "if %s: %s"%(who, effect)
  1539.     match = re.match(r"^(~|[Tt]arget (.+)|([Ee]quipped|[Ee]nchanted|[Ff]ortified) (permanent|artifact|creature|land|enchantment|planeswalker)) is (indestructible|unblockable) this turn\.$", txt)
  1540.     if match:
  1541.         who, what = match.group(1, 5)
  1542.         if who == "~":
  1543.             who = "source"
  1544.         elif who.startswith(("Target ", "target ")):
  1545.             target = parse_target(match.group(2), card)
  1546.             who = "target"
  1547.         else: who = "source.attached_to"
  1548.         effect = "until_end_of_turn(%s.%s())"%(who, what)
  1549.         if who == "source.attached_to":
  1550.             effect = "if %s: %s"%(who, effect)
  1551.     match = re.match(r"^[Aa]dd (one|two|three|four|five|six|seven|eight|nine|ten) mana of any (one )?color to your mana pool\.$", txt)
  1552.     if match:
  1553.         num = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(match.group(1)) + 1
  1554.         effect = "controller.add_mana(%r, %r, %r, %r, %r)"%("W"*num, "U"*num, "B"*num, "R"*num, "G"*num)
  1555.     match = re.match(r"^[Aa]dd (two|three|four|five|six|seven|eight|nine|ten) mana in any combination of colors to your mana pool\.$", txt)
  1556.     if match:
  1557.         num = ["two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(match.group(1)) + 2
  1558.         effect = "controller.add_mana(%r)"%("(W/U/B/R/G)"*num)
  1559.     match = re.match(r"^[Aa]dd (([WUBRG1234567890/(){}]+(, )?(( )?or )?)+) to your mana pool\.$", txt)
  1560.     if match:
  1561.         mana = match.group(1)
  1562.         mana = [strip_parens(m.strip(",")) for m in mana.split(" ") if not m == "or"]
  1563.         effect = "controller.add_mana(%s)"%(', '.join([repr(m) for m in mana]))
  1564.     match = re.match(r"^[Pp]ut (an?|two|three|four|five|six|seven|eight|nine|ten) (([+-]\d+)/([+-]\d+)|\w+) counter(s?) on (~|target ([^.]+)|(enchanted|equipped|fortified) (permanent|artifact|creature|land|enchantment|planeswalker))\.$", txt)
  1565.     if match:
  1566.         counter = match.group(2)
  1567.         if counter[0] in "+-":
  1568.             power, toughness = match.group(3, 4)
  1569.             counter = "PowerToughnessCounter(%i, %i)"%(int(power), int(toughness))
  1570.         else:
  1571.             counter = repr(counter)
  1572.         num = match.group(1)
  1573.         if num in ("a", "an"):
  1574.             num = ""
  1575.         else:
  1576.             num = ", number=%i"%(["two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 2)
  1577.         if match.group(6) == "~":
  1578.             effect = "source"
  1579.         elif match.group(6).startswith("target "):
  1580.             target = parse_target(match.group(7), card)
  1581.             effect = "target"
  1582.         else:
  1583.             effect = "source.attached_to"
  1584.         effect += ".add_counters(%s%s)"%(counter, num)
  1585.         if effect.startswith("source.attached_to"):
  1586.             effect = "if source.attached_to: %s"%effect
  1587.     match = re.match(r"^[Pp]ut (an?|two|three|four|five|six|seven|eight|nine|ten)( legendary)? (\d+)/(\d+) (.+?) tokens?( with (.+?))?( named (.+?))? onto the battlefield\.$", txt)
  1588.     if match:
  1589.         num, legendary, power, toughness, characteristics, abilities, name = match.group(1, 2, 3, 4, 5, 7, 9)
  1590.         if num == "an": num = "a"
  1591.         num = ["a", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 1
  1592.         if num > 1:
  1593.             num = ", number=%d"%num
  1594.         else: num = ""
  1595.         power, toughness = int(power), int(toughness)
  1596.         pt = '"P/T": (%d, %d), '%(power, toughness)
  1597.         if legendary: legendary = '"supertypes": Legendary, '
  1598.         else: legendary = ""
  1599.         characteristics = characteristics.replace('colorless ', '').replace(' and ', ' ')
  1600.         colors = []
  1601.         subtypes = []
  1602.         types = []
  1603.         for word in characteristics.split(" "):
  1604.             if word in ("white", "blue", "black", "red", "green"): colors.append(word.capitalize())
  1605.             elif word[0].isupper(): subtypes.append(word) # In most use-cases, subtypes are capitalized and types are not; since we don't have to worry about start-of-sentence capitalization here, we can use this to divide the two without an exhaustive list of either.
  1606.             else: types.append(word.capitalize())
  1607.         if colors:
  1608.             if len(colors) == 1: colors = colors[0]
  1609.             else: colors = "(%s)"%(', '.join(colors))
  1610.             colors = '"color": %s, '%colors
  1611.         else: colors = ""
  1612.         if subtypes:
  1613.             if len(subtypes) == 1: subtypes = subtypes[0]
  1614.             else: subtypes = "(%s)"%(', '.join(subtypes))
  1615.             subtypes = '"subtypes": %s, '%subtypes
  1616.         else: subtypes = ""
  1617.         if types:
  1618.             if len(types) == 1: types = types[0]
  1619.             else: types = "(%s)"%(', '.join(types))
  1620.             types = '"types": %s, '%types
  1621.         else: types = ""
  1622.         if abilities:
  1623.             abilities = abilities.replace(", and ", ", ").replace(" and ", ", ").split(", ")
  1624.             if len(abilities) == 1: abilities = abilities[0]
  1625.             else: abilities = tuple(abilities)
  1626.             abilities = '"abilities": %r, '%(abilities,)
  1627.         else: abilities = ""
  1628.         if name: name = '"name": %r, '%name
  1629.         else: name = ""
  1630.         effect = ("controller.play_tokens({%s%s%s%s%s%s%s}%s)"%(legendary, pt, colors, subtypes, types, abilities, name, num)).replace(", }", "}")
  1631.     match = re.match(r"^[Ee]xile (~|target (.+)|(equipped|enchanted|fortified) (permanent|artifact|creature|land|enchantment|planeswalker))\.$", txt)
  1632.     if match:
  1633.         if match.group(1) == "~":
  1634.             effect = "source"
  1635.         elif match.group(1).startswith("target "):
  1636.             target = parse_target(match.group(2), card)
  1637.             effect = "target"
  1638.         else:
  1639.             effect = "if source.attached_to: source.attached_to"
  1640.         effect += ".move_to('exile')"
  1641.     match = re.match(r"^[Ss]acrifice (~|target (.+)|(equipped|enchanted|fortified) (permanent|artifact|creature|land|enchantment|planeswalker))\.$", txt)
  1642.     if match:
  1643.         if match.group(1) == "~":
  1644.             effect = "source"
  1645.         elif match.group(1).startswith("target "):
  1646.             target = parse_target(match.group(2), card)
  1647.             effect = "target"
  1648.         else: effect = "source.attached_to"
  1649.         effect = "controller.sacrifice(%s)"%effect
  1650.         if effect.endswith("_to)"):
  1651.             effect = "if source.attached_to: %s"%effect
  1652.     match = re.match(r"^[Tt]arget (.+?)'s controller sacrifices it.$", txt)
  1653.     if match:
  1654.         target = parse_target(match.group(1), card)
  1655.         effect = "target.controller.sacrifice(target)"
  1656.     match = re.match(r"^[Rr]eturn (~|an? (.+?)( you control)?|(equipped|enchanted|fortified) (permanent|artifact|creature|land|enchantment|planeswalker)|target (.+?)) to its owner's hand\.$", txt)
  1657.     if match:
  1658.         who = match.group(1)
  1659.         if who == "~":
  1660.             effect = "source.move_to('hand')"
  1661.         elif who.startswith(("a ", "an ")):
  1662.             effect = "for perm in controller.choose_from_zone(zone='battlefield', cardtype=%s, required=True, action=\"return to its owner's hand\"%s):\n            perm.move_to('hand')"%(parse_target(match.group(2), card), "" if match.group(3) else ", all=True")
  1663.         elif who.startswith("target "):
  1664.             target = parse_target(match.group(4), card)
  1665.             effect = "target.move_to('hand')"
  1666.         else:
  1667.             effect = "if source.attached_to: source.attached_to.move_to('hand')"
  1668.     match = re.match(r"^[Dd]estroy (~|target (.+?)|(equipped|enchanted|fortified) (permanent|artifact|creature|land|enchantment|planeswalker))(\. It can't be regenerated)?\.$", txt)
  1669.     if match:
  1670.         if match.group(1) == "~":
  1671.             effect = "source"
  1672.         elif match.group(1).startswith("target "):
  1673.             target = parse_target(match.group(2), card)
  1674.             effect = "target"
  1675.         else: effect = "if source.attached_to: source.attached_to"
  1676.         effect += ".destroy(%s)"%("regenerate=False" if match.group(5) else "")
  1677.     match = re.match(r"^[Dd]estroy all (.+?)( you control)?(\. They can't be regenerated)?\.$", txt)
  1678.     if match:
  1679.         type = parse_target(parse_plural(match.group(1), card), card)
  1680.         effect = "for permanent in controller.battlefield.get(%s%s): permanent.destroy(%s)"%(type, ("" if match.group(2) else ", all=True"), ("regenerate=False" if match.group(3) else ""))
  1681.     match = re.match(r"^([Yy]ou |([Tt]arget|[Ee]ach) (player|opponent) )?[Ss]acrifices? (an?|two|three|four|five|six|seven|eight|nine|ten) (.+?)\.$", txt)
  1682.     if match:
  1683.         who, targeted, ptype, num, type = match.group(1, 2, 3, 4, 5)
  1684.         if not who or who in ("You ", "you "):
  1685.             effect = "controller"
  1686.         elif targeted in ("Target", "target"):
  1687.             effect = "target"
  1688.             target = "isPlayer" if ptype == "player" else "OpponentMatch(controller)"
  1689.         else:
  1690.             effect = "for player in "
  1691.             if ptype == "player":
  1692.                 effect += "Keeper.players"
  1693.             else: effect += "controller.opponents"
  1694.             effect += ":\n            player"
  1695.         if num == "an": num = "a"
  1696.         num = ["a", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 1
  1697.         type = parse_target(parse_plural(type, card), card)
  1698.         effect += ".force_sacrifice(cardtype=%s%s)"%(type, "" if num == 1 else ", number=%i"%num)
  1699.     match = re.match(r"^([Yy]ou |([Tt]arget|[Ee]ach) (player|opponent) )?[Dd]iscards? (a|two|three|four|five|six|seven|eight|nine|ten) cards?( at random)?\.$", txt)
  1700.     if match:
  1701.         who, targeted, ptype, num, random = match.group(1, 2, 3, 4, 5)
  1702.         if not who or who in ("You ", "you "):
  1703.             who = "controller"
  1704.             effect = who
  1705.         elif targeted in ("Target", "target"):
  1706.             who = "target"
  1707.             effect = who
  1708.             target = "isPlayer" if ptype == "player" else "OpponentMatch(controller)"
  1709.         else:
  1710.             who = "player"
  1711.             effect = "for player in "
  1712.             if ptype == "player":
  1713.                 effect += "Keeper.players"
  1714.             else: effect += "controller.opponents"
  1715.             effect += ":\n            player"
  1716.         num = ["a", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 1
  1717.         if random:
  1718.             effect += ".discard_at_random(%s)"%("" if num == 1 else str(num))
  1719.         else:
  1720.             effect += ".force_discard(%s)"%("" if num == 1 else str(num))
  1721.     match = re.match(r"^[Rr]egenerate (~|target (.+)|(equipped|enchanted|fortified) (permanent|artifact|creature|land|enchantment|planeswalker))\.$", txt)
  1722.     if match:
  1723.         if match.group(1) == "~":
  1724.             effect = "source"
  1725.         elif match.group(1).startswith("target "):
  1726.             target = parse_target(match.group(2), card)
  1727.             effect = "target"
  1728.         else: effect = "if source.attached_to: source.attached_to"
  1729.         effect += ".regenerate()"
  1730.     match = re.match(r"^[Yy]ou may put (an|two|three|four|five|six|seven|eight|nine|ten|) (([+-]\d+)/([+-]\d+)|\w+) counter(s?) on (~|target ([^.]+)|(equipped|enchanted|fortified) (permanent|artifact|creature|land|enchantment|planeswalker))\.$", txt)
  1731.     if match:
  1732.         num, counter, who = match.group(1, 2, 6)
  1733.         if counter[0] in "+-":
  1734.             power, toughness = match.group(3, 4)
  1735.             counter = "PowerToughnessCounter(%i, %i)"%(int(power), int(toughness))
  1736.         else:
  1737.             counter = repr(counter)
  1738.         if num in ("a", "an"):
  1739.             num = ""
  1740.         else:
  1741.             num = ", number=%i"%(["two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 2)
  1742.         if who == "~":
  1743.             who = "source"
  1744.         elif who.startswith("target "):
  1745.             target = parse_target(match.group(7), card)
  1746.             who = "target"
  1747.         else:
  1748.             who = "source.attached_to"
  1749.         effect = "%s.add_counters(%s%s)"%(who, counter, num)
  1750.         effect = 'if %scontroller.you_may("put %s %s counter%s on %%s"%%%s.name): %s'%(("%s and "%who if who == "source.attached_to" else ""), match.group(1), match.group(2), ("s" if match.group(5) else ""), who, effect)
  1751.     match = re.match(r"^(~|[Tt]arget (.+?)|([Ee]quipped|[Ee]nchanted|[Ff]ortified) (permanent|artifact|creature|land|enchantment|planeswalker)) can't be blocked this turn except by (one|two|three|four|five|six|seven|eight|nine|ten) or (more|fewer) creatures\.$", txt)
  1752.     if match:
  1753.         who, num, dir = match.group(1, 5, 6)
  1754.         if who == "~":
  1755.             who = "source"
  1756.         elif who.startswith(("Target ", "target ")):
  1757.             target = parse_target(match.group(2), card)
  1758.             who = "target"
  1759.         else: who = "source.attached_to"
  1760.         num = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 1
  1761.         if dir == "more":
  1762.             dir = ">="
  1763.         else:
  1764.             dir = "<="
  1765.         if triggered: return vars, target, "def checkBlock(self, combat_assignment, not_blocking): return (not self in combat_assignment) or (len(combat_assignment[self]) == 0 or len(combat_assignment[self]) %s %i)"%(dir, num), '%suntil_end_of_turn(override(%s, "checkBlock", checkBlock))'%(("if %s: "%who if who == "source.attached_to" else ""), who)
  1766.         else: return target, "def checkBlock(self, combat_assignment, not_blocking): return (not self in combat_assignment) or (len(combat_assignment[self]) == 0 or len(combat_assignment[self]) %s %i)"%(dir, num), '%suntil_end_of_turn(override(%s, "checkBlock", checkBlock))'%(("if %s: "%who if who == "source.attached_to" else ""), who)
  1767.     match = re.match(r"^([Tt]ap|[Uu]ntap) (~|target (.+?)|(equipped|enchanted|fortified) (permanent|artifact|creature|land|enchantment|planeswalker))\.$", txt)
  1768.     if match:
  1769.         dir, who = match.group(1, 2)
  1770.         dir = dir.lower()
  1771.         if who == "~":
  1772.             who = "source"
  1773.         elif who.startswith("target "):
  1774.             target = parse_target(match.group(3), card)
  1775.             who = "target"
  1776.         else: who = "source.attached_to"
  1777.         effect = "%s.%s()"%(who, dir)
  1778.         if who == "source.attached_to":
  1779.             effect = "if %s: %s"%(who, effect)
  1780.     match = re.match(r"^~ doesn't untap during your next untap step\.$", txt)
  1781.     if match:
  1782.         effect = "source.doesnt_untap_your_next_untap_step()"
  1783.     match = re.match(r"^[Dd]raw a card at the beginning of the next turn's upkeep\.$", txt)
  1784.     if match:
  1785.         effect = "source.delay(draw_card_next_upkeep)"
  1786.     if effect:
  1787.         if triggered: return vars, target, effect
  1788.         else: return target, effect
  1789.     else:
  1790.         return None
  1791.  
  1792. def parse_static_effect(txt, card):
  1793.     match = re.match(r"~ gets ([+-]\d+)/([+-]\d+)\.$", txt)
  1794.     if match:
  1795.         power, toughness = match.group(1, 2)
  1796.         return "source.augment_power_toughness(%i, %i)"%(int(power), int(toughness))
  1797.     match = re.match(r"~ has (.+)\.$", txt)
  1798.     if match:
  1799.         return "source.abilities.add(%s())"%(match.group(1).lower().replace(" ", "_"))
  1800.     match = re.match(r"~ gets ([+-]\d+)/([+-]\d+) and has (.+)\.$", txt)
  1801.     if match:
  1802.         power, toughness, abilities = match.group(1, 2, 3)
  1803.         if " and " in abilities:
  1804.             if ", " in abilities:
  1805.                 lst = abilities.split(", ")
  1806.                 lst[-1] = lst[-1].replace("and ", "")
  1807.             else:
  1808.                 lst = abilities.split(" and ")
  1809.             abilities = []
  1810.             for item in lst: abilities.append("source.abilities.add(%s())"%(item.replace(" ", "_").lower()))
  1811.             abilities = ", ".join(abilities)
  1812.         else:
  1813.             abilities = "source.abilities.add(%s())"%(abilities.replace(" ", "_").lower())
  1814.         return "source.augment_power_toughness(%i, %i), %s"%(int(power), int(toughness), abilities)
  1815.     match = re.match(r"~ can't be blocked except by (one|two|three|four|five|six|seven|eight|nine|ten) or (more|fewer) creatures\.$", txt)
  1816.     if match:
  1817.         num, dir = match.group(1, 2)
  1818.         num = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 1
  1819.         if dir == "more":
  1820.             dir = ">="
  1821.         else:
  1822.             dir = "<="
  1823.         return ("def checkBlock(self, combat_assignment, not_blocking): return (not self in combat_assignment) or (len(combat_assignment[self]) == 0 or len(combat_assignment[self]) %s %i)"%(dir, num), 'override(source, "checkBlock", checkBlock)')
  1824.     else:
  1825.         return None
  1826.  
  1827. def parse_attached_effect(txt, card):
  1828.     match = re.match(r"[Ee](quipped|nchanted) creature gets ([+-]\d+)/([+-]\d+)\.$", txt)
  1829.     if match:
  1830.         power, toughness = match.group(2, 3)
  1831.         return "card.augment_power_toughness(%i, %i)"%(int(power), int(toughness))
  1832.     match = re.match(r"([Ee](quipped|nchanted)|[Ff]ortified) (permanent|artifact|creature|land|enchantment|planeswalker) has (.+?)\.$", txt)
  1833.     if match:
  1834.         return "card.abilities.add(%s())"%(match.group(4).lower().replace(" ", "_"))
  1835.     match = re.match(r"[Ee](quipped|nchanted) creature gets ([+-]\d+)/([+-]\d+) and has (.+?)\.$", txt)
  1836.     if match:
  1837.         power, toughness, abilities = match.group(2, 3, 4)
  1838.         if " and " in abilities:
  1839.             if ", " in abilities:
  1840.                 lst = abilities.split(", ")
  1841.                 lst[-1] = lst[-1].replace("and ", "")
  1842.             else:
  1843.                 lst = abilities.split(" and ")
  1844.             abilities = []
  1845.             for item in lst: abilities.append("card.abilities.add(%s())"%(item.replace(" ", "_").lower()))
  1846.             abilities = ", ".join(abilities)
  1847.         else:
  1848.             abilities = "card.abilities.add(%s())"%(abilities.replace(" ", "_").lower())
  1849.         return "card.augment_power_toughness(%i, %i), %s"%(int(power), int(toughness), abilities)
  1850.     match = re.match(r"[Ee](quipped|nchanted) creature can't be blocked except by (one|two|three|four|five|six|seven|eight|nine|ten) or (more|fewer) creatures\.$", txt)
  1851.     if match:
  1852.         num, dir = match.group(2, 3)
  1853.         num = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 1
  1854.         if dir == "more":
  1855.             dir = ">="
  1856.         else:
  1857.             dir = "<="
  1858.         return ("def checkBlock(self, combat_assignment, not_blocking): return (not self in combat_assignment) or (len(combat_assignment[self]) == 0 or len(combat_assignment[self]) %s %i)"%(dir, num), 'override(card, "checkBlock", checkBlock)')
  1859.     match = re.match(r"[Ee](quipped|nchanted) creature can't (attack|block|attack or block)\.$", txt)
  1860.     if match:
  1861.         type = match.group(2)
  1862.         effect = ""
  1863.         if type == "attack" or type == "attack or block":
  1864.             effect += 'override(card, "canAttack", lambda self: False)'
  1865.             if type.endswith("block"): effect += ", "
  1866.         if type == "block" or type == "attack or block":
  1867.             effect += 'override(card, "canBlock", lambda self: False)'
  1868.         return effect
  1869.     else:
  1870.         return None
  1871.  
  1872. def parse_keywords(txt, card):
  1873.     lines = []
  1874.     txt = txt.lower().replace(";", ",")
  1875.     parts = [t.strip() for t in txt.split(",")]
  1876.     for p in parts:
  1877.         if p in keywords:
  1878.             keyword = keywords[p]
  1879.         else:
  1880.             for k, data in variable_keywords:
  1881.                 if p.startswith(k) and (len(p) == len(k) or not p[len(k)].isalpha()):
  1882.                     break
  1883.             else: continue
  1884.             val = p[len(k):].strip("- \t")
  1885.             if len(data) > 2:
  1886.                 val = val.split(" ")
  1887.                 # Suspend and Reinforce are written "Keyword Number--Cost"
  1888.                 # However, the magiccards.info parser writes it "Keyword Number-Cost".
  1889.                 val = sum([i.split("-") for i in val], [])
  1890.                 # But we can make it work with either! This strips out blank strings:
  1891.                 val = [i for i in val if i]
  1892.                 if len(val) == len(data) - 1:
  1893.                     result = []
  1894.                     for type, info in zip(data[1:], val):
  1895.                         result.append(convert_value(info, type, card))
  1896.                     keyword = data[0]%tuple(result)
  1897.                 else:
  1898.                     continue
  1899.             else:
  1900.                 keyword = data[0]%convert_value(val, data[1], card)
  1901.         if keyword: lines.append("abilities.add(%s)\n"%keyword)
  1902.     #lines.extend(["abilities.add(%s)\n"%keywords[p] for p in parts if p in keywords])
  1903.     if len(lines) < len(parts): return []
  1904.     return lines
  1905.  
  1906. def convert_value(value, type, card):
  1907.     if type == "number":
  1908.         # Do nothing
  1909.         return value
  1910.     elif type == "cost":
  1911.         return parse_cost(value, card)
  1912.     elif type == "subtype":
  1913.         return value.capitalize()
  1914.     elif type == "types":
  1915.         types = []
  1916.         subtypes = []
  1917.         for val in value.split(" or "):
  1918.             val = parse_plural(val, card)
  1919.             if val in types_list: types.append(val.capitalize())
  1920.             else: subtypes.append(val.capitalize())
  1921.         temp = []
  1922.         if types: temp.append("types=[%s]"%", ".join(types))
  1923.         if subtypes: temp.append("subtypes=[%s]"%", ".join(subtypes))
  1924.         return ", ".join(temp)
  1925.     elif type == "target":
  1926.         return parse_target(value, card)
  1927.     return "# XXX"
  1928.  
  1929. def strip_parens(manastr):
  1930.     result = ""
  1931.     current = ""
  1932.     inner = False
  1933.     for char in manastr:
  1934.         if char in "0123456789WUBRGTQSPXYZwubrgtqspxyz" or (inner and char == "/"):
  1935.             current += char
  1936.         elif char in ("(", "{"):
  1937.             inner = True
  1938.         elif char in (")", "}"):
  1939.             inner = False
  1940.         if not inner:
  1941.             if len(current) > 1: result += "(" + current + ")"
  1942.             else: result += current
  1943.             current = ""
  1944.     return result
  1945.  
  1946. def parse_cost(s, card):
  1947.     if re.search(r", (?!rounded)", s): #Some costs annoyingly have commas. Here, I'm assuming they're all of the "rounded up/down" variety.
  1948.         return " + ".join([parse_cost(cost, card) for cost in re.split(r", (?!rounded)", s)])
  1949.     else:
  1950.         if not set(s).difference(set("0123456789WUBRGTQSPXYZwubrgtqspxyz{}/()")):
  1951.             s = strip_parens(s)
  1952.         #if s.startswith("{"):
  1953.         #    s = s.replace("{", "").replace("}", "")
  1954.         if s in ("T", "t"):
  1955.             return "TapCost()"
  1956.         elif s in ("Q", "q"):
  1957.             return "UntapCost()"
  1958.         elif re.match(r"^(un)?tap (an|one|two|three|four|five|six|seven|eight|nine|ten|x|any number of) (un)?tapped (.+?) you control\.?$", s.lower()):
  1959.             dir, num, type = re.match(r"^(un)?tap (an|one|two|three|four|five|six|seven|eight|nine|ten|x|any number of) (un)?tapped (.+?) you control\.?$", s.lower()).group(1, 2, 4)
  1960.             num = {"an": 1,
  1961.                    "one": 1,
  1962.                    "two": 2,
  1963.                    "three": 3,
  1964.                    "four": 4,
  1965.                    "five": 5,
  1966.                    "six": 6,
  1967.                    "seven": 7,
  1968.                    "eight": 8,
  1969.                    "nine": 9,
  1970.                    "ten": 10,
  1971.                    "x": None,
  1972.                    "any number of": None}[num]
  1973.             type = parse_target(parse_plural(type, card), card)
  1974.             if num is None:
  1975.                 num = ", number=controller.getX()"
  1976.             elif num == 1: num = ""
  1977.             else: num = ", number=%i"%num
  1978.             if dir: dir = "Untap"
  1979.             else: dir = "Tap"
  1980.             return "%sCost(%s%s)"%(dir, type, num)
  1981.         elif re.match(r"[Pp]ay ([0123456789WUBRGSPXYZwubrgspxyz{}()/]+) and (\d+) life(, rounded (up|down))?\.?", s):
  1982.             return "ManaCost(%r) + LifeCost(%i)"%(strip_parens(s.split()[1]), int(s.split()[3]))
  1983.         elif s.lower().startswith("pay") and s.lower().endswith(("life", "life.", "life, rounded up", "life, rounded up.", "life, rounded down", "life, rounded down.")):
  1984.             if s.lower().split(" ")[1] == "half": # I hope there's no "pay half of X, rounded down" life costs out there... it would just get REALLY confusing.
  1985.                 if s.lower().endswith(("down", "down.")): return "LifeCost(controller.life / 2)"
  1986.                 return "LifeCost((controller.life + 1) / 2)"
  1987.             elif s.lower().split(" ")[1] == "x":
  1988.                 return "LifeCost(controller.getX())" # The value can be retrieved with cost.payment if need be.
  1989.             return "LifeCost(%d)"%int(s.split(" ")[1])
  1990.         elif re.match(r"^sacrifice (~|(an?|two|three|four|five|six|seven|eight|nine|ten) (.+?))\.?$", s.lower()):
  1991.             who, num, type = re.match(r"^sacrifice (~|(an?|two|three|four|five|six|seven|eight|nine|ten) (.+?))\.?$", s.lower()).group(1, 2, 3)
  1992.             if who == "~": return "SacrificeCost()"
  1993.             else:
  1994.                 type = parse_target(parse_plural(type, card), card)
  1995.                 if num == "an": num = "a"
  1996.                 num = ["a", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 1
  1997.                 return "SacrificeCost(%s%s)"%(type, ("" if num == 1 else ", number=%i"%num))
  1998.         elif re.match(r"^exile (~|(an?|two|three|four|five|six|seven|eight|nine|ten) (.+?))( from your (hand|graveyard))\.?$", s.lower()):
  1999.             who, num, type, where = re.match(r"^exile (~|(an?|two|three|four|five|six|seven|eight|nine|ten) (.+?))( from your (hand|graveyard))\.?$", s.lower()).group(1, 2, 3, 5)
  2000.             if not where: where = "Battlefield"
  2001.             else: where = where.capitalize()
  2002.             if who == "~":
  2003.                 return "ExileFrom%sCost()"%where
  2004.             else:
  2005.                 type = parse_target(parse_plural(type, card), card)
  2006.                 if num == "an": num = "a"
  2007.                 num = ["a", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 1
  2008.                 return "ExileFrom%sCost(%s%s)"%(where, type, ("" if num == 1 else ", number=%i"%num))
  2009.         elif re.match(r"^discard (an?|two|three|four|five|six|seven|eight|nine|ten) (.+?)\.?$", s.lower()):
  2010.             num, what = re.match(r"^discard (an?|two|three|four|five|six|seven|eight|nine|ten) (.+?)\.?$", s.lower()).group(1, 2)
  2011.             what = parse_target(parse_plural(what, card), card)
  2012.             if num == "an": num = "a"
  2013.             num = ["a", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten"].index(num) + 1
  2014.             return "DiscardCost(%s%s)"%(what, ("" if num == 1 else ", number=%i"%num))
  2015.         elif (s.startswith(("-", "+")) or (s == "0" and "Planeswalker" in card["types"])) and not set(s[1:]).difference(set("0123456789")):
  2016.             return "LoyaltyCost(%i)"%(int(s))
  2017.         elif s.lower().startswith("remove ") and s.lower().endswith(("counter from ~", "counters from ~")):
  2018.             temp = {"one": 1,
  2019.                     "two": 2,
  2020.                     "three": 3,
  2021.                     "four": 4,
  2022.                     "five": 5,
  2023.                     "six": 6,
  2024.                     "seven": 7,
  2025.                     "eight": 8,
  2026.                     "nine": 9,
  2027.                     "ten": 10, # Does any card ever remove this many counters as part of a (non-X) cost?
  2028.                     "all": -1,
  2029.                     "a": 1,
  2030.                     "an": 1,
  2031.                     "x": None,
  2032.                     "any": None,
  2033.                    }[s.lower().split()[1]]
  2034.             if " ".join(s.lower().split()[2:4]) == "number of":
  2035.                 if len(s.lower().split()) == 7: counter = ""
  2036.                 else: counter = s.lower().split()[4].replace("/", "")
  2037.             else:
  2038.                 if len(s.lower().split()) == 5: counter = ""
  2039.                 else: counter = s.lower().split()[2].replace("/", "")
  2040.             if temp is None:
  2041.                 return "RemoveCounterCost(%snumber=controller.getX())"%(repr(counter)+", " if counter else "") # As with LifeCost above, the value can be retrieved with cost.payment if need be.
  2042.             elif temp == 1:
  2043.                 return "RemoveCounterCost(%s)"%(repr(counter) if counter else "")
  2044.             else:
  2045.                 return "RemoveCounterCost(%snumber=%d)"%((repr(counter)+", " if counter else ""), temp)
  2046.         elif s.lower().startswith("remove ") and ("counter from a" in s.lower() or "counters from a" in s.lower()) and s.lower().endswith(" you control"):
  2047.             temp = {"one": 1,
  2048.                     "two": 2,
  2049.                     "three": 3,
  2050.                     "four": 4,
  2051.                     "five": 5,
  2052.                     "six": 6,
  2053.                     "seven": 7,
  2054.                     "eight": 8,
  2055.                     "nine": 9,
  2056.                     "ten": 10,
  2057.                     "all": -1,
  2058.                     "every": -1,
  2059.                     "each": -1,
  2060.                     "a": 1,
  2061.                     "an": 1,
  2062.                     "x": None,
  2063.                     "any number of": None,
  2064.                    }[s.lower().split()[1]]
  2065.             if " ".join(s.lower().split()[2:4]) == "counter from": counter = ""
  2066.             else: counter = s.lower().split()[2].replace("/", "")
  2067.             cardtype = parse_target(s.lower().split(" from ", 1)[1][:-12].split(" ", 1)[1], card)
  2068.             if temp is None:
  2069.                 return "RemoveCounterCost(%scardtype=%s, number=controller.getX())"%((repr(counter)+", " if counter else ""), cardtype)
  2070.             elif temp == 1:
  2071.                 return "RemoveCounterCost(%scardtype=%s)"%((repr(counter)+", " if counter else ""), cardtype)
  2072.             else:
  2073.                 return "RemoveCounterCost(%scardtype=%s, number=%d)"%((repr(counter)+", " if counter else ""), cardtype, temp)
  2074.         elif set(s).difference(set("0123456789WUBRGSPXYZwubrgspxyz{}()/")):
  2075.             return "# XXX cost object"
  2076.         else:
  2077.             return "ManaCost(%s)"%repr(s.upper())
  2078.  
  2079. types_list = ("artifact",
  2080.               "creature",
  2081.               "enchantment",
  2082.               "instant",
  2083.               "land",
  2084.               "planeswalker",
  2085.               "sorcery",
  2086.               "tribal",
  2087.              )
  2088.  
  2089. keywords = dict([
  2090.                  ("first strike", "first_strike()"),
  2091.                  ("double strike", "double_strike()"),
  2092.                  ("trample", "trample()"),
  2093.                  ("haste", "haste()"),
  2094.                  ("flash", "flash()"),
  2095.                  ("flying", "flying()"),
  2096.                  ("banding", "banding()"),
  2097.                  ("reach", "reach()"),
  2098.                  ("protection from white", "protection_from_white()"),
  2099.                  ("protection from blue", "protection_from_blue()"),
  2100.                  ("protection from black", "protection_from_black()"),
  2101.                  ("protection from red", "protection_from_red()"),
  2102.                  ("protection from green", "protection_from_green()"),
  2103.                  ("protection from artifacts", "protection_from_artifacts()"),
  2104.                  ("protection from everything", "protection_from_everything()"),
  2105.                  ("protection from multicolored", "protection_from_multicolored()"),
  2106.                  ("protection from monocolored", "protection_from_monocolored()"),
  2107.                  ("lifelink", "lifelink()"),
  2108.                  ("plainswalk", "plainswalk()"),
  2109.                  ("islandwalk", "islandwalk()"),
  2110.                  ("swampwalk", "swampwalk()"),
  2111.                  ("mountainwalk", "mountainwalk()"),
  2112.                  ("forestwalk", "forestwalk()"),
  2113.                  ("nonbasic landwalk", "nonbasic_landwalk()"),
  2114.                  ("legendary_landwalk", "legendary_landwalk()"),
  2115.                  ("shadow", "shadow()"),
  2116.                  ("defender", "defender()"),
  2117.                  ("shroud", "shroud()"),
  2118.                  ("vigilance", "vigilance()"),
  2119.                  ("fear", "fear()"),
  2120.                  ("flanking", "flanking()"),
  2121.                  ("exalted", "exalted()"),
  2122.                  ("cascade", "cascase()"),
  2123.                  ("deathtouch", "deathtouch()"),
  2124.                  ("intimidate", "intimidate()"),
  2125.                  ("bloodthirst x", "bloodthirst_x()"), #rule 702.51b - "Bloodthirst X" is a special form of bloodthirst. "Bloodthirst X" means "This permanent enters the battlefield with X +1/+1 counters on it, where X is the total damage your opponents have been dealt this turn."
  2126.                  ("sunburst", "sunburst()"),
  2127.                  ("hideaway", "hideaway()"),
  2128.                  ("changeling", "changeling()"),
  2129.                  ("horsemanship", "horsemanship()"),
  2130.                  ("persist", "persist()"),
  2131.                  ("wither", "wither()"),
  2132.                  ("split second", "split_second()"),
  2133.                  ("provoke", "provoke()"),
  2134.                  ("storm", "storm()"),
  2135.                  ("epic", "epic()"),
  2136.                  ("convoke", "convoke()"),
  2137.                  ("haunt", "haunt()"),
  2138.                  ("gravestorm", "gravestorm()"),
  2139.                  ("delve", "delve()"),
  2140.                  ("conspire", "conspire()"),
  2141.                  ("phasing", "phasing()"),
  2142.                  ("rebound", "rebound()"),
  2143.                  ("totem armor", "totem_armor()"),
  2144.                  ("infect", "infect()"),
  2145.                  ("battle cry", "battle_cry()"),
  2146.                  ("living weapon", "living_weapon()"),
  2147.                  ("hexproof", "hexproof()"),
  2148.                  ("undying", "undying()"),
  2149.                  ("soulbond", "soulbond()"),
  2150.                  ])
  2151.  
  2152. # Has to be a tuple because Python versions prior to 2.7 don't have OrderedDicts, and "champion an" must be checked before "champion a".
  2153. variable_keywords = (
  2154.                      ("enchant", ("enchant(%s)", "target")),
  2155.                      ("kicker", ("kicker(%s)", "cost")),
  2156.                      ("equip", ("equip(%s)", "cost")),
  2157.                      ("absorb", ("absorb(%s)", "number")),
  2158.                      ("fortify", ("fortify(%s)", "cost")),
  2159.                      ("multikicker", ("multikicker(%s)", "cost")),
  2160.                      ("devour", ("devour(%s)", "number")),
  2161.                      ("unearth", ("unearth(%s)", "cost")),
  2162.                      ("recover", ("recover(%s)", "cost")),
  2163.                      ("cycling", ("cycling(%s)", "cost")),
  2164.                      ("basic landcycling", ("basic_landcycling(%s)", "cost")),
  2165.                      ("plainscycling", ("plains_cycling(%s)", "cost")),
  2166.                      ("islandcycling", ("island_cycling(%s)", "cost")),
  2167.                      ("swampcycling", ("swamp_cycling(%s)", "cost")),
  2168.                      ("mountaincycling", ("mountain_cycling(%s)", "cost")),
  2169.                      ("forestcycling", ("forest_cycling(%s)", "cost")),
  2170.                      ("modular", ("modular(%s)", "number")),
  2171.                      ("graft", ("graft(%s)", "number")),
  2172.                      ("soulshift", ("soulshift(%s)", "number")),
  2173.                      ("bushido", ("bushido(%s)", "number")),
  2174.                      ("evoke", ("evoke(%s)", "cost")),
  2175.                      ("reinforce", ("reinforce(%s, %s)", "number", "cost")),
  2176.                      ("prowl", ("prowl(%s)", "cost")),
  2177.                      ("fading", ("fading(%s)", "number")),
  2178.                      ("transmute", ("transmute(%s)", "cost")),
  2179.                      ("echo", ("echo(%s)", "cost")),
  2180.                      ("suspend", ("suspend(%s, %s)", "number", "cost")),
  2181.                      ("rampage", ("rampage(%s)", "number")),
  2182.                      ("cumulative upkeep", ("cumulative_upkeep(%s)", "cost")),
  2183.                      ("vanishing", ("vanishing(%s)", "number")),
  2184.                      ("buyback", ("buyback(%s)", "cost")),
  2185.                      ("flashback", ("flashback(%s)", "cost")),
  2186.                      ("madness", ("madness(%s)", "cost")),
  2187.                      ("morph", ("morph(%s)", "cost")),
  2188.                      ("amplify", ("amplify(%s)", "number")),
  2189.                      ("affinity for", ("affinity(%s)", "types")),
  2190.                      ("entwine", ("entwine(%s)", "cost")),
  2191.                      ("splice onto", ("splice(%s, %s)", "subtype", "cost")),
  2192.                      ("offering", ("offering(%s)", "subtype")),
  2193.                      ("ninjutsu", ("ninjutsu(%s)", "cost")),
  2194.                      ("dredge", ("dredge(%s)", "number")),
  2195.                      ("bloodthirst", ("bloodthirst(%s)", "number")),
  2196.                      ("replicate", ("replicate(%s)", "cost")),
  2197.                      ("graft", ("graft(%s)", "number")),
  2198.                      ("ripple", ("ripple(%s)", "number")),
  2199.                      ("aura swap", ("aura_swap(%s)", "cost")),
  2200.                      ("frenzy", ("frenzy(%s)", "number")),
  2201.                      ("poisonous", ("poisonous(%s)", "number")),
  2202.                      ("transfigure", ("transfigure(%s)", "number")),
  2203.                      ("champion an", ("champion(%s)", "types")),
  2204.                      ("champion a", ("champion(%s)", "types")),
  2205.                      ("annihilator", ("annihilator(%s)", "number")),
  2206.                      ("level up", ("level_up(%s)", "cost")),
  2207.                      ("miracle", ("miracle(%s)", "cost")),
  2208.                     )
  2209.  
  2210. activated = '''@activated(txt=text[%d])
  2211. def ability():
  2212.    def effects(controller, source):
  2213.        cost = yield %s
  2214.        target = yield NoTarget()
  2215.        
  2216.        yield
  2217.    return effects
  2218. abilities.add(ability)
  2219. '''
  2220.  
  2221. activated_limit = '''@activated(txt=text[%d], limit=%s)
  2222. def ability():
  2223.    def effects(controller, source):
  2224.        cost = yield %s
  2225.        target = yield NoTarget()
  2226.        
  2227.        yield
  2228.    return effects
  2229. abilities.add(ability)
  2230. '''
  2231.  
  2232. mana = '''@mana(txt=text[%d])
  2233. def ability():
  2234.    def effects(controller, source):
  2235.        cost = yield %s
  2236.        target = yield NoTarget()
  2237.        controller.add_mana("0")
  2238.        yield
  2239.    return effects
  2240. abilities.add(ability)
  2241. '''
  2242.  
  2243. mana_limit = '''@mana(txt=text[%d], limit=%s)
  2244. def ability():
  2245.    def effects(controller, source):
  2246.        cost = yield %s
  2247.        target = yield NoTarget()
  2248.        controller.add_mana("0")
  2249.        yield
  2250.    return effects
  2251. abilities.add(ability)
  2252. '''
  2253.  
  2254. triggered = '''@triggered(txt=text[%d])
  2255. def ability():
  2256.    def condition(source, /* relevant event fields */):
  2257.        return True
  2258.    def effects(controller, source, /* relevant event fields */):
  2259.        target = yield NoTarget()
  2260.        
  2261.        yield
  2262.    return Trigger(# XXX Put event here, condition), effects
  2263. abilities.add(ability)
  2264. '''
  2265.  
  2266. static_tracking = '''@static_tracking(txt=text[%d])
  2267. def ability():
  2268.    def condition(source, card):
  2269.        return True
  2270.    def effects(source, card):
  2271.        yield /* continuous_effect */ # Make sure to yield the result of continuous effects
  2272.    return condition, effects
  2273. abilities.add(ability)
  2274. '''
  2275.  
  2276. static = '''@static(txt=text[%d])
  2277. def ability():
  2278.    def condition(source):
  2279.        return True
  2280.    def effects(source):
  2281.        yield /* continuous_effect */ # Make sure to yield the result of continuous effects
  2282.    return condition, effects
  2283. abilities.add(ability)
  2284. '''
  2285.  
  2286. attached = '''@attached(txt=text[%d])
  2287. def ability():
  2288.    def condition(source):
  2289.        return True
  2290.    def effects(source):
  2291.        card = source.attached_to
  2292.        yield /* continuous_effect */ # Make sure to yield the result of continuous effects
  2293.    return condition, effects
  2294. abilities.add(ability)
  2295. '''
  2296.  
  2297. comes_into_play = '''@enters_battlefield(txt=text[%d])
  2298. def ability():
  2299.    def before(source):
  2300.        pass
  2301.    def enterBattlefieldAs(self):
  2302.        \'''XXX Add replacement message\'''
  2303.        pass
  2304.    return before, enterBattlefieldAs
  2305. abilities.add(ability)
  2306. '''
  2307.  
  2308. nonperm = '''\
  2309. #################################
  2310.  
  2311. @%s()
  2312. def ability():
  2313.    def effects(controller, source):
  2314.        cost = yield source.cost
  2315.        target = yield NoTarget()
  2316.        
  2317.        yield
  2318.    return effects
  2319. abilities.add(ability)
  2320. '''
  2321.  
  2322. if __name__ == "__main__":
  2323.     import sys, os
  2324.     if sys.version_info[0] < 3: input = raw_input
  2325.     outfile = None
  2326.     if len(sys.argv) == 3:
  2327.         lines = []
  2328.         with open(sys.argv[1], "r") as f:
  2329.             for line in f:
  2330.                 line = line.strip()
  2331.                 lines.append(line)
  2332.         if not "|" in lines[0]:
  2333.             newlines = []
  2334.             current = []
  2335.             for line in lines:
  2336.                 if line:
  2337.                     current.append(line)
  2338.                 elif current:
  2339.                     newlines.append("|".join(current))
  2340.                     current = []
  2341.             if current:
  2342.                 newlines.append("|".join(current))
  2343.             lines = newlines
  2344.         results = []
  2345.         for line in lines:
  2346.             dict = parse_card_oracle(line)
  2347.             print("Parsing %r..."%dict["name"])
  2348.             results.append(carddict_to_code(dict) + "--------------- %s\n"%dict["name"])
  2349.         with open(sys.argv[2], "a") as f:
  2350.             f.write("\n".join(results))
  2351.         raise SystemExit("Conversion complete.")
  2352.     elif len(sys.argv) == 2:
  2353.         outfile = sys.argv[1]
  2354.     elif len(sys.argv) > 1:
  2355.         raise SystemExit("Usage: %s [[read_file] write_file]"%(os.path.basename(sys.argv[0])))
  2356.     line = None
  2357.     lines = []
  2358.     while not line == "exit":
  2359.         while not (isinstance(line, str) and (line.strip() == "" or line == "exit")):
  2360.             line = input()
  2361.             if not line.strip() == "": lines.append(line)
  2362.         if not line == "exit" and lines:
  2363.             dict = parse_card_oracle("|".join(lines))
  2364.             result = carddict_to_code(dict) + "--------------- %s\n\n"%dict["name"]
  2365.             if outfile:
  2366.                 with open(outfile, "a") as f:
  2367.                     f.write(result)
  2368.             else:
  2369.                 print result
  2370.             line = None
  2371.             lines = []
Add Comment
Please, Sign In to add comment