forked from frankkarsten/MTG-Math
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathArenaOpenAnalysis.py
354 lines (328 loc) · 12.7 KB
/
ArenaOpenAnalysis.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
import numpy as np
from matplotlib import pyplot as plt
#Let's define some values for Day 1. Keys represent bestof
max_wins = {1: 7, 3: 4}
max_losses = {1: 3, 3: 1}
#Keys are tuples of the form (bestof, wins)
Day_1_gems = {
(1, 0): 0,
(1, 1): 0,
(1, 2): 0,
(1, 3): 400,
(1, 4): 800,
(1, 5): 1200,
(1, 6): 1600,
(1, 7): 2000,
(3, 0): 0,
(3, 1): 1000,
(3, 2): 2500,
(3, 3): 5000,
(3, 4): 5000
}
def binom(n, k):
"""
Parameters:
n - Number of elements of the entire set
k - Number of elements in the subset
It should hold that 0 <= k <= n
Returns - The binomial coefficient n choose k that represents the number of ways of picking k unordered outcomes from n possibilities
"""
answer = 1
for i in range(1, min(k, n - k) + 1):
answer = answer * (n + 1 - i) / i
return int(answer)
def binom_prob(n, k, p):
"""
Parameters:
n - Number of independent trials
k - Number of successes
p - Probability of success in each trial
Returns - The binomial probability of getting exactly k successes
"""
return binom(n, k) * ( p ** k ) * ( ( 1- p ) ** ( n - k) )
def record_prob(p, Wmax, Lmax, W, L):
"""
Parameters:
p - The probability to win a round
Wmax, Lmax - You play an event until Wmax wins or Wmax losses, whichever comes first
W, L - The record you're interested in
Returns - The probability to end the event with W wins and L losses. It must hold that either Wmax = W or Lmax = L.
"""
if W < Wmax and L == Lmax:
#We first win W rounds and lose L - 1 rounds in any order, then lose the last round
return binom_prob(W + Lmax - 1, W, p) * (1 - p)
elif W == Wmax and L < Lmax:
#We first win W - 1 rounds and lose L rounds in any order, then win the last round
return (binom_prob(Wmax + L - 1, Wmax - 1, p) * p)
else:
#This isn't a feasible record
return 0
def match_win_prob(G):
"""
Parameters:
G - The game win rate
Returns - The probability to win a best-of-three match, assuming constant game win rate
"""
return G * G + 2 * G * G * (1 - G)
def qualification_prob(G, bestof):
"""
Parameters:
G - The game win rate
bestof - Either 1 or 3
Returns - The probability to earn a Day 2 qualification in an Arena Open
"""
round_win_prob = G if bestof == 1 else match_win_prob(G)
answer = 0
for losses in range(max_losses[bestof]):
answer += record_prob(round_win_prob, max_wins[bestof], max_losses[bestof], max_wins[bestof], losses)
return answer
#Determine and plot the probabilities to earn a Day 2 qualification
x_axis = np.arange(0.40, 0.701, 0.001)
y_axis = np.empty(301)
fig, ax = plt.subplots()
for bestof in [1,3]:
for x in range(301):
y_axis[x] = qualification_prob(0.40+x/1000, bestof)
ax.plot(x_axis, y_axis, label='best-of-'+str(bestof))
ax.set(xlabel='Game win rate', ylabel='Day 2 qualification probability',
title='Arena Open: Probability to earn a Day 2 qualification')
ax.set_xticklabels(['{:.0%}'.format(x) for x in ax.get_xticks()])
ax.set_yticklabels(['{:.0%}'.format(y) for y in ax.get_yticks()])
ax.grid(True)
plt.legend()
plt.xlim(.4,.7)
plt.ylim(0,.48)
fig.savefig("Day_2_qualification_probability.png")
print("\n")
print(f'Probability to qualify in a Bo1 event for a 50% win rate player: {qualification_prob(0.5,1) * 100: .1f}%')
print(f'Expected Bo1 events till qualification for a 50% win rate player: {1/qualification_prob(0.5,1) : .1f}')
print(f'Probability to qualify in a Bo3 event for a 50% win rate player: {qualification_prob(0.5,3) * 100: .1f}%')
print(f'Expected Bo1 events till qualification for a 50% win rate player: {1/qualification_prob(0.5,3) : .1f}')
def expected_games(G, outcome):
"""
Parameters:
G - The game win rate
outcome - 'win' or 'loss'
Returns - The expected number of games played in a best-of-three match with the given outcome
"""
if outcome == 'win':
prob_2_0 = G * G
prob_2_1 = 2 * G * G * (1 - G)
prob_match_win = prob_2_0 + prob_2_1
return ( 2 * prob_2_0 + 3 * prob_2_1 ) / prob_match_win
if outcome == 'loss':
prob_0_2 = (1 - G) * (1 - G)
prob_1_2 = 2 * G * (1 - G) * ( 1 - G)
prob_match_loss = prob_0_2 + prob_1_2
return ( 2 * prob_0_2 + 3 * prob_1_2 ) / prob_match_loss
def expected_games_event(G, bestof):
"""
Parameters:
G - The game win rate
bestof - Either 1 or 3
Returns - The expected number of games played in one event
"""
expectation = 0
W = max_wins[bestof]
success_prob = qualification_prob(G, bestof)
round_win_prob = G if bestof == 1 else match_win_prob(G)
games_per_round_win = 1 if bestof == 1 else expected_games(G, 'win')
games_per_round_loss = 1 if bestof == 1 else expected_games(G, 'loss')
for wins in range(max_wins[bestof]):
record_probability = record_prob(round_win_prob, max_wins[bestof], max_losses[bestof], wins, max_losses[bestof])
nr_games = wins * games_per_round_win + max_losses[bestof] * games_per_round_loss
expectation += record_probability * nr_games
for losses in range(max_losses[bestof]):
record_probability = record_prob(round_win_prob, max_wins[bestof], max_losses[bestof], max_wins[bestof], losses)
nr_games = max_wins[bestof] * games_per_round_win + losses * games_per_round_loss
expectation += record_probability * nr_games
return expectation
def expected_games_success(G, bestof):
"""
Parameters:
G - The game win rate
bestof - Either 1 or 3
Returns - The expected number of games played when you WIN the maximum number of rounds
"""
expectation = 0
W = max_wins[bestof]
success_prob = qualification_prob(G, bestof)
if bestof == 1:
for losses in range(max_losses[bestof]):
#Consider the probability of achieving this record, conditional on reaching the maximum number of wins
record_probability = record_prob(G, W, max_losses[bestof], W, losses) / success_prob
nr_games = W + losses
expectation += record_probability * nr_games
if bestof == 3:
for losses in range(max_losses[bestof]):
#Consider the probability of achieving this record, conditional on reaching the maximum number of wins
record_probability = record_prob(match_win_prob(G), W, max_losses[bestof], W, losses) / success_prob
nr_games = W * expected_games(G, 'win') + losses * expected_games(G, 'loss')
expectation += record_probability * nr_games
return expectation
def expected_games_failure(G, bestof):
"""
Parameters:
G - The game win rate
bestof - Either 1 or 3
Returns - The expected number of games played when you LOSE the maximum number of rounds
"""
expectation = 0
L = max_losses[bestof]
failure_prob = 1 - qualification_prob(G, bestof)
if bestof == 1:
for wins in range(max_wins[bestof]):
#Consider the probability of achieving this record, conditional on reaching the maximum number of losses
record_probability = record_prob(G, max_wins[bestof], L, wins, L) / failure_prob
nr_games = wins + L
expectation += record_probability * nr_games
if bestof == 3:
for wins in range(max_wins[bestof]):
#Consider the probability of achieving this record, conditional on reaching the maximum number of losses
record_probability = record_prob(match_win_prob(G), max_wins[bestof], L, wins, L) / failure_prob
nr_games = wins * expected_games(G, 'win') + L * expected_games(G, 'loss')
expectation += record_probability * nr_games
return expectation
def expected_games_until_qualification(G, bestof):
"""
Parameters:
G - The game win rate
bestof - Either 1 or 3
Returns - The expected number of games played until earning a Day 2 qualification
Denoting this quantity by E, it's derived by solving:
E = (exp_games_failure + E) * failure_prob + exp_games_success * success_prob
"""
success_prob = qualification_prob(G, bestof)
failure_prob = 1 - qualification_prob(G, bestof)
exp_games_failure = expected_games_failure(G, bestof) if G < 1.0 else 0
exp_games_success = expected_games_success(G, bestof)
return (failure_prob * exp_games_failure + success_prob * exp_games_success) / success_prob
print("\n")
print(f'Expected nr games per Bo1 event for a 50% win rate player: {expected_games_event(0.5,1): .1f}')
print(f'Expected nr games per Bo3 event for a 50% win rate player: {expected_games_event(0.5,3): .1f}')
#Determine and plot the expected number of games to earn a Day 2 qualification
x_axis = np.arange(0.40, 0.701, 0.001)
y_axis = np.empty(301)
fig, ax = plt.subplots()
for bestof in [1,3]:
for x in range(301):
y_axis[x] = expected_games_until_qualification(0.40+x/1000, bestof)
ax.plot(x_axis, y_axis, label='best-of-'+str(bestof))
ax.set(xlabel='Game win rate', ylabel='Expected number of games',
title='Arena Open: Expected number of games to earn a Day 2 qualification')
ax.set_xticklabels(['{:.0%}'.format(x) for x in ax.get_xticks()])
ax.set_yticklabels(['{:.0f}'.format(y) for y in ax.get_yticks()])
ax.grid(True)
plt.xlim(.4,.7)
plt.ylim(0,250)
plt.legend()
fig.savefig("Day_2_qualification_nr_games.png")
print("\n")
print(f'Expected nr games per qualification via Bo1, 50% win rate: {expected_games_until_qualification(0.5,1): .1f}')
print(f'Expected nr games per qualification via Bo3, 50% win rate: {expected_games_until_qualification(0.5,3): .1f}')
print("\n")
#Let's define some values for Day 2. Keys represent wins
Day_2_gems = {
0: 0,
1: 2000,
2: 4000,
3: 6000,
4: 10000,
5: 20000,
6: 0,
7: 0
}
Day_2_dollars = {
0: 0,
1: 0,
2: 0,
3: 0,
4: 0,
5: 0,
6: 1000,
7: 2000
}
dollar_per_gem = 0.005
def expected_dollar_Day2(G):
"""
Parameters:
G - The game win rate
Returns - The expected earnings (in dollar, with gems translated to dollars) on Day 2
Note - For Day 2, it's play until 7 wins or 2 losses
"""
expectation = 0
p = match_win_prob(G)
for wins in [0, 1, 2, 3, 4, 5, 6]:
record_probability = record_prob(p, 7, 2, wins, 2)
dollar_earnings = Day_2_gems[wins] * dollar_per_gem + Day_2_dollars[wins]
expectation += record_probability * dollar_earnings
for losses in [0, 1]:
record_probability = record_prob(p, 7, 2, 7, losses)
dollar_earnings = Day_2_gems[7] * dollar_per_gem + Day_2_dollars[7]
expectation += record_probability * dollar_earnings
return expectation
def expected_profit_Day1(G, bestof):
"""
Parameters:
G - The game win rate
bestof - Either 1 or 3, represents the type of event entered
Returns - The expected profit (in dollar, with gems translated to dollars) on both Day 1 AND Day 2, minus entry fee
"""
expectation = 0
round_win_prob = G if bestof == 1 else match_win_prob(G)
for wins in range(max_wins[bestof]):
record_probability = record_prob(round_win_prob, max_wins[bestof], max_losses[bestof], wins, max_losses[bestof])
dollar_earnings = Day_1_gems[(bestof, wins)] * dollar_per_gem
expectation += record_probability * dollar_earnings
for losses in range(max_losses[bestof]):
record_probability = record_prob(round_win_prob, max_wins[bestof], max_losses[bestof], max_wins[bestof], losses)
dollar_earnings = Day_1_gems[(bestof,max_wins[bestof])] * dollar_per_gem + expected_dollar_Day2(G)
expectation += record_probability * dollar_earnings
return expectation - 20
#Determine and plot the expected number of games to earn a Day 2 qualification
x_axis = np.arange(0.40, 0.701, 0.001)
y_axis = np.empty(301)
fig, ax = plt.subplots()
for bestof in [1,3]:
for x in range(301):
y_axis[x] = expected_profit_Day1(0.40+x/1000, bestof)
ax.plot(x_axis, y_axis, label='best-of-'+str(bestof))
ax.set(xlabel='Game win rate', ylabel='Expected profit',
title='Arena Open: Expected profit as a function of game win rate')
ax.set_xticklabels(['{:.0%}'.format(x) for x in ax.get_xticks()])
ax.set_yticklabels(['${:.0f}'.format(y) for y in ax.get_yticks()])
ax.grid(True)
ax.axhline(linewidth=2, color='k')
plt.legend()
plt.xlim(.4,.7)
fig.savefig("Expected_profit_full.png")
#Let's zoom in
x_axis = np.arange(0.40, 0.701, 0.001)
y_axis = np.empty(301)
fig, ax = plt.subplots()
for bestof in [1,3]:
for x in range(301):
y_axis[x] = expected_profit_Day1(0.40+x/1000, bestof)
ax.plot(x_axis, y_axis, label='best-of-'+str(bestof))
ax.set(xlabel='Game win rate', ylabel='Expected profit',
title='Arena Open: Expected profit as a function of game win rate')
ax.set_xticklabels(['{:.0%}'.format(x) for x in ax.get_xticks()])
ax.set_yticklabels(['${:.0f}'.format(y) for y in ax.get_yticks()])
ax.grid(True)
ax.axhline(linewidth=2, color='k')
plt.legend()
plt.xlim(.4,.55)
plt.ylim(-20,20)
fig.savefig("Expected_profit_zoomed.png")
#Also determine the break-even and intersection points
bo1 = np.empty(3001)
bo3 = np.empty(3001)
for x in range(1,3001):
bo1[x] = expected_profit_Day1(0.40+x/10000, 1)
if bo1[x] >= 0 and bo1[x - 1] < 0:
print(f'Break even in best-of-1 at: {(0.40+x/10000)*100: .1f}% win rate.')
bo3[x] = expected_profit_Day1(0.40+x/10000, 3)
if bo3[x] >= 0 and bo3[x - 1] < 0:
print(f'Break even in best-of-3 at: {(0.40+x/10000)*100: .1f}% win rate.')
if bo1[x] >= bo3[x] and bo1[x - 1] < bo3[x - 1]:
print(f'Intersection at: {(0.40+x/10000)*100: .1f}% win rate.')