Latest Legends Pulling Simulator

kbmartinkbmartin Age Unconfirmed Posts: 13 Just Dropped In
edited February 2017 in Theories and Statistics
Update: Code can now accomodate people who only want two fully-covered, and don't care which two. That scenario is more of an improvement, over three, than I originally suggested.

Update 2017-02-26: I found a way to make the code run 4-5 times faster by having the simulation check to see if toons are fully covered only when they increment. Also changed Natasha to Panther. New code is below.

Hey everyone, I wanted to see how many Latest Legends I would have to pull to fully cover all the new 5*. SO I wrote some Python code to simulate pulling latest legends. A Monte Carlo simulation with 100,000 runs yields rather stable results.

The code tells you how many latest legends you need to pull, to have any given percentage chance of getting fully covered five-stars. It gives you results for the current situation, where customer service is swapping duplicate-color (>5) Latest Legends covers for a different color of the same toon; it also gives results for the future, when, I gather, such swaps will no longer be available.

Here are some examples of the code in action. Caveat: I'm an amateur and I very easily could have screwed up something and the numbers could all be wrong. If you see an error in my code, please do tell me! OK? Good.

If you have NO latest legends covers and want to fully cover all three five-stars:
50%: with swaps: 315, without swaps: 377
55%: with swaps: 323, without swaps: 388
60%: with swaps: 331, without swaps: 398
65%: with swaps: 339, without swaps: 410
70%: with swaps: 348, without swaps: 423
75%: with swaps: 358, without swaps: 438
80%: with swaps: 369, without swaps: 456
85%: with swaps: 383, without swaps: 478
90%: with swaps: 402, without swaps: 508
95%: with swaps: 431, without swaps: 556

By the way, if you're really unlucky (not unlucky though, ha ha), there is around a 0.1% chance that you'll have to pull more than 600 times to fully cover with customer service swaps, or more than 800 times without the swaps. I've seen the odd simulation go 1100 pulls. Random chance can be brutal.

If you have no latest legends, but only care about getting two five-stars fully covered, and you don't care which two:
50%: with swaps: 253, without swaps: 295
55%: with swaps: 258, without swaps: 302
60%: with swaps: 264, without swaps: 310
65%: with swaps: 270, without swaps: 317
70%: with swaps: 277, without swaps: 326
75%: with swaps: 284, without swaps: 335
80%: with swaps: 292, without swaps: 347
85%: with swaps: 302, without swaps: 360
90%: with swaps: 314, without swaps: 377
95%: with swaps: 334, without swaps: 405

Hmm. That's a significant, although not overwhelming, improvement. Could save you 80-100 pulls. (As it happens, if you want a specific two, it only saves you twenty pulls on average, so you may as well go for all three.)

Let's say you're me. I already have a 0/2/0 Natasha and a 0/0/1 Thanos, and I want to hoard until you can pull LTs and have all three latest fully-covered.
50%: with swaps: 295, without swaps: 359
55%: with swaps: 302, without swaps: 369
60%: with swaps: 310, without swaps: 380
65%: with swaps: 318, without swaps: 391
70%: with swaps: 326, without swaps: 405
75%: with swaps: 336, without swaps: 420
80%: with swaps: 348, without swaps: 437
85%: with swaps: 362, without swaps: 458
90%: with swaps: 380, without swaps: 488
95%: with swaps: 408, without swaps: 539

Let's say you're ._-KENSH-_. (!!!) A much better situation. You already have a 4/7/2 Natasha, a 4/6/1 Strange and a 1/4/2 Thanos. Here's what you need to do to fully cover that Thanos:
50%: with swaps: 114, without swaps: 186
55%: with swaps: 120, without swaps: 195
60%: with swaps: 126, without swaps: 204
65%: with swaps: 133, without swaps: 215
70%: with swaps: 140, without swaps: 227
75%: with swaps: 148, without swaps: 240
80%: with swaps: 157, without swaps: 256
85%: with swaps: 168, without swaps: 276
90%: with swaps: 183, without swaps: 303
95%: with swaps: 206, without swaps: 349

