From 34da084a942405707ba3b243f7baca473dd75d6a Mon Sep 17 00:00:00 2001 From: random-nick Date: Tue, 17 Jul 2018 18:18:37 +0200 Subject: [PATCH] Add !rolestats command (#344) This might me a little confusing, because it calculates the number of players that won (similar to !playerstats) instead of the number of times a team won (like !gamestats). This way it gives the proper winrate of a player playing that role, but gives an inflated number of total victories and games in cases where there are multiples of a role in the same game. Maybe it would be better to hide the actual numbers and show only the winrates in order to avoid confusion. --- src/db.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ src/settings.py | 1 + src/utilities.py | 22 +++++++++++++++- src/wolfgame.py | 42 ++++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 1 deletion(-) diff --git a/src/db.py b/src/db.py index c9175fb..2b6a7e2 100644 --- a/src/db.py +++ b/src/db.py @@ -463,6 +463,74 @@ def get_game_totals(mode): else: return "Total games (\u0002{0}\u0002): {1} | {2}".format(mode, total_games, ", ".join(totals)) +def get_role_stats(role, mode=None): + conn = _conn() + c = conn.cursor() + + if mode is None: + c.execute("""SELECT + gpr.role AS role, + SUM(gp.team_win) AS team, + SUM(gp.indiv_win) AS indiv, + SUM(gp.team_win OR gp.indiv_win) AS overall, + COUNT(1) AS total + FROM game g + JOIN game_player gp + ON gp.game = g.id + JOIN game_player_role gpr + ON gpr.game_player = gp.id + WHERE role = ? + GROUP BY role""", (role,)) + else: + c.execute("""SELECT + gpr.role AS role, + g.gamemode AS gamemode, + SUM(gp.team_win) AS team, + SUM(gp.indiv_win) AS indiv, + SUM(gp.team_win OR gp.indiv_win) AS overall, + COUNT(1) AS total + FROM game g + JOIN game_player gp + ON gp.game = g.id + JOIN game_player_role gpr + ON gpr.game_player = gp.id + WHERE role = ? + AND gamemode = ? + GROUP BY role, gamemode""", (role, mode)) + + row = c.fetchone() + if row and row[2] is not None: + if mode is None: + return ("\u0002{0[0]}\u0002 | Team winners: {0[1]} ({1:.0%}), " + "Individual winners: {0[2]} ({2:.0%}), Overall winners: {0[3]} ({3:.0%}), Total games: {0[4]}.").format(row, row[1]/row[4], row[2]/row[4], row[3]/row[4]) + else: + return ("\u0002{0[0]}\u0002 in \u0002{0[1]}\u0002 | Team winners: {0[2]} ({1:.0%}), " + "Individual winners: {0[3]} ({2:.0%}), Overall winners: {0[4]} ({3:.0%}), Total games: {0[5]}.").format(row, row[2]/row[5], row[3]/row[5], row[4]/row[5]) + else: + if mode is None: + return "No stats for \u0002{0}\u0002.".format(role) + else: + return "No stats for \u0002{0}\u0002 in \u0002{1}\u0002.".format(role, mode) + +def get_role_totals(): + conn = _conn() + c = conn.cursor() + c.execute("SELECT COUNT(1) FROM game") + total_games = c.fetchone()[0] + if not total_games: + return "No games played." + c.execute("""SELECT + gpr.role AS role, + COUNT(1) AS count + FROM game_player_role gpr + GROUP BY role + ORDER BY count DESC""") + totals = [] + for row in c: + totals.append("\u0002{0}\u0002: {1}".format(*row)) + return "Total games: {0} | {1}".format(total_games, ", ".join(totals)) + + def get_warning_points(acc, hostmask): peid, plid = _get_ids(acc, hostmask) conn = _conn() diff --git a/src/settings.py b/src/settings.py index f1f1cb1..cf981e8 100644 --- a/src/settings.py +++ b/src/settings.py @@ -23,6 +23,7 @@ VOTES_RATE_LIMIT = 60 ADMINS_RATE_LIMIT = 300 GSTATS_RATE_LIMIT = 0 PSTATS_RATE_LIMIT = 0 +RSTATS_RATE_LIMIT = 0 TIME_RATE_LIMIT = 10 START_RATE_LIMIT = 10 # (per-user) WAIT_RATE_LIMIT = 10 # (per-user) diff --git a/src/utilities.py b/src/utilities.py index baa5a63..b2842b2 100644 --- a/src/utilities.py +++ b/src/utilities.py @@ -1,4 +1,5 @@ import itertools +import functools import fnmatch import re @@ -9,7 +10,7 @@ from src.events import Event from src.messages import messages __all__ = ["pm", "is_fake_nick", "mass_mode", "mass_privmsg", "reply", - "is_user_simple", "is_user_notice", "in_wolflist", + "is_user_simple", "is_user_notice", "in_wolflist", "complete_role", "relay_wolfchat_command", "irc_lower", "irc_equals", "match_hostmask", "is_owner", "is_admin", "plural", "singular", "list_players", "get_role", "get_roles", "role_order", "break_long_message", @@ -375,4 +376,23 @@ def get_victim(cli, nick, victim, in_chan, self_in_list=False, bot_in_list=False class InvalidModeException(Exception): pass +def complete_role(var, wrapper, role): + if role not in var.ROLE_GUIDE.keys(): + special_keys = {"lover"} + evt = Event("get_role_metadata", {}) + evt.dispatch(var, "special_keys") + special_keys = functools.reduce(lambda x, y: x | y, evt.data.values(), special_keys) + if role.lower() in var.ROLE_ALIASES: + matches = (var.ROLE_ALIASES[role.lower()],) + else: + matches = complete_match(role, var.ROLE_GUIDE.keys() | special_keys) + if not matches: + wrapper.reply(messages["no_such_role"].format(role)) + return False + if len(matches) > 1: + wrapper.reply(messages["ambiguous_role"].format(", ".join(matches))) + return False + return matches[0] + return role + # vim: set sw=4 expandtab: diff --git a/src/wolfgame.py b/src/wolfgame.py index ba13f5e..46adf86 100644 --- a/src/wolfgame.py +++ b/src/wolfgame.py @@ -72,6 +72,7 @@ var.LAST_VOTES = None var.LAST_ADMINS = None var.LAST_GSTATS = None var.LAST_PSTATS = None +var.LAST_RSTATS = None var.LAST_TIME = None var.LAST_START = {} var.LAST_WAIT = {} @@ -977,6 +978,7 @@ def join_player(var, wrapper, who=None, forced=False, *, sanity=True): var.LAST_STATS = None # reset var.LAST_GSTATS = None var.LAST_PSTATS = None + var.LAST_RSTATS = None var.LAST_TIME = None with var.WARNING_LOCK: @@ -5574,6 +5576,46 @@ def my_stats(cli, nick, chan, rest): rest = rest.split() player_stats.func(cli, nick, chan, " ".join([nick] + rest)) +@command("rolestats", "rstats", pm=True) +def role_stats(var, wrapper, rest): + """Gets the stats for a given role in a given gamemode or lists role totals across all games if no role is given.""" + if (wrapper.target != users.Bot and var.LAST_RSTATS and var.RSTATS_RATE_LIMIT and + var.LAST_RSTATS + timedelta(seconds=var.RSTATS_RATE_LIMIT) > datetime.now()): + wrapper.pm(messages["command_ratelimited"]) + return + + if wrapper.target != users.Bot: + var.LAST_RSTATS = datetime.now() + + if var.PHASE not in ("none", "join") and wrapper.target is not channel.Main: + wrapper.pm(messages["stats_wait_for_game_end"]) + return + + rest = rest.split() + if len(rest) == 0: + # this is a long message + wrapper.pm(db.get_role_totals()) + elif len(rest) == 1 or (rest[-1] == "all" and rest.pop()): + role = complete_role(var, wrapper, " ".join(rest)) + if role: + wrapper.reply(db.get_role_stats(role)) + else: + role = complete_role(var, wrapper, " ".join(rest[:-1])) + if not role: + return + gamemode = rest[-1] + if gamemode not in var.GAME_MODES.keys(): + matches = complete_match(gamemode, var.GAME_MODES.keys()) + if len(matches) == 1: + gamemode = matches[0] + if not matches: + wrapper.pm(messages["invalid_mode"].format(rest[1])) + return + if len(matches) > 1: + wrapper.pm(messages["ambiguous_mode"].format(rest[1], ", ".join(matches))) + return + wrapper.reply(db.get_role_stats(role, gamemode)) + # Called from !game and !join, used to vote for a game mode def vote_gamemode(var, wrapper, gamemode, doreply): if var.FGAMED: