From 1994e7330f37c5ce38fec5851f6b7edb5f8ab581 Mon Sep 17 00:00:00 2001 From: Yizhe Shen Date: Sun, 9 Feb 2014 03:42:56 -0500 Subject: [PATCH 1/7] Added commands to access player stats. Loosely based on code from jcao219. New commands are "player", "p", "mystats", "me". The commands access existing stats stored in the game database (data.sqlite3). --- modules/wolfgame.py | 38 +++++++++++++++++++++++++++++++++++--- settings/wolfgame.py | 8 +++++++- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/modules/wolfgame.py b/modules/wolfgame.py index 03622b3..1d76580 100644 --- a/modules/wolfgame.py +++ b/modules/wolfgame.py @@ -2868,13 +2868,45 @@ def flastgame(cli, nick, rest): if rest.strip(): aftergame(cli, rawnick, rest) - - - @cmd("flastgame", admin_only=True, raw_nick=True) def _flastgame(cli, nick, chan, rest): flastgame(cli, nick, rest) + +@cmd("player", "p") +def player_stats(cli, nick, chan, rest): + """Gets the specified player's stats based on role""" + if var.PHASE not in ("none", "join"): + cli.notice(nick, "Wait until the game is over to view stats.") + return + + params = rest.split() + if len(params) < 2: + cli.notice(nick, "Supply a nick and role name.") + return + + player = params[0] + role = params[1] + + msg = var.get_player_stats(player, role) + if msg == "": + cli.notice(nick, "No stats for {0} as {1}.".format(player, role)) + else: + cli.notice(nick, msg) + +@pmcmd("player", "p") +def player_stats_pm(cli, nick, rest): + player_stats(cli, nick, "", rest) + +@pmcmd("mystats", "me") +def my_stats_pm(cli, nick, rest): + my_stats(cli, nick, "", rest) + +@cmd("mystats", "me") +def my_stats(cli, nick, chan, rest): + player_stats(cli, nick, chan, "{0} {1}".format(nick, rest)) + + before_debug_mode_commands = list(COMMANDS.keys()) before_debug_mode_pmcommands = list(PM_COMMANDS.keys()) diff --git a/settings/wolfgame.py b/settings/wolfgame.py index c318146..7300586 100644 --- a/settings/wolfgame.py +++ b/settings/wolfgame.py @@ -250,6 +250,7 @@ def add_simple_rolemsg(clk): def remove_ping(clk): with conn: c.execute('DELETE from ping where cloak=?', (clk,)) + def add_ping(clk): with conn: c.execute('INSERT into ping VALUES (?)', (clk,)) @@ -277,5 +278,10 @@ def update_role_stats(acc, role, won, iwon): c.execute("INSERT OR REPLACE INTO rolestats VALUES (?,?,?,?,?)", (acc, role, wins, iwins, total)) - +def get_player_stats(player, role): + with conn: + for row in c.execute("SELECT * FROM rolestats WHERE player = '%s' AND role = '%s'" % (player, role)): + return "As {2}, {0} has {3} team wins, {4} individual wins, and {5} total games.".format(player, *row) + else: + return "" From cbae0291f86a102e1cc79537d1e0ffc60da669b1 Mon Sep 17 00:00:00 2001 From: Yizhe Shen Date: Sun, 9 Feb 2014 18:44:59 -0500 Subject: [PATCH 2/7] Added game stats to database and "gamestats" command. Added a game stats table to game DB to store wolf/village wins for each game size. Added "gamestats" command to display stored stats. --- modules/wolfgame.py | 27 +++++++++++++++++++++++++++ settings/wolfgame.py | 31 ++++++++++++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/modules/wolfgame.py b/modules/wolfgame.py index 1d76580..b514694 100644 --- a/modules/wolfgame.py +++ b/modules/wolfgame.py @@ -837,6 +837,12 @@ def stop_game(cli, winner = ""): var.update_role_stats(acc, rol, won, iwon) + size = len(var.list_players()) + len(var.DEAD) + if winner == "wolves": + var.update_game_stats(size, False, True) + elif winner == "villagers": + var.update_game_stats(size, True, False) + reset(cli) # This must be after reset(cli) @@ -2872,6 +2878,27 @@ def flastgame(cli, nick, rest): def _flastgame(cli, nick, chan, rest): flastgame(cli, nick, rest) +@cmd("gamestats", "gstats") +def game_stats(cli, nick, chan, rest): + """Gets the game stats for a given game size""" + if var.PHASE not in ("none", "join"): + cli.notice(nick, "Wait until the game is over to view stats.") + return + + if rest == "": + cli.notice(nick, "Supply a game size") + return + + if not rest.isdigit(): + cli.notice(nick, "Please enter an integer.") + return + + size = int(rest.strip()) + msg = var.get_game_stats(size) + if msg == "": + cli.msg(chan, "No stats for {0} player games.".format(size)) + else: + cli.msg(chan, msg) @cmd("player", "p") def player_stats(cli, nick, chan, rest): diff --git a/settings/wolfgame.py b/settings/wolfgame.py index 7300586..da9fb81 100644 --- a/settings/wolfgame.py +++ b/settings/wolfgame.py @@ -223,6 +223,11 @@ with conn: 'teamwins SMALLINT, individualwins SMALLINT, totalgames SMALLINT, '+ 'UNIQUE(player, role))')) + + c.execute(('CREATE TABLE IF NOT EXISTS gamestats (size SMALLINT, villagewins SMALLINT, ' + + 'wolfwins SMALLINT, totalgames SMALLINT, UNIQUE(size))')) + + if OPT_IN_PING: c.execute('CREATE TABLE IF NOT EXISTS ping (cloak text)') @@ -257,7 +262,6 @@ def add_ping(clk): def update_role_stats(acc, role, won, iwon): - with conn: wins, iwins, totalgames = 0, 0, 0 @@ -278,6 +282,25 @@ def update_role_stats(acc, role, won, iwon): c.execute("INSERT OR REPLACE INTO rolestats VALUES (?,?,?,?,?)", (acc, role, wins, iwins, total)) +def update_game_stats(size, vwon, wwon): + with conn: + vwins, wwins, total = 0, 0, 0 + + c.execute("SELECT villagewins, wolfwins, totalgames FROM gamestats "+ + "WHERE size = %d" % size) + row = c.fetchone() + if row: + vwins, wwins, total = row + + if vwon: + vwins += 1 + if wwon: + wwins += 1 + total += 1 + + c.execute("INSERT OR REPLACE INTO gamestats VALUES (?,?,?,?)", + (size, vwins, wwins, total)) + def get_player_stats(player, role): with conn: for row in c.execute("SELECT * FROM rolestats WHERE player = '%s' AND role = '%s'" % (player, role)): @@ -285,3 +308,9 @@ def get_player_stats(player, role): else: return "" +def get_game_stats(size): + with conn: + for row in c.execute("SELECT * FROM gamestats WHERE size = %d" % (size)): + return "{0} player games: {1} village wins, {2} wolf wins, and {3} total games.".format(*row) + else: + return "" \ No newline at end of file From a12128b4868bbb7a71fbbeff219d33824fc0d864 Mon Sep 17 00:00:00 2001 From: Yizhe Shen Date: Sun, 9 Feb 2014 21:12:25 -0500 Subject: [PATCH 3/7] Fix for multi-word roles. Also added help definition for "mystats" command. --- modules/wolfgame.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/modules/wolfgame.py b/modules/wolfgame.py index b514694..765d8c1 100644 --- a/modules/wolfgame.py +++ b/modules/wolfgame.py @@ -2902,7 +2902,7 @@ def game_stats(cli, nick, chan, rest): @cmd("player", "p") def player_stats(cli, nick, chan, rest): - """Gets the specified player's stats based on role""" + """Gets the stats for the specified player and role.""" if var.PHASE not in ("none", "join"): cli.notice(nick, "Wait until the game is over to view stats.") return @@ -2913,7 +2913,7 @@ def player_stats(cli, nick, chan, rest): return player = params[0] - role = params[1] + role = " ".join(params[1:]) msg = var.get_player_stats(player, role) if msg == "": @@ -2931,6 +2931,11 @@ def my_stats_pm(cli, nick, rest): @cmd("mystats", "me") def my_stats(cli, nick, chan, rest): + """Gets the your own stats for the specified role.""" + if rest == "": + cli.notice(nick, "Supply a role.") + return + player_stats(cli, nick, chan, "{0} {1}".format(nick, rest)) From 829fd260e25bbe261847f6892be0d38e4cc5f534 Mon Sep 17 00:00:00 2001 From: Yizhe Shen Date: Sun, 9 Feb 2014 22:31:29 -0500 Subject: [PATCH 4/7] Changed behaviour of "gamestats" command. "gamestats" without a parameter will list game totals for all game sizes. The role parameter for player stats commands is now case insensitive. --- modules/wolfgame.py | 6 +++--- settings/wolfgame.py | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/modules/wolfgame.py b/modules/wolfgame.py index 765d8c1..484b1ad 100644 --- a/modules/wolfgame.py +++ b/modules/wolfgame.py @@ -2880,13 +2880,13 @@ def _flastgame(cli, nick, chan, rest): @cmd("gamestats", "gstats") def game_stats(cli, nick, chan, rest): - """Gets the game stats for a given game size""" + """Gets the game stats for a given game size or lists game totals for all game sizes if no game size is given.""" if var.PHASE not in ("none", "join"): cli.notice(nick, "Wait until the game is over to view stats.") return if rest == "": - cli.notice(nick, "Supply a game size") + cli.msg(chan, var.get_game_totals()) return if not rest.isdigit(): @@ -2913,7 +2913,7 @@ def player_stats(cli, nick, chan, rest): return player = params[0] - role = " ".join(params[1:]) + role = " ".join(params[1:]).lower() msg = var.get_player_stats(player, role) if msg == "": diff --git a/settings/wolfgame.py b/settings/wolfgame.py index da9fb81..45e7854 100644 --- a/settings/wolfgame.py +++ b/settings/wolfgame.py @@ -313,4 +313,18 @@ def get_game_stats(size): for row in c.execute("SELECT * FROM gamestats WHERE size = %d" % (size)): return "{0} player games: {1} village wins, {2} wolf wins, and {3} total games.".format(*row) else: - return "" \ No newline at end of file + return "" + +def get_game_totals(): + sizeList = [] + with conn: + for size in range(4, MAX_PLAYERS): + c.execute("SELECT size, totalgames FROM gamestats WHERE size = %d" % size) + row = c.fetchone() + if row: + sizeList.append("{0}p({1})".format(*row)) + + if len(sizeList) == 0: + return "No games have been played." + else: + return "Game totals: %s" % ", ".join(sizeList) From 8b235580a2dbf13ca55dd873c5143b9020f2294a Mon Sep 17 00:00:00 2001 From: Yizhe Shen Date: Sun, 9 Feb 2014 23:35:33 -0500 Subject: [PATCH 5/7] Fix for secure SQL operations. Removed Python string operations from SQL operations. --- settings/wolfgame.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/settings/wolfgame.py b/settings/wolfgame.py index 45e7854..a3c1473 100644 --- a/settings/wolfgame.py +++ b/settings/wolfgame.py @@ -287,7 +287,7 @@ def update_game_stats(size, vwon, wwon): vwins, wwins, total = 0, 0, 0 c.execute("SELECT villagewins, wolfwins, totalgames FROM gamestats "+ - "WHERE size = %d" % size) + "WHERE size=?", (size,)) row = c.fetchone() if row: vwins, wwins, total = row @@ -303,14 +303,14 @@ def update_game_stats(size, vwon, wwon): def get_player_stats(player, role): with conn: - for row in c.execute("SELECT * FROM rolestats WHERE player = '%s' AND role = '%s'" % (player, role)): + for row in c.execute("SELECT * FROM rolestats WHERE player=? AND role=?", (player, role)): return "As {2}, {0} has {3} team wins, {4} individual wins, and {5} total games.".format(player, *row) else: return "" def get_game_stats(size): with conn: - for row in c.execute("SELECT * FROM gamestats WHERE size = %d" % (size)): + for row in c.execute("SELECT * FROM gamestats WHERE size=?", (size,)): return "{0} player games: {1} village wins, {2} wolf wins, and {3} total games.".format(*row) else: return "" @@ -319,7 +319,7 @@ def get_game_totals(): sizeList = [] with conn: for size in range(4, MAX_PLAYERS): - c.execute("SELECT size, totalgames FROM gamestats WHERE size = %d" % size) + c.execute("SELECT size, totalgames FROM gamestats WHERE size=?", (size,)) row = c.fetchone() if row: sizeList.append("{0}p({1})".format(*row)) From 977112826f39763bad0b3f52fb6b4d07063e984d Mon Sep 17 00:00:00 2001 From: Yizhe Shen Date: Mon, 10 Feb 2014 18:37:05 -0500 Subject: [PATCH 6/7] Updated stats text. Player stats command checks for account. Updated game text for stats commands. Fixed error with "mystats" help string. Added additional check in player stats commands to find the associated account before attempting to retrieve stats. --- modules/wolfgame.py | 39 ++++++++++++++++++++++++++++----------- settings/wolfgame.py | 8 ++++---- 2 files changed, 32 insertions(+), 15 deletions(-) diff --git a/modules/wolfgame.py b/modules/wolfgame.py index 484b1ad..9e1c08a 100644 --- a/modules/wolfgame.py +++ b/modules/wolfgame.py @@ -2877,7 +2877,8 @@ def flastgame(cli, nick, rest): @cmd("flastgame", admin_only=True, raw_nick=True) def _flastgame(cli, nick, chan, rest): flastgame(cli, nick, rest) - + + @cmd("gamestats", "gstats") def game_stats(cli, nick, chan, rest): """Gets the game stats for a given game size or lists game totals for all game sizes if no game size is given.""" @@ -2885,39 +2886,48 @@ def game_stats(cli, nick, chan, rest): cli.notice(nick, "Wait until the game is over to view stats.") return + # List all games sizes and totals if no size is given. if rest == "": cli.msg(chan, var.get_game_totals()) return + # Check that size is an integer. if not rest.isdigit(): cli.notice(nick, "Please enter an integer.") return - + + # Attempt to find game stats for the given game size. size = int(rest.strip()) msg = var.get_game_stats(size) if msg == "": - cli.msg(chan, "No stats for {0} player games.".format(size)) + cli.msg(chan, "No stats for \u0002{0}\u0002 player games.".format(size)) else: cli.msg(chan, msg) @cmd("player", "p") def player_stats(cli, nick, chan, rest): - """Gets the stats for the specified player and role.""" + """Gets the stats for the given player and role.""" if var.PHASE not in ("none", "join"): cli.notice(nick, "Wait until the game is over to view stats.") return - + + # Check if we have enough parameters. params = rest.split() if len(params) < 2: cli.notice(nick, "Supply a nick and role name.") return - - player = params[0] + + # Find the player's account if possible. + if params[0] in var.USERS: + acc = var.USERS[params[0]]["account"] + else: + acc = params[0] role = " ".join(params[1:]).lower() - msg = var.get_player_stats(player, role) + # Attempt to find the player's stats. + msg = var.get_player_stats(acc, role) if msg == "": - cli.notice(nick, "No stats for {0} as {1}.".format(player, role)) + cli.notice(nick, "No stats for {0} as {1}.".format(acc, role)) else: cli.notice(nick, msg) @@ -2931,12 +2941,19 @@ def my_stats_pm(cli, nick, rest): @cmd("mystats", "me") def my_stats(cli, nick, chan, rest): - """Gets the your own stats for the specified role.""" + """Gets your own stats for the given role.""" + # Check if role has been given if rest == "": cli.notice(nick, "Supply a role.") return - player_stats(cli, nick, chan, "{0} {1}".format(nick, rest)) + # Check if player is identified + acc = var.USERS[nick]["account"] + if acc == "*": + cli.notice(nick, "You are not identified with NickServ.") + return + + player_stats(cli, nick, chan, "{0} {1}".format(acc, rest)) before_debug_mode_commands = list(COMMANDS.keys()) diff --git a/settings/wolfgame.py b/settings/wolfgame.py index a3c1473..566699a 100644 --- a/settings/wolfgame.py +++ b/settings/wolfgame.py @@ -301,17 +301,17 @@ def update_game_stats(size, vwon, wwon): c.execute("INSERT OR REPLACE INTO gamestats VALUES (?,?,?,?)", (size, vwins, wwins, total)) -def get_player_stats(player, role): +def get_player_stats(acc, role): with conn: - for row in c.execute("SELECT * FROM rolestats WHERE player=? AND role=?", (player, role)): - return "As {2}, {0} has {3} team wins, {4} individual wins, and {5} total games.".format(player, *row) + for row in c.execute("SELECT * FROM rolestats WHERE player=? AND role=?", (acc, role)): + return "\u0002{0}\u0002 as \u0002{1}\u0002 | Team wins: {2}, Individual wins: {3}, Total games: {4}".format(*row) else: return "" def get_game_stats(size): with conn: for row in c.execute("SELECT * FROM gamestats WHERE size=?", (size,)): - return "{0} player games: {1} village wins, {2} wolf wins, and {3} total games.".format(*row) + return "\u0002{0}\u0002 player games | Village wins: {1}, Wolf wins: {2}, Total games: {3}".format(*row) else: return "" From 79b870499beca4797e95f6ba5bc4d5bc4ece1f79 Mon Sep 17 00:00:00 2001 From: Yizhe Shen Date: Mon, 10 Feb 2014 23:39:42 -0500 Subject: [PATCH 7/7] Added additional limits for "gamestats" parameters. Added check to make sure the parameter is an integer between 4 and var.MAX_PLAYERS. --- modules/wolfgame.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/modules/wolfgame.py b/modules/wolfgame.py index 9e1c08a..d8f83f1 100644 --- a/modules/wolfgame.py +++ b/modules/wolfgame.py @@ -2891,13 +2891,14 @@ def game_stats(cli, nick, chan, rest): cli.msg(chan, var.get_game_totals()) return - # Check that size is an integer. - if not rest.isdigit(): - cli.notice(nick, "Please enter an integer.") + # Check for invalid input + rest = rest.strip() + if not rest.isdigit() or int(rest) > var.MAX_PLAYERS or int(rest) < 4: + cli.notice(nick, "Please enter an integer between {0} and {1}.".format(4, var.MAX_PLAYERS)) return # Attempt to find game stats for the given game size. - size = int(rest.strip()) + size = int(rest) msg = var.get_game_stats(size) if msg == "": cli.msg(chan, "No stats for \u0002{0}\u0002 player games.".format(size))