The Python (3.6) code follows. I know there are some inefficiencies (fewer since the most recent update!. I also know that a real programmer could probably make this all into an awesome online calculator, provide graphical analysis of the data, etc., etc. Maybe someone on the forums has already done that and this was, "a waste of time" (although it was a labor of love and worth it in its own right). I only taught myself Python over the last two weeks. Before that I hadn't written anything in ten years, since I was writing stuff for fun in C++. Programming is not my day job. So go easy on me. Please attribute me if you share or improve. And, enjoy.

A commander at MPQU
"""copyright Kevin Martin 2017-02-24"""
"""Simulate pulling Latest Legends covers from MPQ toons until you have
   fully-covered Latest Legends."""

simsize = 100000

cutoffs_range = range(50, 100, 5)
"""50% - 95% by 5% increments"""

number_I_want = 3
"""change this to 1 or 2 if you only need one or two fully-covered"""

from random import randrange, seed

class Toon:

    """Defines the number of each of three covers you have in an MPQ toon"""

    def check_coverage(self):
        """change coverage from False to True if covered"""
        if self.covered == False:
            sortedtoon = sorted(self.coverage)
            if sortedtoon[0] == 3:
                self.covered = all((sortedtoon[1] >= 5, sortedtoon[2] >= 5))
            elif sortedtoon[0] > 3:
                self.covered = all((sortedtoon[1] >= 4, sortedtoon[2] >= 5))

    def check_coverage_with_swaps(self):
        if self.covered_with_swaps == False:
             self.covered_with_swaps = (self.coverage[0]
                                        + self.coverage[1]
                                        + self.coverage[2]
                                        >= 13)
    def __init__(self, coverage):
        self.coverage = coverage
        self.covered = False
        self.covered_with_swaps = False

    def increment(self):
        """Adds a random cover to a toon and check coverage status"""
        color = randrange(3)
        self.coverage[color] = self.coverage[color] + 1
noswap_tally = []
swap_tally = []

"""Run the simulation, simsize number of times"""
for sim in range(simsize):

    """initialize this particular simulation"""
    pullcount = 0
    all_covered_with_swaps = False

    """For baseline scenario:"""
    Panther = Toon([0, 0, 0])
    Strange = Toon([0, 0, 0])
    Thanos = Toon([0, 0, 0])

    """Recent simulations I've been asked to run."""

    """Simulation for RemoDestroyer
    Panther = Toon([0, 0, 0])
    Strange = Toon([2, 2, 3])
    Thanos = Toon([2, 1, 1])"""
    """Run the simulation until all toons are fully covered,
       assuming no customer service swaps."""
    while (Panther.covered
        + Strange.covered
        + Thanos.covered
        < number_I_want):
        pull = randrange(20)
        """5% chance of pulling each of the latest legends."""
        if pull == 0:
        elif pull == 1:
        elif pull == 2:

        """If customer service is still swapping duplicate covers,
           note that the simulation would have ended here and add
           how many pulls that took to the swap tally"""
        if not all_covered_with_swaps:
            if (Panther.covered_with_swaps
               + Strange.covered_with_swaps 
               + Thanos.covered_with_swaps
               >= number_I_want):

                all_covered_with_swaps = True

        pullcount = pullcount + 1;

    """progress bar"""
    if (sim % 1000) == 0:
        print('Sims: {:d} / {:d}'.format(sim, simsize))

    """the simulation finished, add how many pulls it took to the noswap

"""sort the tallies of simulations with a given number of
   pulls needed for full coverage, from fewest to most"""

"""dictionaries of how many pulls it took to reach
   given cutoff percentages of simulations"""
swap_cutoff_tally = dict()
noswap_cutoff_tally = dict()

swap_currenttotal, noswap_currenttotal = 0, 0

"""print a list of all numbers of pulls,
   where a simulation reached full coverage"""
for num_pulls in range(swap_tally[0], noswap_tally[len(noswap_tally)-1]+1):

    """set tally counts to the number of simulations that took
       this number of pulls to complete"""
    swap_tallycount = swap_tally.count(num_pulls)
    noswap_tallycount = noswap_tally.count(num_pulls)

    """increment current total number of simulations reviewed"""
    new_swap_currenttotal = swap_currenttotal + swap_tallycount
    new_noswap_currenttotal = noswap_currenttotal + noswap_tallycount

    """if we've crossed a cutoff, record it"""
    for cutoff in cutoffs_range:
        if all((swap_currenttotal/simsize < cutoff / 100,
                new_swap_currenttotal/simsize >= cutoff / 100)):
            swap_cutoff_tally.setdefault(cutoff, num_pulls)
        if all((noswap_currenttotal/simsize < cutoff / 100,
                new_noswap_currenttotal/simsize >= cutoff / 100)):
            noswap_cutoff_tally.setdefault(cutoff, num_pulls) 

    """finalize increments"""
    swap_currenttotal = new_swap_currenttotal
    noswap_currenttotal = new_noswap_currenttotal

    """print raw data"""
    if any ((swap_tallycount, noswap_tallycount)):
        print('{:4d}: With swaps: {:3d} {:4.1%},  No swaps: {:3d} {:4.1%}'
                      swap_tallycount, swap_currenttotal/simsize,
                      noswap_tallycount, noswap_currenttotal/simsize))

