forked from frankkarsten/MTG-Math
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathLayDownArms.py
163 lines (140 loc) · 5.08 KB
/
LayDownArms.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
import random
def put_lands_on_bottom(hand, lands_to_bottom):
"""
Parameters:
hand - A dictionary, with the same cardnames as in decklist, with number drawn
lands_to_bottom - The number of lands to bottom (must be <= number of lands in hand)
Returns - nothing, it just adjusts the hand value
"""
Bottomable_other_lands = min(hand['Other Land'], lands_to_bottom)
hand['Other Land'] -= Bottomable_other_lands
Bottomable_basic_lands = min(hand['Plains'], lands_to_bottom - Bottomable_other_lands)
hand['Plains'] -= Bottomable_basic_lands
def put_spells_on_bottom(hand, spells_to_bottom):
"""
Parameters:
hand - A dictionary, with the same cardnames as in decklist, with number drawn
spells_to_bottom - The number of spells to bottom (must be <= number of spells in hand)
Returns - nothing, it just adjusts the hand value
"""
Bottomable_other_spells = min(hand['Other Spell'], spells_to_bottom)
hand['Other Spell'] -= Bottomable_other_spells
Bottomable_Lay = min(hand['Lay'], spells_to_bottom - Bottomable_other_spells)
hand['Lay'] -= Bottomable_Lay
def total_lands(hand):
return hand['Plains'] + hand['Other Land']
def total_spells(hand):
return hand['Lay'] + hand['Other Spell']
def run_one_sim():
#Conditional on drawing at least one Lay and at least two lands by a certain turn,
#after accounting for mulligans in a 26-land, 60-card deck with 4 Lay,
#we'll look for the probability to have access to a Plains
keephand = False
for handsize in [7, 6, 5, 4]:
#We may mull 7, 6, or 5 cards and keep every 4-card hand
#Once we actually keep, the variable keephand will be set to True
if not keephand:
#Construct library as a list
library = []
for card in decklist.keys():
library += [card] * decklist[card]
random.shuffle(library)
#Construct a random opening hand
hand = {
'Lay': 0,
'Other Spell': 0,
'Plains': 0,
'Other Land': 0
}
for _ in range(7):
card_drawn = library.pop(0)
hand[card_drawn] += 1
if handsize == 7:
#Do we keep?
if (total_lands(hand) >= 2 and total_lands(hand) <= 5):
keephand = True
if handsize == 6:
#We have to bottom. Ideal would be 3 land, 3 spells
if total_spells(hand) > 3:
put_spells_on_bottom(hand, 1)
else:
#The hand has 0, 1, 2, or 3 spells so we put a land on the bottom
put_lands_on_bottom(hand, 1)
#Do we keep?
if (total_lands(hand) >= 2 and total_lands(hand) <= 4):
keephand = True
if handsize == 5:
#We have to bottom. Ideal would be 3 land, 2 spells
if total_spells(hand) > 3:
#Two spells on the bottom
put_spells_on_bottom(hand, 2)
elif total_spells(hand) == 3:
#One land, one spell on the bottom
put_lands_on_bottom(hand, 1)
put_spells_on_bottom(hand, 1)
else:
#The hand has 0, 1, or 2 spells so we put two land on the bottom
put_lands_on_bottom(hand, 2)
#Do we keep?
if (total_lands(hand) >= 2 and total_lands(hand) <= 4):
keephand = True
if handsize == 4:
#We have to bottom. Ideal would be 3 land, 1 spell
if total_spells(hand) > 3:
#Three spells on the bottom
put_spells_on_bottom(hand, 3)
elif total_spells(hand) == 3:
#One land, two spell on the bottom
put_lands_on_bottom(hand, 1)
put_spells_on_bottom(hand, 2)
elif total_spells(hand) == 2:
#Two land, one spell on the bottom
put_lands_on_bottom(hand, 2)
put_spells_on_bottom(hand, 1)
else:
#The hand has 0 or 1 spell so we put three land on the bottom
put_lands_on_bottom(hand, 3)
#Do we keep?
keephand = True
if play_draw == 'draw':
#Draw a card on turn 1
card_drawn = library.pop(0)
hand[card_drawn] += 1
for turn in range(2, turn_allowed + 1):
#If, e.g., turn_allowed is 3 then this range is {2, 3}
card_drawn = library.pop(0)
hand[card_drawn] += 1
if total_lands(hand) >= turn_allowed and hand['Lay'] >= 1 and hand['Plains'] >= turn_allowed:
return 'Success'
if total_lands(hand) >= turn_allowed and hand['Lay'] >= 1 and hand['Plains'] < turn_allowed:
return 'Failure'
if total_lands(hand) < turn_allowed or hand['Lay'] == 0:
return 'Irrelevant for conditioning'
num_simulations = 500000
#Uncertainty with five million simulations will be about +/- 0.03%
nr_lands = 26
deck_size = 60
nr_Lay = 4
for turn_allowed in [2, 3, 4]:
for basic_lands in [17, 18, 19, 20, 21, 22, 23, 24, 25, 26]:
for play_draw in ['play', 'draw']:
decklist = {
'Lay': nr_Lay,
'Other Spell': deck_size - nr_Lay - nr_lands,
'Plains': basic_lands,
'Other Land': nr_lands - basic_lands
}
num_success = 0.0
num_relevant_games = 0.0
for _ in range(num_simulations):
outcome = run_one_sim()
if outcome == 'Success':
num_success += 1
num_relevant_games += 1
if outcome == 'Failure':
num_relevant_games += 1
if play_draw == 'play':
prob_on_play = num_success / num_relevant_games
if play_draw == 'draw':
prob_on_draw = num_success / num_relevant_games
print(f"Conditional probability of basic on turn {turn_allowed} with {basic_lands} basics: {prob_on_play *100:.1f}% (play) / {prob_on_draw *100:.1f}% (draw)")