forked from rrenaud/dominionstats
-
Notifications
You must be signed in to change notification settings - Fork 3
/
Copy pathoptimal_card_ratios.py
214 lines (175 loc) · 7.5 KB
/
optimal_card_ratios.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
#!/usr/bin/python
# -*- coding: utf-8 -*-
import logging
import time
from game import Game
from primitive_util import PrimitiveConversion, ConvertibleDefaultDict
from stats import MeanVarStat
import dominioncards
import dominionstats.utils.log
import incremental_scanner
import utils
# Module-level logging instance
log = logging.getLogger(__name__)
log.addHandler(logging.NullHandler())
class DBCardRatioTracker(PrimitiveConversion):
""" This keeps track of every final and progressive card ratio for one
pair of cards.
"""
def __init__(self):
self.final = ConvertibleDefaultDict(MeanVarStat)
self.progressive = ConvertibleDefaultDict(MeanVarStat)
def add_outcome(self, tracker_type, ratio, win_points):
if tracker_type == 'final':
tracker = self.final
elif tracker_type == 'progressive':
tracker = self.progressive
else:
raise
if ratio not in tracker:
tracker[ratio] = MeanVarStat()
tracker[ratio].add_outcome(win_points)
class DBCardRatioTrackerManager:
""" This keeps track of every final and progressive card ratio for all
pairs of cards. This manages DBCardRatioTracker instances.
"""
def __init__(self, collection, incremental=True):
self.collection = collection
self.incremental = incremental
self.trackers = {}
if self.incremental:
for entry in collection.find():
tracker = DBCardRatioTracker()
tracker.from_primitive_object(entry)
self.trackers[entry['_id']] = tracker
def integrate_results(self, tracker_type, ratio_dict, win_points):
for key in ratio_dict:
if key not in self.trackers:
self.trackers[key] = DBCardRatioTracker()
tracker = self.trackers[key]
for ratio in ratio_dict[key]:
ratio = str(ratio[0]) + ':' + str(ratio[1])
tracker.add_outcome(tracker_type, ratio, win_points)
def save(self):
if not self.incremental:
self.collection.drop()
for key, tracker in self.trackers.iteritems():
utils.write_object_to_db(tracker, self.collection, key)
class CardRatioTracker:
""" Base class for the final and progressive card ratio trackers.
"""
def __init__(self, supply):
self.card_counts = {}
for card in [dominioncards.Estate, dominioncards.Duchy,
dominioncards.Province, dominioncards.Curse,
dominioncards.Copper, dominioncards.Silver,
dominioncards.Gold] + supply:
self.card_counts[card] = 0
def get_card_ratios(self):
ratios = {}
for card1 in self.card_counts.iterkeys():
for card2 in self.card_counts.iterkeys():
if str(card1) < str(card2):
key = str(card1) + ':' + str(card2)
ratios[key] = set([(self.card_counts[card1], self.card_counts[card2])])
return ratios
class FinalCardRatioTracker(CardRatioTracker):
""" This is used to get the ratios between all of the cards in the supply
that a player has at the end of the game.
"""
def __init__(self, supply):
CardRatioTracker.__init__(self, supply)
def adjust_card_count(self, card, adjustment):
if card not in self.card_counts:
return
self.card_counts[card] += adjustment
def get_ratio_dict(self):
return CardRatioTracker.get_card_ratios(self)
class ProgressiveCardRatioTracker(CardRatioTracker):
""" This tracks all of the ratios between all of the cards in the supply
that a player has at any point throughout the whole game.
"""
def __init__(self, supply):
CardRatioTracker.__init__(self, supply)
self.card_counts[dominioncards.Estate] = 3
self.card_counts[dominioncards.Copper] = 7
self.ratios = self.get_card_ratios()
def adjust_card_count(self, card, adjustment):
if card not in self.card_counts:
return
self.card_counts[card] += adjustment
for card2 in self.card_counts.iterkeys():
if card != card2:
if str(card) < str(card2):
c1, c2 = card, card2
else:
c1, c2 = card2, card
key = str(c1) + ':' + str(c2)
self.ratios[key].add((self.card_counts[c1], self.card_counts[c2]))
def get_ratio_dict(self):
return self.ratios
def process_game(game):
names = game.all_player_names()
supply = game.get_supply()
name_to_final_tracker = dict((name, FinalCardRatioTracker(supply)) for name in names)
name_to_progressive_tracker = dict((name, ProgressiveCardRatioTracker(supply)) for name in names)
name_to_win_points = dict((player_deck.name(), player_deck.WinPoints()) for player_deck in game.get_player_decks())
for player_deck in game.get_player_decks():
tracker = name_to_final_tracker[player_deck.name()]
for card, count in player_deck.Deck().iteritems():
tracker.adjust_card_count(card, count)
for turn in game.get_turns():
for deck_change in turn.deck_changes():
tracker = name_to_progressive_tracker[deck_change.name]
for card in deck_change.buys:
tracker.adjust_card_count(card, 1)
for card in deck_change.gains:
tracker.adjust_card_count(card, 1)
for card in deck_change.returns:
tracker.adjust_card_count(card, -1)
for card in deck_change.trashes:
tracker.adjust_card_count(card, -1)
retval = []
for name in names:
retval.append([name_to_final_tracker[name].get_ratio_dict(),
name_to_progressive_tracker[name].get_ratio_dict(),
name_to_win_points[name]])
return retval
def main(args):
commit_after = 25000
database = utils.get_mongo_database()
games = database.games
collection = database.optimal_card_ratios
db_tracker = None
scanner = incremental_scanner.IncrementalScanner('optimal_card_ratios', database)
if not args.incremental:
log.warning('resetting scanner and db')
scanner.reset()
log.info("Starting run: %s", scanner.status_msg())
for ind, game in enumerate(
utils.progress_meter(scanner.scan(games, {}))):
if not db_tracker:
log.debug("Initializing db tracker manager")
db_tracker = DBCardRatioTrackerManager(collection, args.incremental)
log.debug("DB tracker manager initialized")
result = process_game(Game(game))
for final_ratio_dict, progressive_ratio_dict, win_points in result:
db_tracker.integrate_results('final', final_ratio_dict, win_points)
db_tracker.integrate_results('progressive', progressive_ratio_dict, win_points)
if args.max_games >= 0 and ind >= args.max_games:
log.info("Reached max_games of %d", args.max_games)
break
if ind % commit_after == 0 and ind > 0:
start = time.time()
db_tracker.save()
scanner.save()
log.info("Committed calculations to the DB in %5.2fs", time.time() - start)
log.info("Ending run: %s", scanner.status_msg())
if db_tracker:
db_tracker.save()
scanner.save()
if __name__ == '__main__':
parser = utils.incremental_max_parser()
outer_args = parser.parse_args()
dominionstats.utils.log.initialize_logging(outer_args.debug)
main(outer_args)