From 28f2dcebf8527efc44687e0ae0ad33535ecc2d07 Mon Sep 17 00:00:00 2001 From: skizzerz Date: Thu, 7 May 2015 23:29:04 -0500 Subject: [PATCH 1/3] Add event system with one sample event in chk_win Further information on how this system works may be found on the wiki. If there is no information on the wiki, poke woffle incessently until there is. --- src/events.py | 36 ++++++++++++++++++++++++++++++++++++ src/wolfgame.py | 42 +++++++++++++++++++++++++++++++----------- 2 files changed, 67 insertions(+), 11 deletions(-) create mode 100644 src/events.py diff --git a/src/events.py b/src/events.py new file mode 100644 index 0000000..05ee753 --- /dev/null +++ b/src/events.py @@ -0,0 +1,36 @@ +# event system + +EVENT_CALLBACKS = {} + +def add_listener(event, callback, priority = 5): + if event not in EVENT_CALLBACKS: + EVENT_CALLBACKS[event] = [] + + if (priority, callback) not in EVENT_CALLBACKS[event]: + EVENT_CALLBACKS[event].append((priority, callback)) + EVENT_CALLBACKS[event].sort(key = lambda x: x[0]) + +def remove_listener(event, callback, priority = 5): + if event in EVENT_CALLBACKS and (priority, callback) in EVENT_CALLBACKS[event]: + EVENT_CALLBACKS[event].remove((priority, callback)) + + +class Event: + def __init__(self, name, data): + self.stop_processing = False + self.prevent_default = False + self.name = name + self.data = data + + def dispatch(self, *args): + if self.name not in EVENT_CALLBACKS: + return + + for item in list(EVENT_CALLBACKS[self.name]): + item[1](self, *args) + if self.stop_processing: + break + + return not self.prevent_default + +# vim: set expandtab:sw=4:ts=4: diff --git a/src/wolfgame.py b/src/wolfgame.py index 6a1023e..3c48706 100644 --- a/src/wolfgame.py +++ b/src/wolfgame.py @@ -40,6 +40,10 @@ from src import logger import urllib.request import sqlite3 +# done this way so that events is accessible in !eval (useful for debugging) +from src import events +Event = events.Event + debuglog = logger("debug.log", write=False, display=False) # will be True if in debug mode errlog = logger("errors.log") plog = logger(None) #use this instead of print so that logs have timestamps @@ -80,6 +84,7 @@ var.PINGING_IFS = False var.TIMERS = {} var.ORIGINAL_SETTINGS = {} +var.CURRENT_GAMEMODE = {"name": "default"} var.LAST_SAID_TIME = {} @@ -371,6 +376,10 @@ def pm(cli, target, message): # message either privmsg or notice, depending on cli.msg(target, message) def reset_settings(): + if hasattr(var.CURRENT_GAMEMODE, "teardown") and callable(var.CURRENT_GAMEMODE.teardown): + var.CURRENT_GAMEMODE.teardown() + + var.CURRENT_GAMEMODE = {"name": "default"} for attr in list(var.ORIGINAL_SETTINGS.keys()): setattr(var, attr, var.ORIGINAL_SETTINGS[attr]) dict.clear(var.ORIGINAL_SETTINGS) @@ -412,7 +421,6 @@ def reset(): var.PINGED_ALREADY_ACCS = [] var.NO_LYNCH = [] var.FGAMED = False - var.CURRENT_GAMEMODE = "default" var.GAMEMODE_VOTES = {} #list of players who have used !game reset_settings() @@ -1516,7 +1524,7 @@ def stats(cli, nick, chan, rest): else: cli.notice(nick, msg) - if var.PHASE == "join" or not var.ROLE_REVEAL or var.GAME_MODES[var.CURRENT_GAMEMODE][4]: + if var.PHASE == "join" or not var.ROLE_REVEAL or var.GAME_MODES[var.CURRENT_GAMEMODE.name][4]: return message = [] @@ -2148,7 +2156,7 @@ def stop_game(cli, winner = "", abort = False): if won or iwon: winners.append(splr) - var.update_game_stats(var.CURRENT_GAMEMODE, len(survived) + len(var.DEAD), winner) + var.update_game_stats(var.CURRENT_GAMEMODE.name, len(survived) + len(var.DEAD), winner) # spit out the list of winners winners.sort() @@ -2226,6 +2234,8 @@ def chk_win(cli, end_game = True): except KeyError: pass + winner = None + message = "" if lpl < 1: message = "Game over! There are no players remaining." winner = "none" @@ -2265,8 +2275,15 @@ def chk_win(cli, end_game = True): elif lrealwolves == 0: chk_traitor(cli) return chk_win(cli, end_game) - else: + + event = Event("chk_win", {"winner": winner, "message": message}) + event.dispatch(var, lpl, lwolves, lrealwolves) + winner = event.data["winner"] + message = event.data["message"] + + if winner is None: return False + if end_game: players = [] if winner == "monsters": @@ -5353,7 +5370,7 @@ def transition_night(cli): debuglog(elder, "ELDER DEATH") if var.FIRST_NIGHT and chk_win(cli, end_game=False): # prevent game from ending as soon as it begins (useful for the random game mode) - start(cli, botconfig.NICK, botconfig.CHANNEL, restart=var.CURRENT_GAMEMODE) + start(cli, botconfig.NICK, botconfig.CHANNEL, restart=var.CURRENT_GAMEMODE.name) return # game ended from bitten / amnesiac turning, narcolepsy totem expiring, or other weirdness @@ -5864,13 +5881,16 @@ def cgamemode(cli, arg): md = modeargs.pop(0) try: gm = var.GAME_MODES[md][0](*modeargs) + if hasattr(gm, "startup") and callable(gm.startup): + gm.startup() for attr in dir(gm): val = getattr(gm, attr) if (hasattr(var, attr) and not callable(val) and not attr.startswith("_")): var.ORIGINAL_SETTINGS[attr] = getattr(var, attr) setattr(var, attr, val) - var.CURRENT_GAMEMODE = md + gm.name = md + var.CURRENT_GAMEMODE = gm return True except var.InvalidModeException as e: cli.msg(botconfig.CHANNEL, "Invalid mode: "+str(e)) @@ -6119,7 +6139,7 @@ def start(cli, nick, chan, forced = False, restart = ""): if not restart: cli.msg(chan, ("{0}: Welcome to Werewolf, the popular detective/social party "+ - "game (a theme of Mafia). Using the \002{1}\002 game mode.").format(", ".join(pl), var.CURRENT_GAMEMODE)) + "game (a theme of Mafia). Using the \002{1}\002 game mode.").format(", ".join(pl), var.CURRENT_GAMEMODE.name)) cli.mode(chan, "+m") var.ORIGINAL_ROLES = copy.deepcopy(var.ROLES) # Make a copy @@ -6876,15 +6896,15 @@ def listroles(cli, nick, chan, rest): rest = re.split(" +", rest.strip(), 1) #message if this game mode has been disabled - if (not len(rest[0]) or rest[0].isdigit()) and var.GAME_MODES[var.CURRENT_GAMEMODE][4]: - txt += " {0}: {1}roles was disabled for the {2} game mode.".format(nick, botconfig.CMD_CHAR, var.CURRENT_GAMEMODE) + if (not len(rest[0]) or rest[0].isdigit()) and var.GAME_MODES[var.CURRENT_GAMEMODE.name][4]: + txt += " {0}: {1}roles was disabled for the {2} game mode.".format(nick, botconfig.CMD_CHAR, var.CURRENT_GAMEMODE.name) rest = [] roleindex = {} #prepend player count if called without any arguments elif not len(rest[0]) and pl > 0: txt += " {0}: There {1} \u0002{2}\u0002 playing.".format(nick, "is" if pl == 1 else "are", pl) if var.PHASE in ["night", "day"]: - txt += " Using the {0} game mode.".format(var.CURRENT_GAMEMODE) + txt += " Using the {0} game mode.".format(var.CURRENT_GAMEMODE.name) #read game mode to get roles for elif len(rest[0]) and not rest[0].isdigit(): @@ -7091,7 +7111,7 @@ def game_stats(cli, nick, chan, rest): cli.notice(nick, "Wait until the game is over to view stats.") return - gamemode = var.CURRENT_GAMEMODE + gamemode = var.CURRENT_GAMEMODE.name gamesize = None rest = rest.split() # Check for gamemode From abf1e1ddd609642818c11e465841e806dc489dff Mon Sep 17 00:00:00 2001 From: skizzerz Date: Fri, 8 May 2015 00:33:15 -0500 Subject: [PATCH 2/3] Fix up evilvillage Villagers win if the wolves die, wolves win if all villagers are dead (removes the stupid way of wolves winning for cultists to elect themselves to be voted). Also swap shaman and hunter so villagers always have a means of getting a kill no matter the game size. --- src/settings.py | 37 +++++++++++++++++++++++++++++++++---- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/src/settings.py b/src/settings.py index 938d8f5..bd89907 100644 --- a/src/settings.py +++ b/src/settings.py @@ -1,5 +1,6 @@ from collections import defaultdict import math +from src import events PING_WAIT = 300 # Seconds PING_MIN_WAIT = 30 # How long !start has to wait after a !ping @@ -521,20 +522,21 @@ class MadMode(object): "assassin" : ( 0 , 0 , 0 , 0 , 0 , 0 , 1 , 1 , 1 ), }) -# evilvillage is broken, disable for now -#@game_mode("evilvillage", minp = 6, maxp = 18) +@game_mode("evilvillage", minp = 6, maxp = 18, likelihood = 1) class EvilVillageMode(object): """Majority of the village is wolf aligned, safes must secretly try to kill the wolves.""" def __init__(self): self.DEFAULT_ROLE = "cultist" + self.DEFAULT_SEEN_AS_VILL = False + self.ABSTAIN_ENABLED = False self.ROLE_INDEX = ( 6 , 10 , 15 ) self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX) self.ROLE_GUIDE.update({# village roles "oracle" : ( 1 , 1 , 0 ), "seer" : ( 0 , 0 , 1 ), "guardian angel" : ( 0 , 1 , 1 ), - "shaman" : ( 1 , 1 , 1 ), - "hunter" : ( 0 , 0 , 1 ), + "shaman" : ( 0 , 0 , 1 ), + "hunter" : ( 1 , 1 , 1 ), "villager" : ( 0 , 0 , 1 ), # wolf roles "wolf" : ( 1 , 1 , 2 ), @@ -543,6 +545,33 @@ class EvilVillageMode(object): "fool" : ( 0 , 1 , 1 ), }) + def startup(self): + events.add_listener("chk_win", self.chk_win, 1) + + def teardown(self): + events.remove_listener("chk_win", self.chk_win, 1) + + def chk_win(self, evt, var, lpl, lwolves, lrealwolves): + lsafes = len(var.list_players(["oracle", "seer", "guardian angel", "shaman", "hunter", "villager"])) + evt.stop_processing = True + + try: + if lrealwolves == 0: + evt.data["winner"] = "villagers" + evt.data["message"] = ("Game over! All the wolves are dead! The villagers " + + "round up the remaining cultists, hang them, and live " + + "happily ever after.") + elif lsafes == 0: + evt.data["winner"] = "wolves" + evt.data["message"] = ("Game over! All the villagers are dead! The cultists rejoice " + + "with their wolf buddies and start plotting to take over the " + + "next village.") + elif evt.data["winner"][0] != "@": + evt.data["winner"] = None + except TypeError: # means that evt.data["winner"] isn't a string or something else subscriptable + evt.data["winner"] = None + + @game_mode("classic", minp = 7, maxp = 21, likelihood = 4) class ClassicMode(object): """Classic game mode from before all the changes.""" From ee9716feebff0aa5d945d679479572734987b999 Mon Sep 17 00:00:00 2001 From: skizzerz Date: Sat, 9 May 2015 19:13:35 -0500 Subject: [PATCH 3/3] Evilvillage balance tweaks --- src/settings.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/src/settings.py b/src/settings.py index bd89907..c0a6fc6 100644 --- a/src/settings.py +++ b/src/settings.py @@ -529,20 +529,22 @@ class EvilVillageMode(object): self.DEFAULT_ROLE = "cultist" self.DEFAULT_SEEN_AS_VILL = False self.ABSTAIN_ENABLED = False - self.ROLE_INDEX = ( 6 , 10 , 15 ) + self.ROLE_INDEX = ( 6 , 8 , 10 , 15 ) self.ROLE_GUIDE = reset_roles(self.ROLE_INDEX) self.ROLE_GUIDE.update({# village roles - "oracle" : ( 1 , 1 , 0 ), - "seer" : ( 0 , 0 , 1 ), - "guardian angel" : ( 0 , 1 , 1 ), - "shaman" : ( 0 , 0 , 1 ), - "hunter" : ( 1 , 1 , 1 ), - "villager" : ( 0 , 0 , 1 ), + "oracle" : ( 0 , 1 , 1 , 0 ), + "seer" : ( 0 , 0 , 0 , 1 ), + "guardian angel" : ( 0 , 0 , 1 , 1 ), + "shaman" : ( 0 , 0 , 0 , 1 ), + "hunter" : ( 1 , 1 , 1 , 1 ), + "villager" : ( 0 , 0 , 0 , 1 ), # wolf roles - "wolf" : ( 1 , 1 , 2 ), - "minion" : ( 0 , 1 , 1 ), + "wolf" : ( 1 , 1 , 1 , 2 ), + "minion" : ( 0 , 0 , 1 , 1 ), # neutral roles - "fool" : ( 0 , 1 , 1 ), + "fool" : ( 0 , 0 , 1 , 1 ), + # templates + "cursed villager" : ( 0 , 1 , 1 , 1 ), }) def startup(self):