538’s Monsters and Gems Puzzle

538 has a weekly riddler post, and I thought I would try this week’s riddle:

A video game requires you to slay monsters to collect gems. Every time you slay a monster, it drops one of three types of gems: a common gem, an uncommon gem or a rare gem. The probabilities of these gems being dropped are in the ratio of 3:2:1 — three common gems for every two uncommon gems for every one rare gem, on average. If you slay monsters until you have at least one of each of the three types of gems, how many of the most common gems will you end up with, on average?

Here is the answer I submitted:

I couldn’t figure out how to reason about this with the probability theory that I know, so I thought I would try to simulate it. I’ve been playing around with Python recently, so I wrote a program that simulates an arbitrary number of trials. Running this repeatedly with 100,000 trials, rounded averages were mostly consistent to 2 decimal places, although sometimes the last decimal place varied by one: 3.65 common gems, 2.43 uncommon gems, 1.22 rare gems, 7.30 monsters killed.

On average it should take 6 dead monsters to get a rare gem. By then, most of the time we would expect that both common and uncommon gems would have appeared. So it’s surprising to me that the average is as much higher than 6 as it is. Intuitively, I think part of what is going on is that because it always takes at least 3 dead monsters to get all 3 gem types, the cases where a rare gem is drawn early on aren’t able to “balance out” the cases where it takes a really long time, as would be the case in a simpler probability distribution. About 15% of the time, it takes 12 or more kills to get all three gem types. I’m no closer, though, to a mathematical derivation of the exact answer.

I have pasted below a sample output from the program, and below that the program itself.

number of trials: 100000
Average number of common gems: 3.64636
Average number of uncommon gems: 2.43568
Average number of rare gems: 1.21557
Average number of monsters killed: 7.29761
max monster count 56
1 dead monsters: 0 (0.000% | 0.000%)
2 dead monsters: 0 (0.000% | 0.000%)
3 dead monsters: 16744 (16.744% | 16.744%)
4 dead monsters: 16700 (16.700% | 33.444%)
5 dead monsters: 13746 (13.746% | 47.190%)
6 dead monsters: 10839 (10.839% | 58.029%)
7 dead monsters: 8446 (8.446% | 66.475%)
8 dead monsters: 6552 (6.552% | 73.027%)
9 dead monsters: 5176 (5.176% | 78.203%)
10 dead monsters: 3937 (3.937% | 82.140%)
11 dead monsters: 3241 (3.241% | 85.381%)
12 dead monsters: 2689 (2.689% | 88.070%)
13 dead monsters: 2104 (2.104% | 90.174%)
14 dead monsters: 1653 (1.653% | 91.827%)
15 dead monsters: 1376 (1.376% | 93.203%)
16 dead monsters: 1200 (1.200% | 94.403%)
17 dead monsters: 907 (0.907% | 95.310%)
18 dead monsters: 807 (0.807% | 96.117%)
19 dead monsters: 620 (0.620% | 96.737%)
20 dead monsters: 529 (0.529% | 97.266%)
21 dead monsters: 443 (0.443% | 97.709%)
22 dead monsters: 382 (0.382% | 98.091%)
23 dead monsters: 312 (0.312% | 98.403%)
24 dead monsters: 305 (0.305% | 98.708%)
25 dead monsters: 221 (0.221% | 98.929%)
26 dead monsters: 178 (0.178% | 99.107%)
27 dead monsters: 139 (0.139% | 99.246%)
28 dead monsters: 129 (0.129% | 99.375%)
29 dead monsters: 95 (0.095% | 99.470%)
30 dead monsters: 99 (0.099% | 99.569%)
31 dead monsters: 60 (0.060% | 99.629%)
32 dead monsters: 58 (0.058% | 99.687%)
33 dead monsters: 46 (0.046% | 99.733%)
34 dead monsters: 50 (0.050% | 99.783%)
35 dead monsters: 33 (0.033% | 99.816%)
36 dead monsters: 31 (0.031% | 99.847%)
37 dead monsters: 25 (0.025% | 99.872%)
38 dead monsters: 25 (0.025% | 99.897%)
39 dead monsters: 19 (0.019% | 99.916%)
40 dead monsters: 19 (0.019% | 99.935%)
41 dead monsters: 14 (0.014% | 99.949%)
42 dead monsters: 15 (0.015% | 99.964%)
43 dead monsters: 6 (0.006% | 99.970%)
44 dead monsters: 5 (0.005% | 99.975%)
45 dead monsters: 6 (0.006% | 99.981%)
46 dead monsters: 4 (0.004% | 99.985%)
47 dead monsters: 4 (0.004% | 99.989%)
48 dead monsters: 1 (0.001% | 99.990%)
49 dead monsters: 2 (0.002% | 99.992%)
50 dead monsters: 0 (0.000% | 99.992%)
51 dead monsters: 3 (0.003% | 99.995%)
52 dead monsters: 3 (0.003% | 99.998%)
53 dead monsters: 1 (0.001% | 99.999%)
54 dead monsters: 0 (0.000% | 99.999%)
55 dead monsters: 0 (0.000% | 99.999%)
56 dead monsters: 1 (0.001% | 100.000%)