"""print summary"""
for cutoffs in cutoffs_range:
    print('{:3.0%}:with swaps:{:3d}, without:{:3d}'
          .format(cutoffs / 100,


  • kbmartinkbmartin Age Unconfirmed Posts: 13 Just Dropped In
    edited January 2017
    Update: This error has been fixed by new code.

    First error discovered -- I simulated pulling until fully covered when one of the three is already fully covered and assumed that was the same as "how long does it take to cover two.". But it's not, because, combinatorics. Will fix and update
  • Mr_SinisterMr_Sinister Age Unconfirmed Posts: 356 Mover and Shaker
    Tl;dr version- "5* champs will cost you lots and lots (and lots) of money to get".
  • kbmartinkbmartin Age Unconfirmed Posts: 13 Just Dropped In
    Tl;dr version- "5* champs will cost you lots and lots (and lots) of money to get".

    Or, in my case, time.
  • Mr_SinisterMr_Sinister Age Unconfirmed Posts: 356 Mover and Shaker
    kbmartin wrote:
    Tl;dr version- "5* champs will cost you lots and lots (and lots) of money to get".

    Or, in my case, time.

    Or crazy amounts of time that the game may or may not have.

    Not dissuading you from that, just remarking in the difficulty of actually getting a covered 5*, never mind 2.
  • Team_JacobTeam_Jacob Posts: 90 Match Maker
    What if you have a 2/1/1 Natasha, a 2/2/2 Thanos and a 0/2/2 Strange? What if you were to wait until the next release?
  • kbmartinkbmartin Age Unconfirmed Posts: 13 Just Dropped In
    50%: with swaps: 214, without swaps: 265
    55%: with swaps: 220, without swaps: 274
    60%: with swaps: 227, without swaps: 284
    65%: with swaps: 234, without swaps: 294
    70%: with swaps: 242, without swaps: 306
    75%: with swaps: 250, without swaps: 319
    80%: with swaps: 261, without swaps: 335
    85%: with swaps: 273, without swaps: 355
    90%: with swaps: 289, without swaps: 383
    95%: with swaps: 314, without swaps: 428

    After Natasha goes to the graveyard, assuming you're 0/0/0 on the new 5* (which is not necessarily a safe assumption, you know):
    50%: with swaps: 265, without swaps: 319
    55%: with swaps: 272, without swaps: 330
    60%: with swaps: 280, without swaps: 341
    65%: with swaps: 289, without swaps: 353
    70%: with swaps: 298, without swaps: 367
    75%: with swaps: 309, without swaps: 382
    80%: with swaps: 321, without swaps: 399
    85%: with swaps: 337, without swaps: 421
    90%: with swaps: 357, without swaps: 451
    95%: with swaps: 387, without swaps: 502
  • RemoDestroyerRemoDestroyer Posts: 277 Mover and Shaker
    What if you have a 2/1/1 Thanos and a 2/2/3 Strange and 0 covers in Black Panther and you were to wait until Natasha is in the graveyard? How many pulls to cover two of three?
  • kbmartinkbmartin Age Unconfirmed Posts: 13 Just Dropped In
    First, note I found a way to make the code run 4-5 times faster by having the simulation check to see if toons are fully covered only when they increment. Code will be updated shortly in original post.
    What if you have a 2/1/1 Thanos and a 2/2/3 Strange and 0 covers in Black Panther and you were to wait until Natasha is in the graveyard? How many pulls to cover two of three?

    To cover all three:
    50%:with swaps:262, without:312
    55%:with swaps:270, without:323
    60%:with swaps:279, without:334
    65%:with swaps:288, without:346
    70%:with swaps:297, without:358
    75%:with swaps:308, without:373
    80%:with swaps:320, without:390
    85%:with swaps:336, without:412
    90%:with swaps:355, without:441
    95%:with swaps:387, without:490

    To cover just two:
    50%:with swaps:175, without:207
    55%:with swaps:180, without:214
    60%:with swaps:186, without:221
    65%:with swaps:192, without:229
    70%:with swaps:199, without:237
    75%:with swaps:206, without:246
    80%:with swaps:214, without:257
    85%:with swaps:224, without:270
    90%:with swaps:236, without:287
    95%:with swaps:256, without:314

    Hope this helps - Data
  • RemoDestroyerRemoDestroyer Posts: 277 Mover and Shaker
    Awesome. Thanks. I'll update if I can actually get one or both covered.

    So far in 56 pulls I got 3 Thanos and 1 Strange.

    Averaged 2.25 pulls a day in February so I'm going to need a lot of luck to beat the odds.
  • trat73trat73 Posts: 72 Match Maker
    Is there any data on the amount of pulls needed to cover only 1 5*? I know I won't have enough tokens to cover two, but I'm wondering at my chances to champ my 2/2/2 Thanos before he leaves Latest
Sign In or Register to comment.