Package: ertai
ErtaiMathematical models of Magic the Gathering.TutorialWe will useertaito obtain the probability of being able to play a card in
our first turn.>>>importertai>>>importrandom>>>defbuild_deck(card_counts,number_of_lands):..."""...     This returns a list of `ertai.Card` objects with the...     specified number of cards...     """...deck=[ertai.BasicLand()for_inrange(number_of_lands)]...forcost,numberincard_counts.items():...cost=ertai.Mana(*(Nonefor_inrange(cost)))...deck+=[ertai.Card(cost=cost)for_inrange(number)]...returndeck>>>card_counts={1:10,2:16}>>>number_of_lands=14>>>deck=build_deck(card_counts=card_counts,number_of_lands=number_of_lands)>>>forcardindeck:...print(card)BasicLand(title=None,cost=0Mana,tapped=False,color=None)BasicLand(title=None,cost=0Mana,tapped=False,color=None)BasicLand(title=None,cost=0Mana,tapped=False,color=None)BasicLand(title=None,cost=0Mana,tapped=False,color=None)BasicLand(title=None,cost=0Mana,tapped=False,color=None)BasicLand(title=None,cost=0Mana,tapped=False,color=None)BasicLand(title=None,cost=0Mana,tapped=False,color=None)BasicLand(title=None,cost=0Mana,tapped=False,color=None)BasicLand(title=None,cost=0Mana,tapped=False,color=None)BasicLand(title=None,cost=0Mana,tapped=False,color=None)BasicLand(title=None,cost=0Mana,tapped=False,color=None)BasicLand(title=None,cost=0Mana,tapped=False,color=None)BasicLand(title=None,cost=0Mana,tapped=False,color=None)BasicLand(title=None,cost=0Mana,tapped=False,color=None)Card(title=None,cost=1NoneMana,tapped=False)Card(title=None,cost=1NoneMana,tapped=False)Card(title=None,cost=1NoneMana,tapped=False)Card(title=None,cost=1NoneMana,tapped=False)Card(title=None,cost=1NoneMana,tapped=False)Card(title=None,cost=1NoneMana,tapped=False)Card(title=None,cost=1NoneMana,tapped=False)Card(title=None,cost=1NoneMana,tapped=False)Card(title=None,cost=1NoneMana,tapped=False)Card(title=None,cost=1NoneMana,tapped=False)Card(title=None,cost=2NoneMana,tapped=False)Card(title=None,cost=2NoneMana,tapped=False)Card(title=None,cost=2NoneMana,tapped=False)Card(title=None,cost=2NoneMana,tapped=False)Card(title=None,cost=2NoneMana,tapped=False)Card(title=None,cost=2NoneMana,tapped=False)Card(title=None,cost=2NoneMana,tapped=False)Card(title=None,cost=2NoneMana,tapped=False)Card(title=None,cost=2NoneMana,tapped=False)Card(title=None,cost=2NoneMana,tapped=False)Card(title=None,cost=2NoneMana,tapped=False)Card(title=None,cost=2NoneMana,tapped=False)Card(title=None,cost=2NoneMana,tapped=False)Card(title=None,cost=2NoneMana,tapped=False)Card(title=None,cost=2NoneMana,tapped=False)Card(title=None,cost=2NoneMana,tapped=False)The above is code that uses theertaiclasses to construct a deck.Now let us write code to find the probability of drawing a hand with a card that
can be played.>>> def play_land(hand):
...     """
...     If the hand contains a land, return it.
...     """
...     for card in hand:
...          if isinstance(card, ertai.BasicLand):
...              hand.remove(card)
...              return card
...     return None
>>> def play_card(hand, pool):
...     """
...     Given a pool of mana, play the first possible card in a hand.
...     """
...     for card in hand:
...         if (card.cost <= pool) and (isinstance(card, ertai.BasicLand) is False):
...             pool = card.cast(pool)
...             hand.remove(card)
...             return card
...     return NoneWith the above we can now write code to draw a hand of card and see if we can
play:>>> def can_play_spell(card_counts, number_of_lands, seed):
...     """
...     Given a hand returns a boolean if we are able to play a spell
...
...     This assumes we play on the first turn.
...     """
...     deck = build_deck(card_counts=card_counts, number_of_lands=number_of_lands)
...     random.seed(seed)
...     random.shuffle(deck)
...     hand = deck[:7]
...     if (land:=play_land(hand)) is not None:
...         pool = land.generate_mana()
...         return play_card(hand, pool) is not None
...     return False
>>> can_play_spell(card_counts=card_counts, number_of_lands=number_of_lands, seed=0)
TrueThe above is a single simulation, let us compute the expected probability:>>>importnumpyasnp>>>defexpected_probability_of_first_turn_play(card_counts,number_of_lands,number_of_repetitions=500):..."""Returns the expected probability of having a first turn play."""...returnnp.mean([can_play_spell(card_counts=card_counts,number_of_lands=number_of_lands,seed=seed)forseedinrange(number_of_repetitions)])Let us see how this probability changes as we modify the number of cards with
cost 1 in our deck. We will assume a 40 card deck with 16 lands and all other
cards have cost 2.>>>number_of_lands=14>>>fornumber_of_cards_with_cost_1inrange(1,26+1):...card_counts={1:number_of_cards_with_cost_1,2:26-number_of_cards_with_cost_1}...probability=expected_probability_of_first_turn_play(card_counts=card_counts,number_of_lands=number_of_lands)...print(number_of_cards_with_cost_1,round(probability,3))10.13420.28630.42240.50450.5860.64470.71480.76290.796100.838110.862120.88130.898140.914150.932160.946170.948180.95190.954200.958210.958220.958230.958240.958250.958260.958We see that as expected the probability of playing in the first turn goes up as
the number of cards with cost 1 increases.How toHow to create manaertaihas a mana class that can be used to create any amount of mana.>>>spell_cost=ertai.Mana("Blue","Blue")>>>spell_cost2BlueManaHow to add more mana>>>spell_cost+=ertai.Mana("Red")>>>spell_cost2BlueMana,1RedManaHow to check if one collection of mana can be paid using another>>>mana_pool=ertai.Mana("Blue","Red","Red","Blue")>>>spell_cost<=mana_poolTrueHow to take one collection of mana away from another>>>mana_pool-=spell_cost>>>mana_pool1RedManaWe see that we no longer have enough mana for the spell:>>>spell_cost<=mana_poolFalseHow to create a basic land>>>island=ertai.BasicLand(title="Island",color="Blue")>>>islandBasicLand(title='Island',cost=0Mana,tapped=False,color='Blue')### How to generate mana from a land>>>island.tappedFalse>>>island.generate_mana()1BlueMana>>>island.tappedTrueWhen tapped the land will not generate mana.>>>island.generate_mana()How to untap or tap a cardAll cards inertaihave auntapmethod.>>>island.untap()>>>island.tappedFalseThey also have atapmethod.>>>island.tap()>>>island.tappedTrueHow to cast a cardAt present, casting a card does nothing but modify a given mana poolif the
cost can be paid.>>>mana_pool=ertai.Mana("Red","Red","Black")>>>card_cost=ertai.Mana("Red")>>>card=ertai.Card(title="Lightning bolt",cost=card_cost)>>>mana_pool=card.cast(mana_pool)>>>mana_pool1RedMana,1BlackManaNote that if a card cannot be case then the mana pool will stay unchanged but no
warning is given.>>>card_cost=ertai.Mana("Blue","Blue")>>>card=ertai.Card(title="Counterspell",cost=card_cost)>>>mana_pool=card.cast(mana_pool)>>>mana_pool1RedMana,1BlackManaHow to create a ceature cardPass apowerandtoughnessto theCreatureclass:>>>creature=ertai.Creature(title="Selfless Savior",cost=ertai.Mana("White"),...power=1,...toughness=1)>>>creatureSelflessSaviorCost:1WhiteManaPower:1Toughness:1How to use a creature to fightWe can then use this creature to fight atargetcreate:>>>target=ertai.Creature(title="Usher of the Fallen",cost=ertai.Mana("White"),...power=2,...toughness=1)>>>creature.fight(target)In this case the create is no longer alive after the fight:>>>creature.is_alive()FalseHow to contributeClone the repository and create a virtual environment:$gitclonehttps://github.com/drvinceknight/ertai.git
$cdertai
$python-mvenvenvActivate the virtual environment and installtox:$sourceenv/bin/activate
$python-mpipinstalltoxMake modifications.To run the tests:$python-mtoxPull requests are welcome.DiscussionWhat is Magic The GatheringMagic the Gathering is a collectable card game that was first released in 1993.Here is the official basic rules:https://magic.wizards.com/en/magic-gameplayHere is the wikipedia page entry:https://en.wikipedia.org/wiki/Magic:_The_GatheringWhy is it called ErtaiAlongside the game there is a lot of lore that exists. Ertai was a particular
character. From [https://villains.fandom.com/wiki/Ertai]:He is a brilliant but arrogant mage who learned from the head wizard of the
Tolarian Academy Barrin and signed up to join the Weatherlight Crew.What can this library be used forAt present this library offers data class that can be used to model aspects of
Magic the Gathering.Theerta.Manaclass allows us to carry out Mana arithmetic:>>> mana_pool = ertai.Mana("Blue", "Blue", "Red", "None", "Blue")
>>> ertai_the_adept_cost = ertai.Mana("Blue", "Blue", "None")
>>> mana_pool - ertai_the_adept_cost
1 Blue Mana, 1 Red ManaTheertai.Cardclass can be used to create new cards with given abilities.Development stackThere are a number of continuous integration checks that are used:blackandflake8for code style.interrogateto ensure all
code has docstrings.mypyfor static type checking.coverageto ensure 100%
coverage of source code by test.mccabeto measure code complexity.toxis used to test the code in isolated environments.ReferenceOther Python magic the gathering librariesMagic: The Gathering SDK:
A wrapper around the MTG API of magicthegathering.io