Here is my python 3 code:

import random
commonCount=[]
uncommonCount=[]
rareCount=[]
monsterCount=[]
numTrials = 100000

maxMonsterCount = 0;

for i in range (0,numTrials):
    commonCount.append(0)
    uncommonCount.append(0)
    rareCount.append(0)
    monsterCount.append(0)

for i in range (0,numTrials):
    while (commonCount[i]==0 or uncommonCount[i]==0 or rareCount[i]==0):
        monsterCount[i] +=1
        if (monsterCount[i] > maxMonsterCount):
            maxMonsterCount = monsterCount[i]
        monster = random.randrange(1,7)
 #randrange syntax includes the first parameter, but excludes the second       
        if (monster==1 or monster==2 or monster==3):
            commonCount[i] += 1
        elif (monster==4 or monster==5):
            uncommonCount[i] += 1
        elif (monster==6):
            rareCount[i] += 1
        else:
            print("ERROR!")            
# Don't run the following lines unless numTrials is small ...
#    print ("i =",i)
#    print ("common:", commonCount[i])
#    print ("uncommon:", uncommonCount[i])
#    print ("rare:", rareCount[i])
#    print ("total:", monsterCount[i])

commonTotal = 0;
uncommonTotal = 0;
rareTotal = 0;
overallTotal = 0;

for i in range (0, numTrials):
    commonTotal += commonCount[i]
    uncommonTotal += uncommonCount[i]
    rareTotal += rareCount[i]
    overallTotal += monsterCount[i]

commonAverage = commonTotal/numTrials
uncommonAverage = uncommonTotal/numTrials
rareAverage = rareTotal/numTrials
totalAverage = overallTotal/numTrials
print ("number of trials:",numTrials)
print ("Average number of common gems:",commonAverage)
print ("Average number of uncommon gems:",uncommonAverage)
print ("Average number of rare gems:",rareAverage)
print ("Average number of monsters killed:", totalAverage)

print("max monster count", maxMonsterCount);

killDistribution=[]
for a in range (0,maxMonsterCount+2):
    killDistribution.append(0)
for i in range (0,numTrials):
    killDistribution[monsterCount[i]] += 1

cumulativePercentage = 0;
for a in range (1,maxMonsterCount+1):
#1 and 2 should always be zero, but I like to leave them in anyway.
    cumulativePercentage += killDistribution[a]/numTrials*100
    print ('{: d} dead monsters: {: d} ({:04.3f}% | {:04.3f}%)'.format(a,killDistribution[a],killDistribution[a]/numTrials*100,cumulativePercentage))