Skip to content

Ocean of Math

  • Ocean Regions
    • Continental Shelf
    • Continental Slope
    • Abyss
  • Archipelagos
    • Numerics
    • Magic: The Gathering
  • About
  • Home
  • Mulligans in the Bazaar

Mulligans in the Bazaar

Posted on January 8, 2023January 8, 2023 By MathFish
Continental Shelf, Magic: The Gathering

Several competitive decks in Vintage Magic: The Gathering are powered by the card Bazaar of Baghdad.

The key card

Deck construction rules mandate at least 60 total cards in a deck with at most four copies of any given card. The game starts by each player drawing seven cards then performing, if he wishes, a series of “mulligans” in which the current hand is reshuffled and a new hand drawn until a desirable hand is drawn (there are reasons related to the nature of the game for why mulligans are allowed, but these are not relevant to this post). After keeping a hand, the player puts a card on the bottom of his deck for each mulligan taken. So a player can opt to keep taking mulligans until a specific card happens to be drawn, and it so happens that decks featuring Bazaar of Baghdad tend to be so reliant on that card that players will do exactly that. Now, there is an odd duck of a card, Serum Powder, that such decks also play:

Unique weirdness

To “exile” cards means to remove them from the game, so they do not get shuffled back into the deck before drawing a new hand. Serum Powder thus increases the probability of finding a Bazaar by removing non-Bazaar cards from the deck with each use. Accounting for Serum Powders, the whole process for beginning the game is as follows:

  1. Set $H=7$, which is the number of cards that will be kept from among the drawn cards.
  2. Draw 7 cards
  3. Decide which $H$ cards to keep (keep any Bazaars, since that is the point) and put the others on the bottom of the deck
  4. If the hand has Bazaar, stop. Otherwise…
  5. If the hand has Serum Powder, exile the $H$ cards kept, then draw $H$ cards and go to step 4. Otherwise…
  6. Decrement $H$ by one and go to step 2

Players are naturally interested in the probability of eventually finding a hand with at least one Bazaar, and in the expected number of cards in the hand that is finally kept. Finding these numbers is a complicated process because:

  • The probability of finding a Bazaar depends on how many cards have been removed by Serum Powders, which depends on how late in the process each Serum Powder was drawn
  • If a Bazaar-less hand has multiple Serum Powders, they all get removed

The flowchart sketched below shows the relevant branches of the mulligan process:

Flowchart for mulligans to Bazaar. Forgive its slapdash nature – graphic design isn’t my day job.

Branches have been labeled with probabilities, with the usual notation for binomial coefficients:

$$\displaystyle \left(\begin{matrix}a \\ b\end{matrix}\right) = \frac{a!}{b!(a-b)!} $$

All the probability expressions involve subtractions of 4, because we are considering hands that do not have Bazaar (that’s why we’re taking mulligans in the first place), and we assume the deck has the maximum allowed four Bazaars in it.

The big loop in the flowchart is repeated until either a hand with Bazaar is drawn or a minimum hand size is reached. This minimum size is usually 1, because if you mulligan down to one card your only options are to mulligan again to zero cards (stupidly bad) or keep the one card and pray (slightly less bad). Rather than figure out by hand all the possible branches one could take on these passes through the loop, here’s a Python program that does the work for you:

#!/usr/bin/env python

from math import comb as nck

