def build_land_draw_database(use_last=False): Log('Starting land draw database construction') if use_last: f_in = open('draw_database_last.txt', 'r') else: f_in = open('draw_database.txt', 'r') f_out = open('land_draws_last.txt', 'w') if use_last: f_prod = None else: f_prod = open('land_draws.txt', 'w') land_mapper = utils.LoadLandMapping() already_processed = set() cnt = 0 for row in f_in: data = row.split(';') transact = data[0] # Eat redundant rows. if transact in already_processed: continue already_processed.add(transact) delim = str(utils.draw_database_separator) for pos in range(utils.draw_database_header_size,len(data)): if not data[pos] == delim: data[pos] = land_mapper.get(int(data[pos].strip()), '?') out_row = ';'.join(data) + '\n' f_out.write(out_row) if f_prod is not None: f_prod.write(out_row)
def run_land_counts(): """ For now, just copy the code. Should refactor into a single function. :return: """ f = open('land_draws.txt', 'r') already_processed = set() test_list = [TestLands()] for row in f: data = row.strip().split(';') user = data[3] mode = data[4] mulligan_count = int(data[8]) card_info = data[(utils.draw_database_header_size):] # card_info = [int(x) for x in card_info] try: idx = card_info.index(str(utils.draw_database_separator)) except ValueError: Log('Row missing deck separator: {0}\n'.format(row)) continue for t in test_list: t.ProcessRow(user, card_info[0:idx], card_info[(idx + 1):], mulligan_count, mode) f_out = open('summary_land.txt', 'w') for t in test_list: s = t.GetOutput() print(s) f_out.write(s)
def GetCardDefinitions(filename, land_mapping): Log('Processing: ' + filename + '\n') f = open(filename, 'r') for row in f: # We are only looking for logged transactions, which are saved in a format that is compatible with w Python dict if not row.startswith('{ "transactionId":'): continue # They use true instead of True... Must be a better way of doing this, but... row = row.replace('true', 'True') row = row.replace('false', 'False') # This should be safe; just allows low-level Python objects, no code execution d = ast.literal_eval(row) if 'greToClientEvent' in d: transact = d['transactionId'] msg_list = d['greToClientEvent']['greToClientMessages'] # Log('Transaction: {0}\n'.format(transact)) for msg in msg_list: try: data = msg['gameStateMessage']['gameObjects'] for obj in data: if obj['type'] == 'GameObjectType_Card': grpId = obj['grpId'] is_land = 'CardType_Land' in obj['cardTypes'] if is_land: land_mapping[grpId] = 'L' else: land_mapping[grpId] = 'N' except KeyError: pass else: # Nothing to process continue
def FindDeck(transact, msg_list): """ Returns None if not a deck message. :param transact: :param msg_list: :return: """ for msg in msg_list: if 'connectResp' in msg: try: deck = msg['connectResp']['deckMessage']['deckCards'] Log('Found Deck in new game connection') Log(str(deck) + '\n') except KeyError: return obj = DeckInfo(transact, deck) return obj
def handle_message(self, transact, msg, row): if 'gameStateMessage' in msg and 'ClientMessageType_MulliganResp' in row: if 'gameStateMessage' in msg: Log('Found a game state message during mulligan\n') hand = [] state_msg = msg['gameStateMessage'] # pprint.pprint(state_msg) # pprint.pprint(state_msg['zones']) self.transact = transact if 'gameObjects' in state_msg: objects = state_msg['gameObjects'] mapping = {} for obj in objects: mapping[obj['instanceId']] = obj['grpId'] for z in state_msg['zones']: # Player number parsing was not working for some reason vs. Sparky, so just # look at all hands. If the wrong player's hand, will get an error when # we try to map object ID's. Fails in a wacky game mode where you can see opponent's # hands. if (z['type'] == 'ZoneType_Hand'): # and z['ownerSeatId'] == self.player_number): hand = (z['objectInstanceIds']) # Note: the object seems to be in reverse order. Sort by objectId, since we might # eventually extend to look at draws into the library. hand.sort() # Log('Hand: ' + str(hand) + '\n') try: card_ids = [mapping[x] for x in hand] Log('Card IDs: ' + str(card_ids) +'\n') # Note: this will fail if they change away from the London Mulligan, # or in alternate play modes. Eliminate? if not len(card_ids) == 7: Log('Logic error - initial draw does not have 7 cards.') else: self.draw = card_ids except KeyError: # Opponent's hand, can't map pass if 'mulliganReq' in msg: # mulliganCount is not defined if zero. get(x,0) returns 0, if the key "x" does not exist. self.mulligan = msg['mulliganReq'].get('mulliganCount', 0) Log('Found mulligan count = {0}\n'.format(self.mulligan))
def Format(self, game_info, timestamp): Log('Formating draw\n') draw = [str(x) for x in self.draw] deck = [str(x) for x in self.deck] cut_name = self.user_name # Eliminate # digits. pos = cut_name.find('#') if pos > -1: cut_name = cut_name[0:pos] row = [self.transact, str(timestamp), self.build, cut_name, game_info.match_type, self.file_name, game_info.other_player, self.Paremeters, str(self.mulligan)] + draw row.append('-1') row += deck out = ';'.join(row) return out + '\n'
def run_tests(test_list): f = open('draw_database.txt', 'r') already_processed = set() for row in f: data = row.strip().split(';') user = data[3] mode = data[4] mulligan_count = int(data[8]) card_info = data[(utils.draw_database_header_size):] card_info = [int(x) for x in card_info] try: idx = card_info.index(utils.draw_database_separator) except ValueError: Log('Row missing deck separator: {0}\n'.format(row)) continue for t in test_list: t.ProcessRow(user, card_info[0:idx], card_info[(idx + 1):], mulligan_count, mode) f_out = open('summary.txt', 'w') for t in test_list: s = t.GetOutput() print(s) f_out.write(s)
def ProcessFile(filename, append_production=False, verbose=False): Log('Processing: ' + filename + '\n') f = open(filename, 'r') deck_handler = None # Metadata that needs to be added to the row user_name = '?' build = '?' draw_time = '?' match_type = '?' handle_last = open('draw_database_last.txt', 'a') # Move game info into an object. Will delete "floating variables" later. game_info = GameInfo() if append_production: handle_prod = open('draw_database.txt', 'a') else: handle_prod = None for row in f: # Look for the client version if build == '?': pos = row.find('\\"clientVersion\\":') else: # We have the build, ignore pos = -1 if pos > -1: cut = row[pos:(pos+80)] cut = cut.split(':') if len(cut) > 1: build = cut[1] pos = build.find(',') build = build[0:pos] build = build.replace('"', '') build = build.replace('\\', '').strip() Log('Build = {0}\n'.format(build)) # We are only looking for logged transactions, which are saved in a format that is compatible with w Python dict if not row.startswith('{ "transactionId":'): continue # They use true instead of True... Must be a better way of doing this, but... row = row.replace('true', 'True') row = row.replace('false', 'False') if verbose: Log('Found a transaction row\n') Log(row + '\n') Log('Evaluating\n') # This should be safe; just allows low-level Python objects, no code execution d = ast.literal_eval(row) # We need to get some initial information in early "transactions" # Find user name if it exists if 'authenticateResponse' in d: if 'screenName' in d['authenticateResponse']: user_name = d['authenticateResponse']['screenName'] Log('Found screen name: ' + user_name + '\n') # This message tells us what players are in the game. We need this # information to get the player ID ("systemSeatId"). The deck will have already been found, # or otherwise we cannot tell what is going on. if 'matchGameRoomStateChangedEvent' in d: Log('Found matchGameRoomStateChangedEvent; clearing GameInfo') game_info = GameInfo() try: match_type = d['matchGameRoomStateChangedEvent']['gameRoomInfo']['gameRoomConfig']['eventId'] Log('Found match type: {0}\n'.format(match_type)) game_info.match_type = match_type # if deck_handler is not None: # deck_handler.match_type = match_type # # Clear now that it is stored in the deck # match_type = '?' except KeyError: pass try: player_list = d['matchGameRoomStateChangedEvent']['gameRoomInfo']['gameRoomConfig']['reservedPlayers'] except KeyError: Log('Mangled matchGameRoomStateChangedEvent event') continue for p in player_list: if p['playerName'] == user_name: player_number = p['systemSeatId'] game_info.player_number = player_number if deck_handler is not None: deck_handler.player_number = player_number else: game_info.other_player = p['playerName'] Log('Found other player: {0}\n'.format(game_info.other_player)) # if deck_handler is not None: # deck_handler.other_player = p['playerName'] # Log('Found other player: {0}\n'.format(deck_handler.other_player)) # greToClientEvents are broadcast messages. Includes the deck list. # Can split information between multiple "messages" within the transaction. if 'greToClientEvent' in d: transact = d['transactionId'] msg_list = d['greToClientEvent']['greToClientMessages'] Log('Transaction: {0}\n'.format(transact)) # pprint.pprint(msg.keys()) else: # Nothing to process continue if 'timestamp' in d: # TODO: figure out how to convert timestamp to useful format. # The other format (XML) has clean time stamps, but the formatting is multi-line and less stable. timestamp = d['timestamp'] else: timestamp = '?' # We need see whether a new deck message has been sent. new_handler = FindDeck(transact, msg_list) if new_handler is not None: deck_handler = new_handler deck_handler.user_name = user_name deck_handler.file_name = filename deck_handler.build = build deck_handler.match_type = match_type player_number = None # If we have a deck handler, we can now attempt to process messages, which are mulligan responses. if deck_handler is not None: for msg in msg_list: deck_handler.handle_message(transact, msg, row) # Once we have the deck, the mulligan count, and the initial draw, that's it. if deck_handler.IsDone(): if deck_handler.user_name.startswith('DeadlyK1tten'): deck_handler.user_name = 'Amaz1ngK1tten' lline = deck_handler.Format(game_info, timestamp) handle_last.write(lline) if handle_prod is not None: handle_prod.write(lline) deck_handler.Clear()