Created
April 15, 2020 14:33
-
-
Save MaxHalford/0bf06078d2dd6ef3609f4e3cca5bc41c to your computer and use it in GitHub Desktop.
Analysis of the Zixor card from Hearthstone
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"cells": [ | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"# Zixor analyis" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"class Card:\n", | |
" \n", | |
" def __eq__(self, other):\n", | |
" \"\"\"We'll use to `==` operator to compare cards.\"\"\"\n", | |
" return self.name == other.name\n", | |
" \n", | |
"class BeastMixin:\n", | |
" pass\n", | |
"\n", | |
"class RushMixin:\n", | |
" pass\n", | |
" \n", | |
"class Zixor(Card, BeastMixin, RushMixin):\n", | |
" \"\"\"Zixor is the first card we want to reach.\"\"\"\n", | |
" name = 'Zixor, Apex Predator'\n", | |
" mana = 3\n", | |
" \n", | |
"class ZixorPrime(Card, BeastMixin, RushMixin):\n", | |
" \"\"\"Zixor Prime is the second card we want to reach.\n", | |
" \n", | |
" This card is randomly inserted into the deck once Zixor's deathrattle triggers.\n", | |
" \n", | |
" \"\"\"\n", | |
" name = 'Zixor Prime'\n", | |
" mana = 8\n", | |
" \n", | |
"class Wisp(Card):\n", | |
" \"\"\"This will act as a filler card.\"\"\"\n", | |
" \n", | |
" name = 'Wisp'\n", | |
" mana = 0" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Triggering Zixor's deathrattle" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"import random\n", | |
"\n", | |
"def look_for_zixor(deck, rng=random):\n", | |
" \"\"\"Simulation with pure draw, no gryphons.\"\"\"\n", | |
" \n", | |
" # Generate a first hand, we'll assume that we're going first so we draw 3 cards\n", | |
" hand, deck = deck[:3], deck[3:]\n", | |
" \n", | |
" # Mulligan if Zixor is not in the hand\n", | |
" if Zixor not in hand:\n", | |
" deck.extend(hand) # add the hand to the end of the deck\n", | |
" n_mulligan = random.randint(2, 3) \n", | |
" hand, deck = deck[:n_mulligan], deck[n_mulligan:]\n", | |
" rng.shuffle(deck)\n", | |
" \n", | |
" # Draw a first card\n", | |
" hand.append(deck.pop(0))\n", | |
" \n", | |
" # Draw until Zixor is in hand and there is enough mana to summon him\n", | |
" turn = 1\n", | |
" while Zixor not in hand or turn < Zixor.mana:\n", | |
" turn += 1\n", | |
" hand.append(deck.pop(0))\n", | |
"\n", | |
" # Let's assume that it will take 1 or 2 turns to trigger Zixor's deathrattle\n", | |
" for _ in range(random.randint(1, 2)):\n", | |
" turn += 1\n", | |
" try:\n", | |
" hand.append(deck.pop(0))\n", | |
" except IndexError: # the deck might be empty at this point\n", | |
" pass\n", | |
"\n", | |
" # Now we randomly insert Zixor Prime into the deck\n", | |
" i = rng.randint(0, len(deck))\n", | |
" deck.insert(i, ZixorPrime)\n", | |
"\n", | |
" return turn, hand, deck" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 3, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"CPU times: user 2.31 s, sys: 10.8 ms, total: 2.32 s\n", | |
"Wall time: 2.33 s\n", | |
"Turns till Zixor deathrattles: 13.747 ± 8.306, median is 13\n" | |
] | |
} | |
], | |
"source": [ | |
"import statistics as stats\n", | |
"\n", | |
"def simulate(rng=random):\n", | |
" deck = [Zixor] + [Wisp] * 29\n", | |
" rng.shuffle(deck)\n", | |
" turn, _, _ = look_for_zixor(deck, rng=rng)\n", | |
" return turn\n", | |
"\n", | |
"%time sims = [simulate() for _ in range(50_000)]\n", | |
"print(f'Turns till Zixor deathrattles: {stats.mean(sims):.3f} ± {stats.stdev(sims):.3f}, median is {int(stats.median(sims))}')" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Drawing Zixor Prime" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 4, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def look_for_zixor_prime(deck, rng=random):\n", | |
" \n", | |
" # We first look for Zixor\n", | |
" turn, hand, deck = look_for_zixor(deck, rng=rng)\n", | |
" \n", | |
" # Draw until Zixor Prime is in hand and there is enough mana to summon him\n", | |
" while ZixorPrime not in hand or turn < ZixorPrime.mana:\n", | |
" turn += 1\n", | |
" hand.append(deck.pop(0))\n", | |
" \n", | |
" return turn, hand, deck" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 5, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"CPU times: user 2.42 s, sys: 11.6 ms, total: 2.43 s\n", | |
"Wall time: 2.44 s\n", | |
"Turns till Zixor Prime is summoned: 21.752 ± 6.310, median is 23\n" | |
] | |
} | |
], | |
"source": [ | |
"def simulate(rng=random):\n", | |
" deck = [Zixor] + [Wisp] * 29\n", | |
" rng.shuffle(deck)\n", | |
" turn, _, _ = look_for_zixor_prime(deck, rng=rng)\n", | |
" return turn\n", | |
"\n", | |
"%time sims = [simulate() for _ in range(50_000)]\n", | |
"print(f'Turns till Zixor Prime is summoned: {stats.mean(sims):.3f} ± {stats.stdev(sims):.3f}, median is {int(stats.median(sims))}')" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"## Card draw helpers" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"[Diving Gryphon](https://www.hearthpwn.com/cards/151350-diving-gryphon)." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 6, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"class DivingGryphon(Card, BeastMixin, RushMixin):\n", | |
" name = 'Diving Gryphon'\n", | |
" mana = 3\n", | |
" \n", | |
" @classmethod\n", | |
" def draw(cls, hand, deck, rng=random):\n", | |
" rush_cards = [i for i, card in enumerate(deck) if issubclass(card, RushMixin)]\n", | |
" if rush_cards:\n", | |
" i = rng.choice(rush_cards)\n", | |
" hand.append(deck.pop(i))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"[Scavenger's Ingenuity](https://www.hearthpwn.com/cards/210658-scavengers-ingenuity)." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 7, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"class ScavengersIngenuity(Card, BeastMixin, RushMixin):\n", | |
" name = \"Scavenger's Ingenuity\"\n", | |
" mana = 3\n", | |
" \n", | |
" @classmethod\n", | |
" def draw(cls, hand, deck, rng=random):\n", | |
" beasts = [i for i, card in enumerate(deck) if issubclass(card, BeastMixin)]\n", | |
" if beasts:\n", | |
" i = rng.choice(beasts)\n", | |
" hand.append(deck.pop(i))" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"[Tracking](https://hearthstone.gamepedia.com/Tracking)." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 9, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"class Tracking(Card):\n", | |
" name = 'Tracking'\n", | |
" mana = 1\n", | |
" \n", | |
" @classmethod\n", | |
" def draw(cls, hand, deck, rng=random):\n", | |
" next_3, deck = deck[:3], deck[3:]\n", | |
" if not next_3:\n", | |
" return\n", | |
" for card in wish_list:\n", | |
" if card in next_3:\n", | |
" hand.append(card)\n", | |
" return\n", | |
" # If no tracked card was in the wish list then we pick one at random\n", | |
" hand.append(rng.choice(next_3))\n", | |
" \n", | |
"wish_list = [ZixorPrime, Zixor, DivingGryphon, ScavengersIngenuity, Tracking]" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Now let's our simulation functions to take into account the draw cards." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 10, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def look_for_zixor(deck, rng=random):\n", | |
" \"\"\"Simulation which takes into account card draw helpers.\"\"\"\n", | |
" \n", | |
" turn = 1\n", | |
" \n", | |
" # Generate a first hand, we'll assume that we're going first so we draw 3 cards\n", | |
" hand, deck = deck[:3], deck[3:]\n", | |
" \n", | |
" # Mulligan if Zixor is not in the hand\n", | |
" if Zixor not in hand:\n", | |
" deck.extend(hand) # add the hand to the end of the deck\n", | |
" n_mulligan = random.randint(2, 3) \n", | |
" hand, deck = deck[:n_mulligan], deck[n_mulligan:]\n", | |
" rng.shuffle(deck)\n", | |
" \n", | |
" # Draw a first card\n", | |
" hand.append(deck.pop(0))\n", | |
" \n", | |
" # Check if any draw card can be used\n", | |
" for draw_card in [Tracking]:\n", | |
" if draw_card in hand and draw_card.mana <= 1:\n", | |
" draw_card.draw(hand, deck)\n", | |
" hand.remove(draw_card)\n", | |
" \n", | |
" # Draw until Zixor is in hand \n", | |
" while Zixor not in hand:\n", | |
" turn += 1\n", | |
" hand.append(deck.pop(0))\n", | |
" \n", | |
" # Check if any draw card can be used\n", | |
" for draw_card in [DivingGryphon, ScavengersIngenuity, Tracking]:\n", | |
" if draw_card in hand and turn >= draw_card.mana:\n", | |
" draw_card.draw(hand, deck)\n", | |
" hand.remove(draw_card)\n", | |
" \n", | |
" # Draw until there is enough mana to summon Zixor\n", | |
" while turn < Zixor.mana:\n", | |
" hand.append(deck.pop(0))\n", | |
" turn += 1\n", | |
"\n", | |
" # Let's assume that it will take 1 or 2 turns to trigger Zixor's deathrattle\n", | |
" for _ in range(random.randint(1, 2)):\n", | |
" turn += 1\n", | |
" try:\n", | |
" hand.append(deck.pop(0))\n", | |
" except IndexError: # the deck might be empty at this point\n", | |
" pass\n", | |
"\n", | |
" # Now we randomly insert Zixor Prime into the deck\n", | |
" i = rng.randint(0, len(deck))\n", | |
" deck.insert(i, ZixorPrime)\n", | |
"\n", | |
" return turn, hand, deck" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 11, | |
"metadata": {}, | |
"outputs": [], | |
"source": [ | |
"def look_for_zixor_prime(deck, rng=random):\n", | |
" \n", | |
" # We first look for Zixor\n", | |
" turn, hand, deck = look_for_zixor(deck, rng=rng)\n", | |
" \n", | |
" # Draw until Zixor Prime is in hand\n", | |
" while ZixorPrime not in hand:\n", | |
" hand.append(deck.pop(0))\n", | |
" turn += 1\n", | |
" \n", | |
" for draw_card in [DivingGryphon, ScavengersIngenuity, Tracking]:\n", | |
" if draw_card in hand and turn >= draw_card.mana:\n", | |
" draw_card.draw(hand, deck)\n", | |
" hand.remove(draw_card)\n", | |
" \n", | |
" while turn < ZixorPrime.mana:\n", | |
" turn += 1\n", | |
" hand.append(deck.pop(0))\n", | |
" \n", | |
" \n", | |
" return turn, hand, deck" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Now let's try out the different possibilities." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 12, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"data": { | |
"text/plain": [ | |
"27" | |
] | |
}, | |
"execution_count": 12, | |
"metadata": {}, | |
"output_type": "execute_result" | |
} | |
], | |
"source": [ | |
"import itertools\n", | |
"\n", | |
"def powerset(iterable):\n", | |
" \"powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)\"\n", | |
" s = list(iterable)\n", | |
" return itertools.chain.from_iterable(itertools.combinations(s, r) for r in range(len(s)+1))\n", | |
"\n", | |
"cards = 2 * [DivingGryphon] + 2 * [Tracking] + 2 * [ScavengersIngenuity]\n", | |
"combos = list(powerset(cards))\n", | |
"combos = list(dict.fromkeys(combos)) # removes duplicates while preserving order\n", | |
"len(combos)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"Let's do a first analysis where there are no beasts in the deck, other than Zixor and potential Diving Gryphons." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 16, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Pure draw\n", | |
" Turns till Zixor deathrattles: 13.743 ± 8.327, median is 13\n", | |
" Turns till Zixor Prime is summoned: 21.802 ± 6.293, median is 23\n", | |
"\n", | |
"1x Diving Gryphon\n", | |
" Turns till Zixor deathrattles: 9.461 ± 6.017, median is 7\n", | |
" Turns till Zixor Prime is summoned: 17.167 ± 6.338, median is 17\n", | |
"\n", | |
"1x Tracking\n", | |
" Turns till Zixor deathrattles: 13.671 ± 8.268, median is 13\n", | |
" Turns till Zixor Prime is summoned: 21.517 ± 6.310, median is 23\n", | |
"\n", | |
"1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 9.453 ± 5.988, median is 7\n", | |
" Turns till Zixor Prime is summoned: 17.057 ± 6.374, median is 17\n", | |
"\n", | |
"2x Diving Gryphon\n", | |
" Turns till Zixor deathrattles: 7.766 ± 4.511, median is 6\n", | |
" Turns till Zixor Prime is summoned: 14.444 ± 5.840, median is 14\n", | |
"\n", | |
"1x Diving Gryphon, 1x Tracking\n", | |
" Turns till Zixor deathrattles: 9.419 ± 5.990, median is 7\n", | |
" Turns till Zixor Prime is summoned: 16.722 ± 6.323, median is 17\n", | |
"\n", | |
"1x Diving Gryphon, 1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 7.640 ± 4.530, median is 5\n", | |
" Turns till Zixor Prime is summoned: 14.539 ± 5.831, median is 14\n", | |
"\n", | |
"2x Tracking\n", | |
" Turns till Zixor deathrattles: 13.560 ± 8.221, median is 12\n", | |
" Turns till Zixor Prime is summoned: 21.195 ± 6.379, median is 23\n", | |
"\n", | |
"1x Tracking, 1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 9.479 ± 6.034, median is 7\n", | |
" Turns till Zixor Prime is summoned: 16.769 ± 6.378, median is 17\n", | |
"\n", | |
"2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 7.830 ± 4.552, median is 6\n", | |
" Turns till Zixor Prime is summoned: 14.441 ± 5.826, median is 14\n", | |
"\n", | |
"2x Diving Gryphon, 1x Tracking\n", | |
" Turns till Zixor deathrattles: 7.721 ± 4.496, median is 6\n", | |
" Turns till Zixor Prime is summoned: 13.988 ± 5.721, median is 13\n", | |
"\n", | |
"2x Diving Gryphon, 1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 6.813 ± 3.560, median is 5\n", | |
" Turns till Zixor Prime is summoned: 12.606 ± 5.125, median is 11\n", | |
"\n", | |
"1x Diving Gryphon, 2x Tracking\n", | |
" Turns till Zixor deathrattles: 9.316 ± 5.994, median is 7\n", | |
" Turns till Zixor Prime is summoned: 16.299 ± 6.315, median is 16\n", | |
"\n", | |
"1x Diving Gryphon, 1x Tracking, 1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 7.623 ± 4.541, median is 5\n", | |
" Turns till Zixor Prime is summoned: 14.234 ± 5.763, median is 13\n", | |
"\n", | |
"1x Diving Gryphon, 2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 6.844 ± 3.530, median is 5\n", | |
" Turns till Zixor Prime is summoned: 12.689 ± 5.158, median is 11\n", | |
"\n", | |
"2x Tracking, 1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 9.399 ± 6.023, median is 7\n", | |
" Turns till Zixor Prime is summoned: 16.338 ± 6.325, median is 16\n", | |
"\n", | |
"1x Tracking, 2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 7.783 ± 4.554, median is 6\n", | |
" Turns till Zixor Prime is summoned: 14.086 ± 5.766, median is 13\n", | |
"\n", | |
"2x Diving Gryphon, 2x Tracking\n", | |
" Turns till Zixor deathrattles: 7.664 ± 4.471, median is 5\n", | |
" Turns till Zixor Prime is summoned: 13.707 ± 5.681, median is 12\n", | |
"\n", | |
"2x Diving Gryphon, 1x Tracking, 1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 6.736 ± 3.468, median is 5\n", | |
" Turns till Zixor Prime is summoned: 12.237 ± 4.973, median is 10\n", | |
"\n", | |
"2x Diving Gryphon, 2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 6.308 ± 2.816, median is 5\n", | |
" Turns till Zixor Prime is summoned: 11.503 ± 4.552, median is 9\n", | |
"\n", | |
"1x Diving Gryphon, 2x Tracking, 1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 7.564 ± 4.463, median is 5\n", | |
" Turns till Zixor Prime is summoned: 13.807 ± 5.629, median is 13\n", | |
"\n", | |
"1x Diving Gryphon, 1x Tracking, 2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 6.787 ± 3.503, median is 5\n", | |
" Turns till Zixor Prime is summoned: 12.432 ± 5.068, median is 11\n", | |
"\n", | |
"2x Tracking, 2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 7.682 ± 4.494, median is 5\n", | |
" Turns till Zixor Prime is summoned: 13.671 ± 5.631, median is 12\n", | |
"\n", | |
"2x Diving Gryphon, 2x Tracking, 1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 6.679 ± 3.458, median is 5\n", | |
" Turns till Zixor Prime is summoned: 11.939 ± 4.860, median is 10\n", | |
"\n", | |
"2x Diving Gryphon, 1x Tracking, 2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 6.257 ± 2.756, median is 5\n", | |
" Turns till Zixor Prime is summoned: 11.234 ± 4.423, median is 9\n", | |
"\n", | |
"1x Diving Gryphon, 2x Tracking, 2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 6.699 ± 3.448, median is 5\n", | |
" Turns till Zixor Prime is summoned: 12.156 ± 4.956, median is 10\n", | |
"\n", | |
"2x Diving Gryphon, 2x Tracking, 2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 6.239 ± 2.775, median is 5\n", | |
" Turns till Zixor Prime is summoned: 10.937 ± 4.223, median is 8\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"import collections\n", | |
"\n", | |
"results = []\n", | |
"\n", | |
"class SnowflipperPenguin(Card, BeastMixin):\n", | |
" \"\"\"This acts as a beast filler. The point of adding beasts to a deck is\n", | |
" to dampen the effect of Scavenger's Ingenuity.\n", | |
" \n", | |
" \"\"\"\n", | |
" name = 'Snowflipper Penguin'\n", | |
" mana = 0\n", | |
"\n", | |
"def analyze(n_beasts):\n", | |
"\n", | |
" for combo in combos:\n", | |
"\n", | |
" counts = collections.Counter(combo)\n", | |
" print(', '.join([f'{count}x {card.name}' for card, count in counts.items()]) or 'Pure draw')\n", | |
"\n", | |
" # Build the deck\n", | |
" deck = [Zixor] + list(combo) + [SnowflipperPenguin] * n_beasts\n", | |
" deck += [Wisp] * (30 - len(deck))\n", | |
"\n", | |
" # Zixor\n", | |
"\n", | |
" def simulate(rng=random):\n", | |
" deck_copy = deck[:]\n", | |
" rng.shuffle(deck_copy)\n", | |
" turn, _, _ = look_for_zixor(deck_copy, rng=rng)\n", | |
" return turn\n", | |
"\n", | |
" sims = [simulate() for _ in range(20_000)]\n", | |
" avg = stats.mean(sims)\n", | |
" std = stats.stdev(sims)\n", | |
" med = stats.median(sims)\n", | |
" print(f' Turns till Zixor deathrattles: {avg:.3f} ± {std:.3f}, median is {int(med)}')\n", | |
"\n", | |
" # Zixor Prime\n", | |
"\n", | |
" def simulate(rng=random):\n", | |
" deck_copy = deck[:]\n", | |
" rng.shuffle(deck_copy)\n", | |
" turn, _, _ = look_for_zixor_prime(deck_copy, rng=rng)\n", | |
" return turn\n", | |
"\n", | |
" sims = [simulate() for _ in range(20_000)]\n", | |
" avg = stats.mean(sims)\n", | |
" std = stats.stdev(sims)\n", | |
" med = stats.median(sims)\n", | |
" print(f' Turns till Zixor Prime is summoned: {avg:.3f} ± {std:.3f}, median is {int(med)}')\n", | |
"\n", | |
" print()\n", | |
" \n", | |
"analyze(n_beasts=0)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"With 2 copies of all 3 draw cards, the median number of turns to reach Zixor Prime is 8, which is exactly it's cost.\n", | |
"\n", | |
"Now let's try with 4 additional beasts in the deck." | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 17, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"Pure draw\n", | |
" Turns till Zixor deathrattles: 13.668 ± 8.286, median is 12\n", | |
" Turns till Zixor Prime is summoned: 21.627 ± 6.331, median is 23\n", | |
"\n", | |
"1x Diving Gryphon\n", | |
" Turns till Zixor deathrattles: 9.455 ± 6.005, median is 7\n", | |
" Turns till Zixor Prime is summoned: 17.025 ± 6.384, median is 17\n", | |
"\n", | |
"1x Tracking\n", | |
" Turns till Zixor deathrattles: 13.706 ± 8.245, median is 13\n", | |
" Turns till Zixor Prime is summoned: 21.526 ± 6.367, median is 23\n", | |
"\n", | |
"1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 12.279 ± 7.563, median is 11\n", | |
" Turns till Zixor Prime is summoned: 19.832 ± 6.273, median is 21\n", | |
"\n", | |
"2x Diving Gryphon\n", | |
" Turns till Zixor deathrattles: 7.820 ± 4.593, median is 6\n", | |
" Turns till Zixor Prime is summoned: 14.381 ± 5.807, median is 13\n", | |
"\n", | |
"1x Diving Gryphon, 1x Tracking\n", | |
" Turns till Zixor deathrattles: 9.449 ± 6.026, median is 7\n", | |
" Turns till Zixor Prime is summoned: 16.710 ± 6.369, median is 17\n", | |
"\n", | |
"1x Diving Gryphon, 1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 9.528 ± 6.085, median is 7\n", | |
" Turns till Zixor Prime is summoned: 16.705 ± 6.200, median is 17\n", | |
"\n", | |
"2x Tracking\n", | |
" Turns till Zixor deathrattles: 13.524 ± 8.264, median is 12\n", | |
" Turns till Zixor Prime is summoned: 21.203 ± 6.360, median is 23\n", | |
"\n", | |
"1x Tracking, 1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 12.129 ± 7.496, median is 10\n", | |
" Turns till Zixor Prime is summoned: 19.419 ± 6.275, median is 20\n", | |
"\n", | |
"2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 11.219 ± 6.856, median is 9\n", | |
" Turns till Zixor Prime is summoned: 18.268 ± 6.113, median is 19\n", | |
"\n", | |
"2x Diving Gryphon, 1x Tracking\n", | |
" Turns till Zixor deathrattles: 7.739 ± 4.512, median is 6\n", | |
" Turns till Zixor Prime is summoned: 14.047 ± 5.728, median is 13\n", | |
"\n", | |
"2x Diving Gryphon, 1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 8.119 ± 4.926, median is 6\n", | |
" Turns till Zixor Prime is summoned: 14.379 ± 5.714, median is 14\n", | |
"\n", | |
"1x Diving Gryphon, 2x Tracking\n", | |
" Turns till Zixor deathrattles: 9.289 ± 5.944, median is 7\n", | |
" Turns till Zixor Prime is summoned: 16.377 ± 6.309, median is 16\n", | |
"\n", | |
"1x Diving Gryphon, 1x Tracking, 1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 9.467 ± 6.039, median is 7\n", | |
" Turns till Zixor Prime is summoned: 16.231 ± 6.109, median is 16\n", | |
"\n", | |
"1x Diving Gryphon, 2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 9.291 ± 5.825, median is 7\n", | |
" Turns till Zixor Prime is summoned: 15.851 ± 5.839, median is 16\n", | |
"\n", | |
"2x Tracking, 1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 11.926 ± 7.437, median is 10\n", | |
" Turns till Zixor Prime is summoned: 19.017 ± 6.310, median is 20\n", | |
"\n", | |
"1x Tracking, 2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 11.022 ± 6.795, median is 9\n", | |
" Turns till Zixor Prime is summoned: 17.776 ± 6.031, median is 18\n", | |
"\n", | |
"2x Diving Gryphon, 2x Tracking\n", | |
" Turns till Zixor deathrattles: 7.676 ± 4.488, median is 5\n", | |
" Turns till Zixor Prime is summoned: 13.644 ± 5.656, median is 12\n", | |
"\n", | |
"2x Diving Gryphon, 1x Tracking, 1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 8.046 ± 4.822, median is 6\n", | |
" Turns till Zixor Prime is summoned: 14.015 ± 5.585, median is 13\n", | |
"\n", | |
"2x Diving Gryphon, 2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 8.101 ± 4.820, median is 6\n", | |
" Turns till Zixor Prime is summoned: 13.959 ± 5.394, median is 13\n", | |
"\n", | |
"1x Diving Gryphon, 2x Tracking, 1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 9.257 ± 5.863, median is 7\n", | |
" Turns till Zixor Prime is summoned: 15.764 ± 6.029, median is 16\n", | |
"\n", | |
"1x Diving Gryphon, 1x Tracking, 2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 9.078 ± 5.626, median is 6\n", | |
" Turns till Zixor Prime is summoned: 15.392 ± 5.740, median is 15\n", | |
"\n", | |
"2x Tracking, 2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 10.637 ± 6.572, median is 8\n", | |
" Turns till Zixor Prime is summoned: 17.260 ± 6.013, median is 18\n", | |
"\n", | |
"2x Diving Gryphon, 2x Tracking, 1x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 7.770 ± 4.609, median is 6\n", | |
" Turns till Zixor Prime is summoned: 13.557 ± 5.450, median is 12\n", | |
"\n", | |
"2x Diving Gryphon, 1x Tracking, 2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 7.871 ± 4.640, median is 6\n", | |
" Turns till Zixor Prime is summoned: 13.561 ± 5.295, median is 12\n", | |
"\n", | |
"1x Diving Gryphon, 2x Tracking, 2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 8.839 ± 5.475, median is 6\n", | |
" Turns till Zixor Prime is summoned: 14.925 ± 5.659, median is 15\n", | |
"\n", | |
"2x Diving Gryphon, 2x Tracking, 2x Scavenger's Ingenuity\n", | |
" Turns till Zixor deathrattles: 7.701 ± 4.469, median is 6\n", | |
" Turns till Zixor Prime is summoned: 13.116 ± 5.097, median is 12\n", | |
"\n" | |
] | |
} | |
], | |
"source": [ | |
"analyze(n_beasts=4)" | |
] | |
}, | |
{ | |
"cell_type": "markdown", | |
"metadata": {}, | |
"source": [ | |
"We can also see how the number of beasts in the deck affects the median of the best combination. " | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": 18, | |
"metadata": {}, | |
"outputs": [ | |
{ | |
"name": "stdout", | |
"output_type": "stream", | |
"text": [ | |
"0: Turns till Zixor Prime is summoned: 10.907 ± 4.197, median is 8\n", | |
"1: Turns till Zixor Prime is summoned: 11.687 ± 4.627, median is 9\n", | |
"2: Turns till Zixor Prime is summoned: 12.307 ± 4.875, median is 10\n", | |
"3: Turns till Zixor Prime is summoned: 12.779 ± 5.043, median is 11\n", | |
"4: Turns till Zixor Prime is summoned: 13.094 ± 5.120, median is 12\n", | |
"5: Turns till Zixor Prime is summoned: 13.350 ± 5.199, median is 12\n", | |
"6: Turns till Zixor Prime is summoned: 13.513 ± 5.203, median is 12\n", | |
"7: Turns till Zixor Prime is summoned: 13.721 ± 5.236, median is 13\n", | |
"8: Turns till Zixor Prime is summoned: 13.869 ± 5.267, median is 13\n", | |
"9: Turns till Zixor Prime is summoned: 14.065 ± 5.301, median is 13\n", | |
"10: Turns till Zixor Prime is summoned: 14.024 ± 5.283, median is 13\n", | |
"11: Turns till Zixor Prime is summoned: 14.221 ± 5.334, median is 14\n", | |
"12: Turns till Zixor Prime is summoned: 14.262 ± 5.336, median is 14\n", | |
"13: Turns till Zixor Prime is summoned: 14.264 ± 5.302, median is 14\n", | |
"14: Turns till Zixor Prime is summoned: 14.423 ± 5.345, median is 14\n" | |
] | |
} | |
], | |
"source": [ | |
"for n_beasts in range(15):\n", | |
" \n", | |
" # Build the deck\n", | |
" combo = 2 * [DivingGryphon] + 2 * [Tracking] + 2 * [ScavengersIngenuity]\n", | |
" deck = [Zixor] + list(combo) + [SnowflipperPenguin] * n_beasts\n", | |
" deck += [Wisp] * (30 - len(deck))\n", | |
" \n", | |
" def simulate(rng=random):\n", | |
" deck_copy = deck[:]\n", | |
" rng.shuffle(deck_copy)\n", | |
" turn, _, _ = look_for_zixor_prime(deck_copy, rng=rng)\n", | |
" return turn\n", | |
"\n", | |
" sims = [simulate() for _ in range(20_000)]\n", | |
" avg = stats.mean(sims)\n", | |
" std = stats.stdev(sims)\n", | |
" med = stats.median(sims)\n", | |
" print(f'{n_beasts}: Turns till Zixor Prime is summoned: {avg:.3f} ± {std:.3f}, median is {int(med)}')" | |
] | |
}, | |
{ | |
"cell_type": "code", | |
"execution_count": null, | |
"metadata": {}, | |
"outputs": [], | |
"source": [] | |
} | |
], | |
"metadata": { | |
"kernelspec": { | |
"display_name": "Python 3", | |
"language": "python", | |
"name": "python3" | |
}, | |
"language_info": { | |
"codemirror_mode": { | |
"name": "ipython", | |
"version": 3 | |
}, | |
"file_extension": ".py", | |
"mimetype": "text/x-python", | |
"name": "python", | |
"nbconvert_exporter": "python", | |
"pygments_lexer": "ipython3", | |
"version": "3.7.4" | |
} | |
}, | |
"nbformat": 4, | |
"nbformat_minor": 4 | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment