def test_initialization(self): """Test various ways of initializing card objects """ card = dominioncards.get_card('Estate') self.assertEquals(card.singular, 'Estate') card = dominioncards.get_card('Card(Estate)') self.assertEquals(card.singular, 'Estate')
def check_game_sanity(game_val, log): """ Check if if game_val is self consistent. In particular, check that the end game player decks match the result of simulating deck interactions saved in game val.""" global __problem_deck_index__ supply = game_val.get_supply() # ignore known bugs. if set(supply).intersection( [get_card('Masquerade'), get_card('Black Market'), get_card('Trader')]): return True # TODO: add score sanity checking here last_state = None game_state_iterator = game_val.game_state_iterator() for game_state in game_state_iterator: last_state = game_state for player_deck in game_val.get_player_decks(): parsed_deck_comp = player_deck.Deck() computed_deck_comp = last_state.get_deck_composition( player_deck.name()) delete_keys_with_empty_vals(parsed_deck_comp) computed_dict_comp = dict(computed_deck_comp) delete_keys_with_empty_vals(computed_dict_comp) if parsed_deck_comp != computed_deck_comp: found_something_wrong = False for card in set(parsed_deck_comp.keys() + computed_deck_comp.keys()): if parsed_deck_comp.get(card, 0) != \ computed_deck_comp.get(card, 0): if not found_something_wrong: __problem_deck_index__ += 1 log.debug('[%d] %18s %9s %9s', __problem_deck_index__, 'card', 'from-data', 'from-sim') log.debug('[%d] %-18s %9d %9d', __problem_deck_index__, card, parsed_deck_comp.get(card, 0), computed_deck_comp.get(card, 0)) found_something_wrong = True if found_something_wrong: try: log.debug('[%d] insane game for %s %s: %s', __problem_deck_index__, player_deck.name(), game_val.get_id(), ' '.join(map(str, game_val.get_supply()))) except UnicodeEncodeError as e: None return False return True
def GET(self): web.header("Content-Type", "text/html; charset=utf-8") query_dict = dict(urlparse.parse_qsl(web.ctx.env['QUERY_STRING'])) db = utils.get_mongo_database() selected_card = '' if 'card' in query_dict: selected_card = query_dict['card'] results = db.trueskill_openings.find({'_id': {'$regex': '^open:'}}) openings = list(results) card_list = dominioncards.opening_cards() def split_opening(o): ret = o['_id'][len('open:'):].split('+') if ret == ['']: return [] # Convert the __repr__() representation stored in the # database to the singular version of the card name. return [dominioncards.get_card(card).singular for card in ret] if selected_card not in ('All cards', ''): openings = [o for o in openings if selected_card in split_opening(o)] openings = [o for o in openings if split_opening(o)] for opening in openings: floor = opening['mu'] - opening['sigma'] * 3 ceil = opening['mu'] + opening['sigma'] * 3 opening['level_key'] = make_level_key(floor, ceil) opening['level_str'] = make_level_str(floor, ceil) opening['skill_str'] = skill_str(opening['mu'], opening['sigma']) opening['cards'] = split_opening(opening) opening['cards'].sort() opening['cards'].sort(key=lambda card: dominioncards.get_card(card).cost, reverse=True) costs = [str(dominioncards.get_card(card).cost) for card in opening['cards']] while len(costs) < 2: costs.append('-') opening['cost'] = '/'.join(costs) openings.sort(key=lambda opening: opening['level_key']) openings.reverse() if selected_card == '': openings = [op for op in openings if op['level_key'][0] != 0 or op['_id'] == ['Silver', 'Silver']] render = web.template.render('') return render.openings_template(openings, card_list, selected_card)
def split_opening(o): ret = o['_id'][len('open:'):].split('+') if ret == ['']: return [] # Convert the __repr__() representation stored in the # database to the singular version of the card name. return [dominioncards.get_card(card).singular for card in ret]
def check_game_sanity(game_val, log): """ Check if if game_val is self consistent. In particular, check that the end game player decks match the result of simulating deck interactions saved in game val.""" global __problem_deck_index__ supply = game_val.get_supply() # ignore known bugs. if set(supply).intersection([get_card('Masquerade'), get_card('Black Market'), get_card('Trader')]): return True # TODO: add score sanity checking here last_state = None game_state_iterator = game_val.game_state_iterator() for game_state in game_state_iterator: last_state = game_state for player_deck in game_val.get_player_decks(): parsed_deck_comp = player_deck.Deck() computed_deck_comp = last_state.get_deck_composition( player_deck.name()) delete_keys_with_empty_vals(parsed_deck_comp) computed_dict_comp = dict(computed_deck_comp) delete_keys_with_empty_vals(computed_dict_comp) if parsed_deck_comp != computed_deck_comp: found_something_wrong = False for card in set(parsed_deck_comp.keys() + computed_deck_comp.keys()): if parsed_deck_comp.get(card, 0) != \ computed_deck_comp.get(card, 0): if not found_something_wrong: __problem_deck_index__ += 1 log.debug('[%d] %18s %9s %9s', __problem_deck_index__, 'card', 'from-data', 'from-sim') log.debug('[%d] %-18s %9d %9d', __problem_deck_index__, card, parsed_deck_comp.get(card, 0), computed_deck_comp.get(card, 0)) found_something_wrong = True if found_something_wrong: try: log.debug('[%d] insane game for %s %s: %s', __problem_deck_index__, player_deck.name(), game_val.get_id(), ' '.join(map(str, game_val.get_supply()))) except UnicodeEncodeError, e: None return False
def GET(self): web.header("Content-Type", "text/html; charset=utf-8") query_dict = dict(urlparse.parse_qsl(web.ctx.env['QUERY_STRING'])) card_list = sorted(set(dominioncards.all_cards()) - set(dominioncards.TOURNAMENT_WINNINGS)) card_x_card = dominioncards.get_card(query_dict.get('card_x', 'Minion')) card_y_card = dominioncards.get_card(query_dict.get('card_y', 'Chapel')) card_x = str(card_x_card) card_y = str(card_y_card) if card_x < card_y: db_id = card_x + ':' + card_y swap_x_and_y = False else: db_id = card_y + ':' + card_x swap_x_and_y = True db = utils.get_mongo_database() db_val = db.optimal_card_ratios.find_one({'_id': db_id}) if not db_val: return 'No stats for "' + card_x + '" and "' + card_y + '".' tracker = DBCardRatioTracker() tracker.from_primitive_object(db_val) num_games = sum(meanvarstat.frequency() for meanvarstat in tracker.final.itervalues()) num_games_threshold = int(round(num_games * .002)) final_table = self.getHtmlTableForStats( tracker.final, swap_x_and_y, num_games, num_games_threshold) num_games = max(meanvarstat.frequency() for meanvarstat in tracker.progressive.itervalues()) num_games_threshold = int(round(num_games * .002)) progressive_table = self.getHtmlTableForStats( tracker.progressive, swap_x_and_y, num_games, num_games_threshold) render = web.template.render('') return render.optimal_card_ratios_template( card_list, card_x_card, card_y_card, final_table, progressive_table)
def parse_supply(log_lines): line = log_lines.pop(0) supply_cards_text = line.split(', ') supply_cards_text[0] = supply_cards_text[0].replace('Supply cards: ','') supply_cards = [] for card_name in supply_cards_text: try: card = get_card(card_name) except KeyError, exception: raise parse_common.ParsingError('%s is not a card in the supply!' % card_name) supply_cards.append(card.index)
def capture_cards(line): """ Given a line of text from isotropic, extract the cards. line: string like 'Rob plays a <span class=card-none>Minion</span>.' returns: list of the card objects, eg, [Minion] """ def _as_int_or_1(string_val): try: return int(string_val) except ValueError: return 1 cards = [] card_sections = SPLIT_COMMA_AND_RE.split(line) for sect in card_sections: split_at_span = sect.split('<span') if not split_at_span: continue first = split_at_span[0] split_first = first.split() if not split_first: mult = 1 else: mult = _as_int_or_1(split_first[-1]) for subsect in split_at_span: start_of_end_span = subsect.find('</span') if start_of_end_span == -1: continue end_of_begin_span = subsect.rfind('>', 0, start_of_end_span) if end_of_begin_span == -1: continue maybe_plural = subsect[end_of_begin_span + 1: start_of_end_span] if maybe_plural == '♦': continue try: card = get_card(maybe_plural) except KeyError, exception: raise ParsingError('Failed to find card in line: %s' % line) cards.extend([card] * mult)
def __init__(self, **args): self.exact_names = [] self.players_restrict = [] self.kingdom_restrict = [] self.limit = 200 self.debug_level = args.get('debug', 0) if 'p1_name' in args: self._add_name(args['p1_name']) if 'p2_name' in args: self._add_name(args['p2_name']) if 'kingdom' in args: self.kingdom_restrict = [] for k in args['kingdom'].split(','): card = dominioncards.get_card(k.strip()) self.kingdom_restrict.append(card.index) self.db_query = {} if self.players_restrict: self.db_query[PLAYERS] = {'$all': self.players_restrict} if self.kingdom_restrict: self.db_query[SUPPLY] = {'$all': self.kingdom_restrict}
def capture_cards(line, return_dict=False): """ Given a section of text from goko, extract the cards. line: string like 'plays 1 Silver, 3 Copper' returns: list of the card objects, eg, [Silver, Copper, Copper, Copper] """ for kw in KEYWORDS: line=line.replace(kw, '') if return_dict: cards = {} else: cards = [] if EMPTY_LINE_RE.match(line): return cards card_sections = COMMA_SPLIT_RE.split(line) for sect in card_sections: multiple = NUMBER_CARD_RE.match(sect) if multiple: mult = int(multiple.group(1)) sect = multiple.group(2) else: mult = 1 try: card = get_card(sect) except KeyError, exception: raise parse_common.ParsingError('Failed to find card in line: %s' % line) if return_dict: cards[str(card.index)]=mult else: cards.extend([card] * mult)
def str_card_index(self, card_name): card = dominioncards.get_card(card_name) return str(card.index)
diamond_loc = chunk.find(u'▼') if diamond_loc != -1: start_point_loc = max(chunk.rfind('(', 0, diamond_loc - 1), chunk.rfind(' ', 0, diamond_loc - 1)) vp_tokens = int(chunk[start_point_loc + 1:diamond_loc - 1]) card_list_chunks = deck_contents[ deck_contents.find(']') + 1:].replace(',', ' ') card_blobs = [x for x in card_list_chunks.split('</span>') if '<span' in x] deck_comp = {} for card_blob in card_blobs: right_bracket_index = card_blob.find('>') card_name = card_blob[right_bracket_index + 1:] try: card = get_card(card_name) except KeyError, exception: raise ParsingError("Failed to get card. chunk: '%s', card_name: '%s', card_blob: '%s'" % \ (chunk, card_name, card_blob[right_bracket_index - 10:])) card_quant = int(card_blob.split()[0]) deck_comp[str(card.index)] = card_quant #FIXME: deck_comp is undefined if there's no vp_list return {NAME: name, POINTS: points, RESIGNED: resigned, DECK: deck_comp, VP_TOKENS: vp_tokens} def parse_decks(decks_blob): """ Parse and return a list of decks""" deck_blobs = [s for s in decks_blob.split('\n\n') if s] return [parse_deck(deck_blob) for deck_blob in deck_blobs] VETO_RE = re.compile('(.*) vetoes (.*)\.')
def parse_turn(turn_blob, names_list): """ Parse the information from a given turn. Return a dict containing the following fields. If any of the fields have a value that evaluates to False, do not keep it. name: player name. number: 1 indexed turn number. plays: List of cards played. buys: List of cards bought. gains: List of cards gained. trashes: List of cards trashed. returns: List of cards returned. ps_tokens: Number of pirate ship tokens gained. vp_tokens: Number of victory point tokens gained. money: Amount of money available during entire buy phase. opp: Dict keyed by opponent index in names_list, containing dicts with trashes/gains. """ lines = turn_blob.strip().split('\n') header = lines[0] parsed_header = parse_turn_header(header, names_list) poss, outpost = False, False if 'pname' in parsed_header: possessee_name = parsed_header['name'] possessee_index = names_list.index(possessee_name) poss = True if 'outpost' in parsed_header: outpost = True ret = {GAINS: [], TRASHES: [], BUYS: []} plays = [] returns = [] turn_money = 0 vp_tokens = 0 ps_tokens = 0 opp_turn_info = collections.defaultdict(lambda: {GAINS: [], TRASHES: [], BUYS: []}) tracker = PlayerTracker() for line_idx, line in enumerate(lines): active_player = tracker.get_active_player(line) if active_player == tracker.current_player(): targ_obj = ret else: # Stop using the player's name here, as it is used as a # key name in a dict, which can't be stored in MongoDB if # it contains a dot ('.') or starts with a dollar # sign. Instead, use the player index number so we can # extract the name later. # # targ_obj = opp_turn_info[names_list[active_player]] targ_obj = opp_turn_info[str(active_player)] has_trashing = KW_TRASHING in line has_trashes = KW_TRASHES in line has_gaining = KW_GAINING in line orig_buys_len = len(targ_obj.get(BUYS, [])) orig_gains_len = len(targ_obj.get(GAINS, [])) did_trading_post_gain = False if has_trashes: if has_gaining: # Trading post turn, first trashes, then gaining gain_start = line.find(KW_GAINING) targ_obj[TRASHES].extend(capture_cards(line[:gain_start])) targ_obj[GAINS].extend(capture_cards(line[gain_start:])) did_trading_post_gain = True else: targ_obj[TRASHES].extend(capture_cards(line)) if KW_WITH_A in line: if KW_REPLACING in line: new_gained_portion = line[line.find(KW_WITH_A):] targ_obj[GAINS].extend(capture_cards(new_gained_portion)) if KW_PLAYS in line or KW_PLAYING in line: plays.extend(capture_cards(line)) if has_gaining and not did_trading_post_gain: if KW_ANOTHER_ONE in line: # mints a gold gaining another one targ_obj[GAINS].extend(capture_cards(line)) else: # gaining always associated with current player? targ_obj[GAINS].extend( capture_cards(line[line.find(KW_GAINING):])) if KW_BUYS in line: targ_obj[BUYS].extend(capture_cards(line)) if KW_GAINS_THE in line: targ_obj[GAINS].extend(capture_cards(line)) if has_trashing: if KW_REVEALS in lines[line_idx - 1] and not KW_DRAWS in line: targ_obj[TRASHES].extend(capture_cards(lines[line_idx - 1])) if KW_REVEALING in line or KW_REVEALS in line: # reveals watchtower trashing ... # noble brigand reveals xx, yy and trashes yy trashed = capture_cards(line[line.find(KW_TRASHING):]) targ_obj[TRASHES].extend(trashed) else: rest = line if KW_GAINING in line: rest = line[:line.find(KW_GAINING)] targ_obj[TRASHES].extend(capture_cards(rest)) if KW_GAINS_A in line or KW_GAMES_A in line: if KW_TOKEN in line: assert get_card('Pirate Ship') in capture_cards(line), 'Pirate ship not in line' ps_tokens += 1 else: rest = line[max(line.find(KW_GAINS_A), line.find(KW_GAMES_A)):] targ_obj[GAINS].extend(capture_cards(rest)) if KW_IS_TRASHED in line: # Saboteur after revealing cards, name not mentioned on this line. cards = capture_cards(line) targ_obj[TRASHES].extend(cards) if KW_REVEALS in line: card_revealed = capture_cards(line) # arg, ambassador requires looking at the next line to figure # out how many copies were returned if (card_revealed and line_idx + 1 < len(lines) and KW_RETURNING in lines[line_idx + 1] and not KW_REVEALING in lines[line_idx + 1]): next_line = lines[line_idx + 1] num_copies = 1 num_copies_match = NUMBER_COPIES.search(next_line) if num_copies_match: num_copies = int(num_copies_match.group(1)) returns.extend(card_revealed * num_copies) if KW_REVEALING in line and KW_TO_THE_SUPPLY in line: # old style ambassador line returns.extend(capture_cards(line)) if KW_GETTING in line or KW_GETS in line or KW_GET in line: money_match = GETTING_MONEY_RE.search(line) if money_match: turn_money += int(money_match.group(1)) if KW_WHICH_IS_WORTH in line: worth_match = WHICH_IS_WORTH_RE.search(line) assert bool(worth_match), line turn_money += int(worth_match.group(1)) if KW_FOR_MONEY in line: worth_match = FOR_MONEY_RE.search(line) assert bool(worth_match), line turn_money += int(worth_match.group(1)) if u'▼' in line: vp_tokens += int(VP_TOKEN_RE.search(line).group('num')) if KW_INSTEAD in line and not KW_WISHING in line and 'Trader' in line: if 'buy_or_gain' in targ_obj: targ_list = targ_obj[targ_obj['buy_or_gain']] non_silver_ind = len(targ_list) - 1 while (non_silver_ind >= 0 and targ_list[non_silver_ind] == 'Silver'): non_silver_ind -= 1 # This shouldn't work when there is no non-silver, but then # non_silver_ind == -1 if there is no non-silver, # which magically pops the last item. <3 guido. targ_list.pop(non_silver_ind) else: assert 'Ill-Gotten Gains' in plays, ( "line %s: line\n, targ_obj: %s\n context: %s" % ( line, str(targ_obj), '\n'.join(lines[line_idx - 2: line_idx + 2]))) now_buys_len = len(targ_obj.get(BUYS, [])) now_gains_len = len(targ_obj.get(GAINS, [])) if now_buys_len > orig_buys_len: targ_obj['buy_or_gain'] = BUYS if now_gains_len > orig_gains_len: targ_obj['buy_or_gain'] = GAINS assert not (now_buys_len > orig_buys_len and now_gains_len > orig_gains_len), 'buys or gains mismatch' def _delete_if_exists(d, n): if n in d: del d[n] _delete_if_exists(ret, 'buy_or_gain') if poss: possessee_info = opp_turn_info[str(possessee_index)] for k in [GAINS, TRASHES]: _delete_if_exists(possessee_info, k) possessee_info[VP_TOKENS], vp_tokens = vp_tokens, 0 possessee_info[RETURNS], returns = returns, [] ret[BUYS] = [] # buys handled by possesion gain line. for opp in opp_turn_info.keys(): _delete_if_exists(opp_turn_info[opp], 'buy_or_gain') delete_keys_with_empty_vals(opp_turn_info[opp]) d = opp_turn_info[opp] for k, v in d.iteritems(): if k==VP_TOKENS: d[k] = v else: d[k] = indexes(v) ret[BUYS] = indexes(ret[BUYS]) ret[GAINS] = indexes(ret[GAINS]) ret[TRASHES] = indexes(ret[TRASHES]) ret.update({NAME: names_list[tracker.current_player()], PLAYS: indexes(plays) , RETURNS: indexes(returns), MONEY: count_money(plays) + turn_money, VP_TOKENS: vp_tokens, PIRATE_TOKENS: ps_tokens, POSSESSION: poss, OUTPOST: outpost, OPP: dict(opp_turn_info)}) delete_keys_with_empty_vals(ret) return ret
line = log_lines.pop(0) supply_cards_text = line.split(', ') supply_cards_text[0] = supply_cards_text[0].replace('Supply cards: ','') supply_cards = [] for card_name in supply_cards_text: try: card = get_card(card_name) except KeyError, exception: raise parse_common.ParsingError('%s is not a card in the supply!' % card_name) supply_cards.append(card.index) bane_match = BANE_RE.match(log_lines[0]) if bane_match: try: bane_card = get_card(bane_match.groups()[0]) log_lines.pop(0) except KeyError, exception: raise parse_common.ParsingError('%s is not a valid bane!' % card_name) return supply_cards def parse_header(log_lines): """Parse the goko header. It begins with the 'Game Setup' line and ends with the blank line before the first player's first turn. """ # first line - header line line = log_lines.pop(0)
def parse_deck(deck_str): """ Given an isotropic deck string, return a dictionary containing the player names deck_str: starts with placement and name, ends with last card in deck. returns dictionary containing the following fields name: vp_tokens: number of vp tokens. deck: Dictionary keyed card name who value is the card frequency. resigned: True iff this player resigned """ try: name_vp_list, _opening, deck_contents = deck_str.split('\n') except ValueError as e: raise ParsingError('Failed right off the bat to split the deck') vp_tokens = 0 #print 'vp', name_vp_list matched_points = POINTS_RE.search(name_vp_list) if matched_points: point_loc = matched_points.end() resigned, points = False, int(matched_points.group(1)) name_points, vp_list = (name_vp_list[:point_loc], name_vp_list[point_loc + 1:]) else: resign_loc = name_vp_list.find('resigned') assert resign_loc != -1, 'could not find resign in %s' % name_vp_list resigned, points = True, -100 name_points, vp_list = (name_vp_list[:resign_loc], name_vp_list[resign_loc + 1:]) last_colon_in_name_points = name_points.rfind(':') name, _points_or_resign = (name_points[:last_colon_in_name_points], name_points[last_colon_in_name_points + 1:]) def cleanup_name(name): """ Given a name and placement, get rid of the bold tags and """ htmlless_name = name.replace('<b>', '').replace('</b>', '') placement_match = PLACEMENT_RE.match(htmlless_name) if placement_match: return placement_match.group(1) return htmlless_name name = cleanup_name(name) for chunk in vp_list.split(','): diamond_loc = chunk.find(u'▼') if diamond_loc != -1: start_point_loc = max(chunk.rfind('(', 0, diamond_loc - 1), chunk.rfind(' ', 0, diamond_loc - 1)) vp_tokens = int(chunk[start_point_loc + 1:diamond_loc - 1]) card_list_chunks = deck_contents[deck_contents.find(']') + 1:].replace( ',', ' ') card_blobs = [ x for x in card_list_chunks.split('</span>') if '<span' in x ] deck_comp = {} for card_blob in card_blobs: right_bracket_index = card_blob.find('>') card_name = card_blob[right_bracket_index + 1:] try: card = get_card(card_name) except KeyError as exception: raise ParsingError("Failed to get card. chunk: '%s', card_name: '%s', card_blob: '%s'" % \ (chunk, card_name, card_blob[right_bracket_index - 10:])) card_quant = int(card_blob.split()[0]) deck_comp[str(card.index)] = card_quant #FIXME: deck_comp is undefined if there's no vp_list return { NAME: name, POINTS: points, RESIGNED: resigned, DECK: deck_comp, VP_TOKENS: vp_tokens }