class BuddyRanker(): def __init__(self, args): # Setup Logger self.logger = logging.Logger('BuddyRanker') console = logging.StreamHandler() console.setLevel('DEBUG' if args.debug else 'INFO') formatter = logging.Formatter( fmt='%(asctime)s %(levelname)-5s %(message)s', datefmt='%Y-%m-%d %H:%M:%S') console.setFormatter(formatter) self.logger.addHandler(console) # Setup Goggle Sheets Credentials and options self.SpreadSheet = SpreadSheet(gsecrets=args.g_secrets, url=args.g_url) self.g_scores_sheet = "Scores" self.g_ranking_sheet = "Rankings" self.g_rank_header = ["Player", "Relative Rank (out of 1000)"] self.ignore_upload = args.g_ignore_upload # Setup localdata parametes self.localdata = args.localdata self.headers = [ 'Player 1', 'Player 2', 'Score Player 1', 'Score Player 2' ] # Bradley Terry related parameters self.init_rank = args.init_rank self.tol = args.tol self.max_itt = args.max_itt def read_local_file(self): lst = [] with open(self.file, 'rb') as csvfile: reader = csv.reader(csvfile, delimiter=',') for row in reader: if len(row) != 4: self.logger.critical( 'FATAL: CSV should contain rows with 4 columns only') exit(1) # Keep same format a google sheet lst.append(dict(zip(self.headers, row))) return lst def get_player_list(self, game_data, headers): players = set() for game in game_data: players.add(game[headers[0]]) players.add(game[headers[1]]) return list(players) def init_fake_winners(self, game_data, headers): players = self.get_player_list(game_data=game_data, headers=headers) wins = {} for player in players: for opp in players: if opp == player: continue if player not in wins.keys(): wins[player] = {opp: 1} else: wins[player][opp] = 1 return wins def get_game_data(self): if self.localdata != None: game_data = self.read_local_file() headers = self.headers else: game_data = self.SpreadSheet.open_sheet( spreadsheet=self.g_scores_sheet) headers = list(reversed(game_data[0].keys())) return game_data, headers def setup_wins(self, game_data, headers): wins = self.init_fake_winners(game_data=game_data, headers=headers) for game in game_data: player1 = game[headers[0]] player2 = game[headers[1]] player1_score = int(game[headers[2]]) player2_score = int(game[headers[3]]) self.logger.debug( "Player1: %s, Score: %s Player2: %s, Score: %s", player1, player1_score, player2, player2_score) # Determine who won game if player1_score > player2_score: self.logger.debug("Player 1 Wins: %s", player1) wins[player1][player2] += 1 elif player2_score > player1_score: self.logger.debug("Player 2 Wins: %s", player2) wins[player2][player1] += 1 else: self.logger.critical("No Ties allowed, continuing") self.logger.info("Wins calculated: %s", wins) return wins def get_games_played(self, wins, player1, player2): tot = 0 if player1 in wins.keys() and player2 in wins[player1].keys(): tot += wins[player1][player2] if player2 in wins.keys() and player1 in wins[player2].keys(): tot += wins[player2][player1] return tot def get_vector_diff(self, list1, list2): # Lists must be of same length diff = 0 for i in range(0, len(list1)): diff += abs(float(list1[i]) - float(list2[i])) return diff def norm_dict(self, ranks): factor = 1.0 / sum(ranks.itervalues()) return {k: v * factor for k, v in ranks.iteritems()} def train_ranking(self): game_data, headers = self.get_game_data() wins = self.setup_wins(game_data=game_data, headers=headers) players = self.get_player_list(game_data=game_data, headers=headers) total_wins = {player: sum(wins[player].values()) for player in wins} # Generate initial rank vector rank = self.norm_dict({player: self.init_rank for player in players}) itt = 0 while itt < self.max_itt: itt += 1 last_rank = rank.copy() for player in players: if player not in wins.keys(): rank[player] = 0 else: tot = 0 for opp in wins[player]: tot_games = self.get_games_played(wins=wins, player1=player, player2=opp) tot = tot_games / (last_rank[player] + last_rank[opp]) rank[player] = total_wins[player] / tot # normalize rank = self.norm_dict(rank) if float(self.get_vector_diff(last_rank.values(), rank.values())) < float(self.tol): self.logger.info("Converged after %s itterations", itt) break elif itt % 10 == 0: self.logger.info("Working on itteration %s ", itt) self.logger.info("Completed ranks: %s", rank) return rank def upload_sheet(self, ranks): if self.ignore_upload: self.logger.info( "Did not upload to Google Sheets due to ignore_upload flag") return # SpreadSheet.upload_sheet uploads 2D array to GSheets data = [] for key in ranks: data.append([key, int(ranks[key] * 1000)]) data = sorted(data, key=lambda x: (x[1]), reverse=True) data.insert(0, self.g_rank_header) self.logger.info("Uploading to Google Sheets") self.SpreadSheet.upload_sheet(data=data, spreadsheet=self.g_ranking_sheet) self.logger.info("Completed upload to Google Sheets")