def __init__(self, game): self.game = game self.turn_ordered_players = game.get_player_decks( sort_by_turn_order=True) self.supply = ConvertibleDefaultDict(value_type=int) num_players = len(game.get_player_decks()) for cardinst in set(EVERY_SET_CARDS + game.get_supply()): self.supply[cardinst] = cardinst.num_copies_per_game(num_players) self.player_decks = ConvertibleDefaultDict( value_type=lambda: ConvertibleDefaultDict(int)) self.player_start_decks = ConvertibleDefaultDict( value_type=lambda: ConvertibleDefaultDict(int)) self.player_vp_tokens = collections.defaultdict(int) if game.player_start_decks is None: self.supply[dominioncards.Copper] = self.supply[dominioncards.Copper] - (len(self.turn_ordered_players) * 7) for player in self.turn_ordered_players: self.player_decks[player.name()][dominioncards.Copper] = 7 self.player_decks[player.name()][dominioncards.Estate] = 3 else: for start_deck in game.player_start_decks: for card in start_deck[START_DECK]: self.supply[dominioncards.index_to_card(card)] -= 1 self.player_decks[start_deck[NAME]][dominioncards.index_to_card(card)] += 1 self.turn_ind = 0
def __init__(self, game_dict): self.turns = [] self.supply = [index_to_card(i) for i in game_dict[SUPPLY]] self.vetoes = game_dict.get(VETO, {}) # pprint.pprint(game_dict) self.player_decks = [PlayerDeck(pd, self) for pd in game_dict[DECKS]] self.id = game_dict.get('_id', '') for raw_pd, pd in zip(game_dict[DECKS], self.player_decks): turn_ct = 0 poss_ct = 0 out_ct = 0 for turn in raw_pd[TURNS]: if POSSESSION in turn: poss_ct += 1 elif OUTPOST in turn: out_ct = 1 else: turn_ct += 1 poss_ct, out_ct = 0, 0 self.turns.append(Turn(turn, game_dict, pd, turn_ct, poss_ct)) pd.set_num_turns(turn_ct) self.turns.sort(key=lambda x: (x.get_turn_no(), x.get_player().TurnOrder(), x.get_poss_no()))
def __init__(self, game_dict): self.turns = [] self.supply = [index_to_card(i) for i in game_dict[SUPPLY]] self.vetoes = game_dict.get(VETO, {}) # pprint.pprint(game_dict) self.player_decks = [PlayerDeck(pd, self) for pd in game_dict[DECKS]] self.id = game_dict.get('_id', '') for raw_pd, pd in zip(game_dict[DECKS], self.player_decks): turn_ct = 0 poss_ct = 0 out_ct = 0 for turn in raw_pd[TURNS]: if POSSESSION in turn: poss_ct += 1 elif OUTPOST in turn: out_ct = 1 else: turn_ct += 1 poss_ct, out_ct = 0, 0 self.turns.append(Turn(turn, game_dict, pd, turn_ct, poss_ct)) pd.set_num_turns(turn_ct) self.turns.sort(key=lambda x: (x.get_turn_no(), x.get_player(). TurnOrder(), x.get_poss_no()))
def track_brokenness(log, parse_error_col, parsed_games): """Print some summary statistics about cards that cause bad parses.""" failures = 0 wrongness = collections.defaultdict(int) overall = collections.defaultdict(int) for raw_game in parsed_games: accurately_parsed = check_game_sanity(game.Game(raw_game), log) if not accurately_parsed: log.warning('Failed to accurately parse game %s', raw_game['_id']) save_parse_error(parse_error_col, log, raw_game, 'check_game_sanity failed') failures += 1 for card in raw_game[SUPPLY]: if not accurately_parsed: wrongness[card] += 1 overall[card] += 1 ratios = [] for card in overall: ratios.append(((float(wrongness[card]) / overall[card]), index_to_card(card))) ratios.sort() if ratios and ratios[-1][0] > 0: log.warning("Ratios for problem cards %s, %d failures out of %d games", ratios[-10:], failures, len(parsed_games)) else: log.debug('Perfect parsing, %d games!', len(parsed_games))
def __init__(self, player_deck_dict, game): self.raw_player = player_deck_dict self.game = game self.player_name = player_deck_dict[NAME] self.win_points = player_deck_dict[WIN_POINTS] self.points = player_deck_dict[POINTS] self.deck = {} for (index, count) in player_deck_dict[DECK].iteritems(): self.deck[ index_to_card(int(index)) ] = count self.turn_order = player_deck_dict[ORDER] self.num_real_turns = 0
def __init__(self, player_deck_dict, game): self.raw_player = player_deck_dict self.game = game self.player_name = player_deck_dict[NAME] self.win_points = player_deck_dict[WIN_POINTS] self.points = player_deck_dict[POINTS] self.deck = {} for (index, count) in player_deck_dict[DECK].iteritems(): self.deck[index_to_card(int(index))] = count self.turn_order = player_deck_dict[ORDER] self.num_real_turns = 0
def parse_game(game_str, dubious_check = False): """ Parse game_str into game dictionary. game_str: Entire contents of a log file from goko. dubious_check: If true, raise a BogusGame exception if the game is suspicious. returns a dict with the following fields: decks: A list of player decks, as documented in parse_endgame. start_decks: A list of player starting decks. Usually 7c3e. supply: A list of cards in the supply. players: A list of normalized player names. game_end: List of cards exhausted that caused the game to end. resigned: True iff some player in the game resigned.. """ # Goko logs are not split into sections by an obvious separator # So analyze sequentially, by lines log_lines = game_str.split('\n') game_dict = parse_header(log_lines) # start_decks, players, and supply are now set validate_names(game_dict, dubious_check) game_dict[VETO] = {} # So much work just to know when two piles are empty for cities! # Here, need to account for number of coppers/zapped silvers in all start # decks. Can't get estates to work with zapped start decks and shelters... removed_from_supply = collections.defaultdict(lambda: 0) for d in game_dict[START_DECKS]: for c in d[START_DECK]: card = index_to_card(c) if card != dominioncards.Estate and not card.is_shelter(): removed_from_supply[card] += 1 turns = parse_turns(log_lines, game_dict[PLAYERS], removed_from_supply) decks = parse_endgame(log_lines) game_dict[DECKS] = decks associate_turns_with_owner(game_dict, turns, dubious_check) return game_dict
def turn_decode(turn_dict, field): return [index_to_card(i) for i in turn_dict.get(field, [])]
def name_getter(ind_str): return dominioncards.index_to_card(int(ind_str)).singular
def GET(self): web.header("Content-Type", "text/html; charset=utf-8") query_dict = dict(urlparse.parse_qsl(web.ctx.env['QUERY_STRING'])) target_player = query_dict['player'].decode('utf-8') db = utils.get_mongo_database() game_stats = db.game_stats norm_target_player = norm_name(target_player) games_coll = game_stats.find({compkey('_id', NAME): norm_target_player}) leaderboard_history_result = db.leaderboard_history.find_one( {'_id': norm_target_player}) leaderboard_history = None if leaderboard_history_result: leaderboard_history = leaderboard_history_result['history'] game_list = [] aliases = set() overall_record = RecordSummary() rec_by_game_size = collections.defaultdict(RecordSummary) rec_by_date = collections.defaultdict(RecordSummary) rec_by_turn_order = collections.defaultdict(RecordSummary) expansion_dist = collections.defaultdict(float) expansion_win_points = collections.defaultdict(float) date_buckets = [1, 3, 5, 10] cutoffs = {} for delta in date_buckets: cutoff = datetime.datetime.now().date() + datetime.timedelta(days = -delta) cutoffs[delta] = cutoff.strftime("%Y%m%d") # NOTE: This assumes that game IDs can be lexically sorted # into temporal order for g in games_coll.sort('_id', pymongo.DESCENDING): g_id = g['_id']['game_id'] game_list.append(g_id) name = g['_id'][NAME] # TODO: Turn this back. The concept of aliases only comes #into play when two different "real" player names both #normalize to the same "normalized" player name. # aliases.add(target_player_cur_name) wp = g[WIN_POINTS] res = g[RESULT] overall_record.record_result(res, wp) game_len = len( g[PLAYERS] ) + 1 rec_by_game_size[game_len].record_result(res, wp) _ord = g[ORDER] rec_by_turn_order[_ord].record_result(res, wp) for delta in date_buckets: if g['game_date'] >= cutoffs[delta]: rec_by_date[delta].record_result(res, wp) supply = [dominioncards.index_to_card(i) for i in g[SUPPLY]] for (ex, wt) in dominioncards.get_expansion_weight(supply).items(): expansion_dist[ex] += wt expansion_win_points[ex] += wt * wp #TODO: a good choice for a template like jinja2 ret = standard_heading("CouncilRoom.com: Dominion Stats: %s" % target_player) ret += '<form action="/player" method="get">' ret += '<span class="subhead">Profile for %s</span>' % target_player leaderboard_history_most_recent = (leaderboard_history[-1] if leaderboard_history else None) if leaderboard_history_most_recent: level = (leaderboard_history_most_recent[1] - leaderboard_history_most_recent[2]) level = int(max(math.floor(level), 0)) ret += '<span class="level">Level ' + str(level) + '</span>' ret += '<span class="search2">' ret += """ Search for another player: <input type="text" name="player" style="width:100px;" /> <input type="submit" value="View Stats!" /> </span></form><br><br> """ if len(aliases) > 1: ret += 'Aliases: ' + ', '.join(aliases) + '\n' ret += render_record_table('Record by game size', overall_record, rec_by_game_size, lambda game_size: '%d players' % game_size) ret += render_record_table('Recent Record', overall_record, rec_by_date, lambda num_days: 'Last %d days' % num_days) ret += render_record_table('Record by turn order', overall_record, rec_by_turn_order, lambda pos: 'Table position %d' % pos) ret += '<div style="clear: both;"> </div>' ret += '<div class="cardborder yellow"><h3>Expansion Data</h3><table class="stats">' ret += '<tr><th>Card Set<th>Avg. Cards<br/> Per Kingdom<th>Weighted<br/> Win Points<th>Favor' for (ex, weight) in sorted(expansion_dist.iteritems(), key=operator.itemgetter(1), reverse=True): if ex == 'Fan': continue wp = expansion_win_points[ex] / weight average = overall_record.average_win_points() ret += '<tr><th>%s</th>'%ex ret += '<td>%.2f</td>'% (weight * 10. / len(game_list)) ret += '<td>%.2f<td>' % wp if average > 0: ret += '<td>%.2f%%</td>'% ( (wp - average) * 100. / average ) else: ret += '<td>0</td>' ret += '</table></div>' ret += '<div style="clear: both;"> </div>' ret += goals.MaybeRenderGoals(db, norm_target_player) ret += '<A HREF="/popular_buys?player=%s"><h2>Stats by card</h2></A>\n' % target_player ret += '<A HREF="/games_by_opponent?player=%s"><h2>Record by opponent</h2></A>\n' % target_player if leaderboard_history: render = web.template.render('') ret += str(render.player_page_leaderboard_history_template( json.dumps(leaderboard_history))) ret += '<h2>Most recent games</h2>\n' qm = query_matcher.QueryMatcher(p1_name=target_player) goko_games = [g for g in game_list if '.txt' in game_list] if len(goko_games) > 2: goko_games.sort(reverse=True) most_recent = goko_games[:3] else: most_recent = game_list[:3] for g_id in most_recent: g = db.games.find_one({'_id': g_id}) game_val = game.Game(g) ret += (query_matcher.GameMatcher(game_val, qm).display_game_snippet() + '<br>') ret += ('<A HREF="/search_result?p1_name=%s">(See more)</A>' % target_player) ret += '</body></html>' return ret
def GET(self): web.header("Content-Type", "text/html; charset=utf-8") query_dict = dict(urlparse.parse_qsl(web.ctx.env['QUERY_STRING'])) target_player = query_dict['player'].decode('utf-8') db = utils.get_mongo_database() game_stats = db.game_stats norm_target_player = norm_name(target_player) games_coll = game_stats.find({compkey('_id', NAME): norm_target_player}) leaderboard_history_result = db.leaderboard_history.find_one( {'_id': norm_target_player}) leaderboard_history = None if leaderboard_history_result: leaderboard_history = leaderboard_history_result['history'] game_list = [] aliases = set() overall_record = RecordSummary() rec_by_game_size = collections.defaultdict(RecordSummary) rec_by_date = collections.defaultdict(RecordSummary) rec_by_turn_order = collections.defaultdict(RecordSummary) expansion_dist = collections.defaultdict(float) expansion_win_points = collections.defaultdict(float) date_buckets = [1, 3, 5, 10] cutoffs = {} for delta in date_buckets: cutoff = datetime.datetime.now().date() + datetime.timedelta(days = -delta) cutoffs[delta] = cutoff.strftime("%Y%m%d") # NOTE: This assumes that game IDs can be lexically sorted # into temporal order for g in games_coll.sort('_id', pymongo.DESCENDING): g_id = g['_id']['game_id'] game_list.append(g_id) name = g['_id'][NAME] # TODO: Turn this back. The concept of aliases only comes #into play when two different "real" player names both #normalize to the same "normalized" player name. # aliases.add(target_player_cur_name) wp = g[WIN_POINTS] res = g[RESULT] overall_record.record_result(res, wp) game_len = len( g[PLAYERS] ) + 1 rec_by_game_size[game_len].record_result(res, wp) _ord = g[ORDER] rec_by_turn_order[_ord].record_result(res, wp) for delta in date_buckets: if g['game_date'] >= cutoffs[delta]: rec_by_date[delta].record_result(res, wp) supply = [dominioncards.index_to_card(i) for i in g[SUPPLY]] for (ex, wt) in dominioncards.get_expansion_weight(supply).items(): expansion_dist[ex] += wt expansion_win_points[ex] += wt * wp #TODO: a good choice for a template like jinja2 ret = standard_heading("CouncilRoom.com: Dominion Stats: %s" % target_player) ret += '<form action="/player" method="get">' ret += '<span class="subhead">Profile for %s</span>' % target_player leaderboard_history_most_recent = (leaderboard_history[-1] if leaderboard_history else None) if leaderboard_history_most_recent: level = (leaderboard_history_most_recent[1] - leaderboard_history_most_recent[2]) level = int(max(math.floor(level), 0)) ret += '<span class="level">Level ' + str(level) + '</span>' ret += '<span class="search2">' ret += """ Search for another player: <input type="text" name="player" style="width:100px;" /> <input type="submit" value="View Stats!" /> </span></form><br><br> """ if len(aliases) > 1: ret += 'Aliases: ' + ', '.join(aliases) + '\n' ret += render_record_table('Record by game size', overall_record, rec_by_game_size, lambda game_size: '%d players' % game_size) ret += render_record_table('Recent Record', overall_record, rec_by_date, lambda num_days: 'Last %d days' % num_days) ret += render_record_table('Record by turn order', overall_record, rec_by_turn_order, lambda pos: 'Table position %d' % pos) ret += '<div style="clear: both;"> </div>' ret += '<div class="cardborder yellow"><h3>Expansion Data</h3><table class="stats">' ret += '<tr><th>Card Set<th>Avg. Cards<br/> Per Kingdom<th>Weighted<br/> Win Points<th>Favor' for (ex, weight) in sorted(expansion_dist.iteritems(), key=operator.itemgetter(1), reverse=True): if ex == 'Fan': continue wp = expansion_win_points[ex] / weight average = overall_record.average_win_points() ret += '<tr><th>%s</th>'%ex ret += '<td>%.2f</td>'% (weight * 10. / len(game_list)) ret += '<td>%.2f<td>' % wp if average > 0: ret += '<td>%.2f%%</td>'% ( (wp - average) * 100. / average ) else: ret += '<td>0</td>' ret += '</table></div>' ret += '<div style="clear: both;"> </div>' ret += goals.MaybeRenderGoals(db, norm_target_player) ret += '<A HREF="/popular_buys?player=%s"><h2>Stats by card</h2></A>\n' % target_player ret += '<A HREF="/games_by_opponent?player=%s"><h2>Record by opponent</h2></A>\n' % target_player if leaderboard_history: render = web.template.render('') ret += str(render.player_page_leaderboard_history_template( json.dumps(leaderboard_history))) ret += '<h2>Most recent games</h2>\n' qm = query_matcher.QueryMatcher(p1_name=target_player) for g_id in game_list[:3]: g = db.games.find_one({'_id': g_id}) game_val = game.Game(g) ret += (query_matcher.GameMatcher(game_val, qm).display_game_snippet() + '<br>') ret += ('<A HREF="/search_result?p1_name=%s">(See more)</A>' % target_player) ret += '</body></html>' return ret