class State:
    def __init__(self, cards, powders, draws, handsize, probability):
        self.N = cards
        self.P = powders
        self.D = draws
        self.H = handsize
        self.probability = probability


    # Form new states from this one for each possible number of Serum Powders
    # in the hand represented by this state, and calculate probabilities for
    # each.
    def fork(self, minhandsize):
        p0bazaars = nck(self.N-4, self.D)/nck(self.N, self.D)
        pbazaar = 1.0 - p0bazaars

        if (self.H == minhandsize):
            return pbazaar, []

        denom = nck(self.N-4, self.D)

        newstates = []
        for npowders in range(min(self.P, self.D)+1):
            # The -4 here is for the four Bazaars that are in the deck but not
            # in the hand, since we are assuming this is a hand we will mulligan.
            prob = p0bazaars * nck(self.P,npowders)*nck(self.N-4-self.P, self.D-npowders)/denom
            if (npowders == 0):
                # Natural mulligan
                newN = self.N  # No cards removed from the deck
                newP = self.P  # Number of Powders in the deck does not change
                newD = 7  # Will draw 7 new cards
                newH = self.H - 1  # Hand size reduced by 1
            else:
                # Powder redraw
                newN -= self.D  # Cards drawn are removed from the deck
                newP = self.P - npowders  # All powders in this hand are removed from the deck
                newD = self.H  # Will draw cards equal to the number of cards currently in hand
                newH = self.H  # Hand size is not altered

            newstates.append(State(newN, newP, newD, newH, prob*self.probability))

        return pbazaar*self.probability, newstates

# For a given number of Serum Powders and a minimum hand size below which you
# will not mulligan, get the probabilities of keeping hands of each size,
# assuming you mulligan strictly to Bazaar.
def get_probabilities(npowders, minhandsize):
    pbazaar_vs_handsize = [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
    # Set up the state for the first draw
    start = State(cards=60, powders=npowders, draws=7, handsize=7, probability=1)

    states = [start]
    # Fork states until all states end in either a hand with Bazaar or a hand
    # of the minimum hand size.
    while states:
        newstates = []
        for s in states:
            pbazaar, forkedstates = s.fork(minhandsize)
            pbazaar_vs_handsize[s.H] += pbazaar
            newstates.extend(forkedstates)
        states = newstates

    return pbazaar_vs_handsize

if (__name__ == "__main__"):
    # Table column headers
    print('| Powders | %6d | %6d | %6d | %6d | %6d | %6d | %6d | Avg hand size |' % (1,2,3,4,5,6,7))

    for npowders in range(5):
        pbazaar_vs_handsize = get_probabilities(npowders, minhandsize=1)
        pbazaar_vs_handsize[1] = 1.0 - sum(pbazaar_vs_handsize[2:])

        expected_handsize = 0.0
        for k,p in enumerate(pbazaar_vs_handsize):
            expected_handsize += k*p
        print('| %7d | %1.4f | %1.4f | %1.4f | %1.4f | %1.4f | %1.4f | %1.4f | %1.4f |' % (npowders, *pbazaar_vs_handsize[1:], expected_handsize))

Which produces the following table for the probabilities of finding Bazaar in a hand of given size, and the average number of cards in the kept hands, for each possible number of Serum Powders.

PowdersP(keep 1)P(keep 2)P(keep 3)P(keep 4)P(keep 5)P(keep 6)P(keep 7)Average hand size
00.04690.03120.05190.08650.14410.23990.39955.5674
10.03550.02590.04510.07870.13820.24380.43275.7207
20.02580.02080.03810.07030.13080.24600.46815.8698
30.01790.01610.03130.06130.12190.24610.50556.0134
40.01170.01190.02460.05190.11130.24370.54506.1503
Probabilities of keeping hands of each size and expected hand sizes vs. number of Serum Powders

Post navigation

❮ Previous Post: Snowpile Math
Next Post: All Finite-Difference Formulas for All Derivatives ❯

Recent Posts

  • Polytope Identities from Vector Calculus
  • The Exponential and Logarithm for Dual Quaternions
  • What is the Basic Kinematic Equation?
  • Numbers With Nothing In Common
  • Magnetism from Relativity

Recent Comments

    Categories

    • Abyss
    • Continental Shelf
    • Continental Slope
    • Magic: The Gathering
    • Numerics
    • Physics
    • Uncategorized
    • What is…

    Copyright © 2025 Ocean of Math.

    Theme: Oceanly by ScriptsTown