No description, website, or topics provided.
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
archive
data
dev
.gitignore
archive.py
driver.py
helpers.py
manager.py
readme.md

readme.md

Titan Breach Simulator

Usage:

./driver.py N DECKNAME [DECKNAMES...]

Where N is the number of trials you want to conduct and DECKNAME is the name of the deck list you want to use. Deck lists are defined in a dictionary near the top of driver.py.

Example Output

[ 26-baseline    0] D 3  B         0 k states /   0.05 s =      6 k states/s

[1] initial
[2] drawing 7
drawing: Mountain ; Bolt ; Breach ; Search ; Bolt ; Forest ; Breach
[13] passing turn

TURN 1
drawing: Forest
HAND:  Mountain ; Bolt ; Breach ; Search ; Bolt ; Forest ; Breach ; Forest
[22] playing Forest
[26] suspending Search
[37] passing turn
HAND:  Mountain ; Bolt ; Breach ; Bolt ; Breach ; Forest
BOARD: ((Forest)) ; (Forest)

TURN 2
drawing: Elder
HAND:  Mountain ; Bolt ; Breach ; Bolt ; Breach ; Forest ; Elder
BOARD: (Forest) ; Forest
[99] playing Mountain
[133] casting and cracking Elder
[144] passing turn
HAND:  Bolt ; Breach ; Bolt ; Breach ; Forest
BOARD: (Forest) ; (Forest) ; (Forest) ; (Mountain)

TURN 3
drawing: Pact
HAND:  Bolt ; Breach ; Bolt ; Breach ; Forest ; Pact
BOARD: Forest ; Forest ; Forest ; Mountain
[205] playing Forest
[279] casting Breach with Pact
16401 PLAY; 16371 DRAW

turn :         play ;         draw ;         mean
   1 :   0.0%  ±   0.0%  ;   0.0%  ±   0.0%  ;   0.0%  ±   0.0%
   2 :   0.1%  ±   0.0%  ;   0.3%  ±   0.0%  ;   0.2%  ±   0.0%
   3 :  25.0%  ±   0.4%  ;  47.6%  ±   0.5%  ;  36.2%  ±   0.3%
   4 :  84.9%  ±   0.7%  ;  94.6%  ±   0.8%  ;  89.7%  ±   0.5%

Advanced Usage

The functions of all the cards in the deck have been coded up in manager.py, as have several plausible additions. If you want to simulate anything else, you'll have to code up the card's effects yourself. This has three steps. For example, to add a cantrip:

  • Implement cast_cantrip methods for the GameState object. The first makes sure the operation makes sense (for example, that the cantrip is in our hand and we can afford it). If it is, it clones the game state then pays the mana, moves the cantrip to the graveyard, and draws a card.
  • Add a call to cast_cantrip in the next_states function of the GameState object. This function figures out all possible plays from the present game state, clones the state that many times, and tries them all.
  • If applicable, update is_creature, is_land, and is_colorless at the bottom of manager.py so that Cantrip interacts correctly with Oath of Nissa, Ancient Stirrings, etc.

Systematic Bias

Shuffling is a problem for the simulation, so we don't do it. When the computer pops Sakura-Tribe Elder for a Mountain, it doesn't pull that Mountain out of our deck; it just creates a new one out of thin air. This means we don't capture the (marginal) effects of deck thinning, and slightly over-estimate the odds of drawing lands.

The problem is a bit subtle, so let's look at an example:

It's T3. We've got plenty of land and a Through the Breach, but we haven't drawn Primeval Titan yet. We play a Wooded Foothills and crack it. The options are Cinder Glade, Forest, Mountain, and Stomping Ground.

Strategically, there's little difference between them. No matter what we fetch, we'll be able to Breach a Titan as soon as we draw it. But the computer sees a choice, so it tries all the options. It makes 4 copies of the current game state (including the order of the deck). Then game state #1 fetches a Cinder Glade and shuffles, game state #2 fetches a Forest and shuffles, etc.

There are about 48 cards left in the deck at this point, including 4 Primeval Titans and 4 Summoner's Pacts, so each game state has a one-in-six chance to hit. If one of them does, it stops the simulation, saying "I found a line that gets T4 Titan!" But -- because the states all shuffled independently -- that happens way more often than one-in-six.

When game states shuffle independently, it essentially lets us double-dip on luck. It's like rolling four dice and keeping the best result. We can make the problem smaller by always shuffling in the same way, or by fetching the land but leaving the rest of the deck alone, but it doesn't go away. The only way to completely solve the problem is to make it impossible for the order of our deck to be affected by a "free" choice (like what to fetch).