def save(self): database.execute( "INSERT INTO pending_matches " "(message_id, player1, player2, time) " "VALUES (?, ?, ?, ?)", (self.message_id, self.player1, self.player2, self.time)) database.commit()
async def cleanup_for_match(self, match): database.execute("DELETE FROM pending_matches WHERE message_id = ?", (match.message_id, )) database.commit() self.message_map.pop(match.message_id) self.player_map.pop(match.player1) self.player_map.pop(match.player2) await (await self.announcement_channel.fetch_message(match.message_id )).clear_reactions()
async def process_player(self, player, flag, embed): old_rating = glicko2.Rating(player["rating_mu"], player["rating_phi"], player["rating_sigma"]) new_rating = old_rating if flag != 0 else \ glicko2.Glicko2().rate_1vs1(old_rating, old_rating)[1] database.execute( "UPDATE players " "SET rating_mu = ?, rating_phi = ?, rating_sigma = ? " "WHERE id = ?", (new_rating.mu, new_rating.phi, new_rating.sigma, player["id"])) database.commit() await match_message_helper.add_report_field(embed, player, None, old_rating.mu, old_rating.phi, new_rating.mu, new_rating.phi)
async def process_match_complete(self, match): player1 = match.player1_data old_rating1 = glicko2.Rating(player1["rating_mu"], player1["rating_phi"], player1["rating_sigma"]) player2 = match.player2_data old_rating2 = glicko2.Rating(player2["rating_mu"], player2["rating_phi"], player2["rating_sigma"]) if match.player1_score > match.player2_score: new_rating1, new_rating2 = (glicko2.Glicko2().rate_1vs1( old_rating1, old_rating2)) else: new_rating2, new_rating1 = (glicko2.Glicko2().rate_1vs1( old_rating2, old_rating1)) cursor = database.execute( """ INSERT INTO matches ( player1, player2, player1_score, player2_score, player1_old_mu, player1_old_phi, player1_old_sigma, player1_new_mu, player1_new_phi, player1_new_sigma, player2_old_mu, player2_old_phi, player2_old_sigma, player2_new_mu, player2_new_phi, player2_new_sigma ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) """, (player1["id"], player2["id"], match.player1_score, match.player2_score, old_rating1.mu, old_rating1.phi, old_rating1.sigma, new_rating1.mu, new_rating1.phi, new_rating1.sigma, old_rating2.mu, old_rating2.phi, old_rating2.sigma, new_rating2.mu, new_rating2.phi, new_rating2.sigma)) database.commit() embed = discord.Embed(type="rich", title="Match recorded", color=0x00E323) await match_message_helper.add_report_field( embed, player1, match.player1_score, old_rating1.mu, old_rating1.phi, new_rating1.mu, new_rating1.phi) await match_message_helper.add_report_field( embed, player2, match.player2_score, old_rating2.mu, old_rating2.phi, new_rating2.mu, new_rating2.phi) embed.set_footer(text=f"Match ID: {cursor.lastrowid}") await self.output_channel.send(embed=embed) await utils.update_role(match.player1, old_rating1.mu, old_rating1.phi, new_rating1.mu, new_rating1.phi) await utils.update_role(match.player2, old_rating2.mu, old_rating2.phi, new_rating2.mu, new_rating2.phi) await self.cleanup_for_match(match)
async def register(self, ctx, *platforms_raw): platforms = self.sanitize_platforms(platforms_raw) if len(platforms) == 0: await ctx.send( "You need to provide a valid platform that is one of the following: " + ", ".join(utils.platform_names) + ".") return platform_names = ", ".join( utils.format_platform_name(platform) for platform in platforms) player = database.execute( "SELECT platforms, rating_mu, rating_phi FROM players WHERE id = ?", (ctx.author.id, )).fetchone() if player is None: # First time registering. database.execute( "INSERT INTO players (id, platforms) VALUES (?, ?)", (ctx.author.id, " ".join(platforms))) database.commit() await utils.update_role(ctx.author.id, None, None, 1500, 350) await ctx.send(f"Signed up for {platform_names}.") else: # Already registered. no_new_entries = True player_platforms = player["platforms"].split() for platform in platforms: if platform not in player_platforms: player_platforms.append(platform) no_new_entries = False if no_new_entries: await ctx.send( f"You're already signed up for {platform_names}.") return else: database.execute( "UPDATE players SET platforms = ? WHERE id = ?", (" ".join(player_platforms), ctx.author.id)) database.commit() await utils.update_role(ctx.author.id, None, None, player["rating_mu"], player["rating_phi"]) await ctx.send(f"Signed up for {platform_names}.")
async def update_displayname(self, ctx, *name): name = " ".join(name).strip() if database.execute( "SELECT EXISTS (SELECT 1 FROM players WHERE id = ? AND platforms <> '')", (ctx.author.id, )).fetchone()[0] == 0: await ctx.send("You are not registered yet.") return if name == "": database.execute( "UPDATE players SET display_name = NULL WHERE id = ?", (ctx.author.id, )) database.commit() await ctx.send("Cleared display name.") else: if database.execute( "SELECT EXISTS (SELECT 1 FROM players WHERE display_name = ?)", (name, )).fetchone()[0] == 1: await ctx.send( f"The display name \"{utils.escape_markdown(name)}\" is already in use by another player." ) return database.execute( "UPDATE players SET display_name = ? WHERE id = ?", (name, ctx.author.id)) database.commit() await ctx.send( f"Display name set to \"{utils.escape_markdown(name)}\".")
def setup(self): if self.running: return self.running = True self.bot_id = utils.bot.user.id self.announcement_channel = utils.bot.get_channel( get_config("matchmaking_announcement_channel")) self.announcement_channel_id = self.announcement_channel.id self.output_channel = utils.bot.get_channel( get_config("command_channel")) for row in (database.execute( "SELECT rowid, * FROM pending_matches").fetchall()): self.add_match(PendingMatch(**row.dict)) self.match_lifetime = get_config("pending_match_lifetime") asyncio.create_task(self.clear_matches()) utils.bot.add_listener(self.on_reaction, "on_raw_reaction_add")
async def on_reaction(self, data): if data.user_id == self.bot_id: return if data.message_id != self.matchfinding_message.id: return if data.emoji.id not in self.emoji_mapping: return platform_data = self.emoji_mapping[data.emoji.id] if data.event_type == "REACTION_ADD": if data.user_id in match_manager.player_map: await remove_reaction(self.matchfinding_message, data.emoji, await utils.get_member(data.user_id)) return if data.user_id in self.player_map: player = self.player_map[data.user_id] else: player_data = database.execute( "SELECT * FROM players WHERE id = ? AND platforms <> ''", (data.user_id, )).fetchone() if player_data is None: await remove_reaction(self.matchfinding_message, data.emoji, await utils.get_member(data.user_id)) return player = Player(player_data, self) if platform_data[0] not in player.platforms: await remove_reaction(self.matchfinding_message, data.emoji, await utils.get_member(data.user_id)) return self.player_map[player.player_id] = player self.pools[platform_data[1]].add(player) player.platform_count += 1 await self.find_matches() else: if data.user_id not in self.player_map: return player = self.player_map[data.user_id] pool = self.pools[platform_data[1]] if player in pool: pool.discard(player) player.platform_count -= 1 if player.platform_count < 1: self.player_map.pop(player.player_id)
async def info_player_user(self, ctx, user: discord.User): """ Gets a Player's information from a discord.User. This is done by getting the ID of the User and passing it to get_player(). :param ctx: Context, comes with every command :param user: A user, which could be a mention, an ID, or anything else Discord can translate into a user. """ player = database.execute( "SELECT * FROM players WHERE id=? AND platforms <> ''", (user.id, )).fetchone() if player is None: await ctx.send( f"The user \"{utils.escape_markdown(user.display_name)}\" isn't registered." ) return await self.send_player_info(ctx, player, await utils.get_member(user.id))
async def add_match_embed_player(self, embed, match, player): player = "player" + player player_row = database.execute( "SELECT display_name FROM players WHERE id = ?", (match[player], )).fetchone() name = player_row["display_name"] if name is None: user = await utils.get_member(match[player]) name = "[No name.]" if user is None else user.display_name rating_change = match[player + "_rating_change"] rating_change_sign = '+' if rating_change >= 0 else '\u2013' embed.add_field( name=utils.escape_markdown(name), value= (f"**{match[player + '_score']}**\n" f"{int(match[player + '_old_mu'])} \u00B1 {int(2 * match[player + '_old_phi'])} \u2192 " f"{int(match[player + '_new_mu'])} \u00B1 {int(2 * match[player + '_new_phi'])}\n" f"{rating_change_sign} {abs(rating_change)}\n" + utils.get_rank_with_comparison( match[player + '_old_mu'], match[player + '_old_phi'], match[player + '_new_mu'], match[player + '_new_phi'])))
async def info_player_name(self, ctx, *name): """ Gets a Player's information from a string. This is done by passing the name to spreadsheets.find_player_id(name) :param ctx: Context, comes with every command :param name: String """ name = " ".join(name).strip() if name == "": await ctx.send_help(self.info_player_name) return player = database.execute( "SELECT * FROM players WHERE display_name=? AND platforms <> ''", (name, )).fetchone() if player is None: await ctx.send( "There is no registered player with the display name " f"\"{utils.escape_markdown(name)}\".") return await self.send_player_info(ctx, player, await utils.get_member(player["id"]))
async def leaderboard(self, ctx): players = database.execute( "SELECT id, display_name, rating_mu, rating_phi " "FROM players WHERE platforms <> '' AND rating_phi < 150 " "ORDER BY rating_mu DESC, rating_phi ASC LIMIT 10").fetchall() if len(players) == 0: await ctx.send( "There are no players with ranks at the moment, so no leaders to display." ) return index = 0 message = "**Top Puyo players**" for player in players: index += 1 message += ("\n" + str(index) + ". " + utils.escape_markdown( await self.get_player_name(player)) + " | " + str(int(player["rating_mu"])) + " \u00B1 " + str(int(2 * player["rating_phi"])) + " | " + utils.get_rank(player["rating_mu"]).name) await ctx.send(message)
async def info_match(self, ctx, ids): try: match_id = int(ids) except ValueError: match_id = 0 if match_id < 1: await ctx.send(f"`{ids}` is not a positive integer.") return match = database.execute("SELECT * FROM matches WHERE id = ?", (match_id, )).fetchone() if match is None: await ctx.send(f"There is not yet a match with ID {match_id}.") return embed = discord.Embed(type="rich", title="Match information", color=0xFF8337) await self.add_match_embed_player(embed, match, "1") await self.add_match_embed_player(embed, match, "2") embed.set_footer(text=f"Match ID: {match_id}") embed.timestamp = match["date"] await ctx.send(embed=embed)
async def info_player_username(self, ctx, platform, *username): username = "******".join(username).strip() if username == "": await ctx.send_help(self.info_player_username) return platform = platform.casefold() if not platform in utils.platform_name_mapping: await ctx.send( "You need to provide a valid platform that is one of the following: " + ", ".join(utils.platform_names) + ".") return player = database.execute( f"SELECT * FROM players WHERE username_{platform}=?", (username, )).fetchone() if player is None: await ctx.send( "There is no registered player with the username " f"\"{utils.escape_markdown(username)}\" on {utils.format_platform_name(platform)}." ) return await self.send_player_info(ctx, player, await utils.get_member(player["id"]))
async def update_username(self, ctx, platform, *name): platform = platform.casefold() if not platform in utils.platform_name_mapping: await ctx.send( "You need to provide a valid platform that is one of the following: " + ", ".join(utils.platform_names) + ".") return name = " ".join(name).strip() player = database.execute( "SELECT platforms FROM players WHERE id = ? AND platforms <> ''", (ctx.author.id, )).fetchone() if player is None: await ctx.send("You are not registered yet.") return if platform not in player[0].split(): await ctx.send( f"You are not signed up for {utils.format_platform_name(platform)}." ) return if name == "": database.execute( f"UPDATE players SET username_{platform} = NULL WHERE id = ?", (ctx.author.id, )) database.commit() await ctx.send( f"Cleared username for {utils.format_platform_name(platform)}." ) else: if database.execute( f"SELECT EXISTS (SELECT 1 FROM players WHERE username_{platform} = ?)", (name, )).fetchone()[0] == 1: await ctx.send( f"The username \"{utils.escape_markdown(name)}\" on " f"{utils.format_platform_name(platform)} is already in use by another player." ) return database.execute( f"UPDATE players SET username_{platform} = ? WHERE id = ?", (name, ctx.author.id)) database.commit() await ctx.send( f"Username on {utils.format_platform_name(platform)} set to " f"\"{utils.escape_markdown(name)}\".")
def get_player_by_ID(id_in): return database.execute( "SELECT id, display_name, rating_mu, rating_phi, rating_sigma " "FROM players WHERE id=? AND platforms<>''", (id_in, )).fetchone()
async def send_player_info(cls, ctx, player, user=None): """ Constructs an embed containing the player info and then sends it as response to the info command. :param ctx: Context to send to. :param player: The player database row object. :param user: The Discord user object. """ rank = utils.get_rank(player["rating_mu"], player["rating_phi"]) embed = discord.Embed( type="rich", title="Puyo Training Grounds player info", description=("" if user is None else f"{user.name}#{user.discriminator}") + f"\nID {player['id']}", colour=rank.color) embed.set_author(name=player["display_name"] or ( "[No name.]" if user is None else user.display_name)) if user is not None: embed.set_thumbnail(url=str(user.avatar_url)) embed.set_footer(text="Registration date") embed.timestamp = player["registration_date"] embed.add_field( name="Platforms", value="\n".join([ utils.format_platform_name(platform) + ("" if player["username_" + platform] is None else utils.escape_markdown(f" ({player['username_' + platform]})")) for platform in player["platforms"].split() ]), inline=False) embed.add_field( name="Rating", value= f"{int(player['rating_mu'])} \u00B1 {int(2 * player['rating_phi'])}" ) # Why double the phi? Because phi is just half of the distance to the boundary of the 95% confidence # interval. Source: https://www.glicko.net/glicko/glicko2.pdf (lines 7 to 11). embed.add_field(name="Rank", value=rank.name) if player["rating_phi"] < 150: embed.add_field( name="Position", value="#" + str( database.execute( "SELECT COUNT(*) FROM players WHERE " "platforms <> '' AND rating_phi < 150 AND " "(rating_mu > :mu OR rating_mu = :mu AND rating_phi < :phi)", { "mu": player["rating_mu"], "phi": player["rating_phi"] }).fetchone()[0] + 1)) matches, wins = database.execute( "SELECT COUNT(*), " "COUNT(" "CASE WHEN (player1 = :id) == (player1_score > player2_score) " "THEN 1 ELSE NULL END" ") FROM matches WHERE player1=:id OR player2=:id", { "id": player["id"] }).fetchone().tuple embed.add_field(name="Matches", value=str(matches)) if matches != 0: ratio = wins * 10000 // matches decimal = ratio % 100 embed.add_field( name="Wins", value= f"{wins} ({ratio // 100}.{'0' if decimal < 10 else ''}{decimal}%)" ) await ctx.send(embed=embed)
async def unregister(self, ctx, *platforms_raw): if ctx.author.id in matchfinder.player_map: await ctx.send( "You are currently in the matckmaking queue. Leave the queue before unregistering." ) return if ctx.author.id in match_manager.player_map: await ctx.send( "You have a match pending. Complete the match before unregistering." ) return nuke = len(platforms_raw) != 0 and platforms_raw[0].casefold() == "all" platforms = self.sanitize_platforms(platforms_raw) if not nuke and len(platforms) == 0: await ctx.send( "You need to provide a valid platform that is one of the following: " + ", ".join(utils.platform_names) + ".") return platform_names = ", ".join( utils.format_platform_name(platform) for platform in platforms) player = database.execute( "SELECT platforms, rating_mu, rating_phi FROM players WHERE id = ? AND platforms <> ''", (ctx.author.id, )).fetchone() if player is None: await ctx.send(f"You are not signed up here, no worries.") else: old_player_platforms = player["platforms"].split() new_player_platforms = ([] if nuke else [ platform for platform in old_player_platforms if platform not in platforms ]) if len(new_player_platforms) != len(old_player_platforms): platform_nullify = ", ".join(f"username_{platform} = NULL" for platform in platforms) zenkeshita = nuke or len(new_player_platforms) == 0 database.execute( """ UPDATE players SET platforms = "", display_name = NULL, username_pc = NULL, username_switch = NULL, username_ps4 = NULL WHERE id = :id """ if zenkeshita else f"UPDATE players SET platforms = :platforms, {platform_nullify} WHERE id = :id", { "platforms": " ".join(new_player_platforms), "id": ctx.author.id }) database.commit() await ctx.send( "Unregistered from __all__ platforms. You are no longer in the system." if nuke else f"Unregistered from {platform_names}." + (" __You are no longer in the system.__" if len(new_player_platforms) == 0 else "")) if zenkeshita: await utils.update_role(ctx.author.id, player["rating_mu"], player["rating_phi"], None, None) else: await ctx.send(f"You are not signed up for {platform_names}.")