From e226a0968c4638af83f171ebed47ee4add090d3c Mon Sep 17 00:00:00 2001 From: jcao219 Date: Tue, 5 Jul 2011 19:40:17 -0500 Subject: [PATCH] finished day/night transitioning stuff --- vars.py | 6 +- wolfbot.py | 10 ++- wolfgame.py | 226 +++++++++++++++++++++++++++++++++------------------- 3 files changed, 155 insertions(+), 87 deletions(-) diff --git a/vars.py b/vars.py index eff2644..87f5500 100644 --- a/vars.py +++ b/vars.py @@ -4,6 +4,7 @@ EXTRA_WAIT = 20 MAXIMUM_WAITED = 2 # limit for amount of !wait's MAX_SHOTS = 2 NIGHT_TIME_LIMIT = 90 +DAY_TIME_LIMIT = 137 ####################################################################################### # PLAYERS SEER WOLF CURSED DRUNK HARLOT TRAITOR GUNNER # @@ -18,7 +19,10 @@ ROLES_GUIDE = { 4 : ( 1 , 1 , 0 , 0 , 0 , 0 , 0 NO_VICTIMS_MESSAGES = ("The body of a young penguin pet is found.", "A pool of blood and wolf paw prints are found.", "Traces of wolf fur are found.") - +LYNCH_MESSAGES = ("The villagers, after much debate, finally decide on lynching \u0002{0}\u0002, who turned out to be... a \u0002{1}\u0002.", + "Under a lot of noise, the pitchfork-bearing villagers lynch \u0002{0}\u0002, who turned out to be... a \u0002{1}\u0002.", + "The mob drags a protesting \u0002{0}\u0002 to the hanging tree. S/He succumbs to the will of the horde, and is hanged. It is discovered (s)he was a \u0002{1}\u0002.", + "Resigned to his/her fate, \u0002{0}\u0002 is led to the gallows. After death, it is discovered (s)he was a \u0002{1}\u0002.") # These change ingame diff --git a/wolfbot.py b/wolfbot.py index d1bee84..a9bb6eb 100644 --- a/wolfbot.py +++ b/wolfbot.py @@ -13,13 +13,15 @@ class WolfBotHandler(DefaultCommandHandler): if chan != botconfig.NICK: #not a PM for x in wolfgame.COMMANDS.keys(): if msg.startswith(x): - h = msg.replace(x, "", 1).lstrip() - wolfgame.COMMANDS[x](self.client, rawnick, chan, h) + h = msg.replace(x, "", 1) + if not h or h[0] == " " or not x: + wolfgame.COMMANDS[x](self.client, rawnick, chan, h.lstrip()) else: for x in wolfgame.PM_COMMANDS.keys(): if msg.startswith(x): - h = msg.replace(x, "", 1).lstrip() - wolfgame.PM_COMMANDS[x](self.client, rawnick, h) + h = msg.replace(x, "", 1) + if not h or h[0] == " " or not x: + wolfgame.PM_COMMANDS[x](self.client, rawnick, h.lstrip()) @protected def __unhandled__(self, cmd, *args): diff --git a/wolfgame.py b/wolfgame.py index c2ec64f..bc8edc8 100644 --- a/wolfgame.py +++ b/wolfgame.py @@ -8,7 +8,7 @@ import random COMMANDS = {} PM_COMMANDS = {} -HOOKS = {} +HOOKS = {} cmd = decorators.generate(COMMANDS) pmcmd = decorators.generate(PM_COMMANDS) @@ -21,31 +21,31 @@ def connect_callback(cli): cli.join(botconfig.CHANNEL) cli.msg("ChanServ", "op "+botconfig.CHANNEL) - + @cmd("!say") def say(cli, nick, rest): # To be removed later cli.msg(botconfig.CHANNEL, "{0} says: {1}".format(nick, rest)) - + def reset(cli): chan = botconfig.CHANNEL vars.PHASE = "none" - + if vars.TIMERS[0]: vars.TIMERS[0].cancel() vars.TIMERS[0] = None if vars.TIMERS[1]: - vars.TIMERS[0].cancel() - vars.TIMERS[0] = None - + vars.TIMERS[1].cancel() + vars.TIMERS[1] = None + cli.mode(chan, "-m") for plr in vars.list_players(): cli.mode(chan, "-v", "{0} {0}!*@*".format(plr)) for deadguy in vars.DEAD: cli.mode(chan, "-q", "{0} {0}!*@*".format(deadguy)) - + vars.ROLES = {"person" : []} vars.CURSED = "" vars.CAN_START_TIME = timedelta(0) @@ -55,7 +55,7 @@ def reset(cli): vars.VOTES = {} - + @pmcmd("!bye", admin_only=True) @cmd("!bye", admin_only=True) def forced_exit(cli, nick, *rest): # Admin Only @@ -63,8 +63,8 @@ def forced_exit(cli, nick, *rest): # Admin Only cli.quit("Forced quit from admin") raise SystemExit - - + + @cmd("!exec", admin_only=True) def py(cli, nick, chan, rest): exec(rest) @@ -84,56 +84,56 @@ def checks(f): return f(*args) return inner - - - + + + @cmd("!ping") def pinger(cli, nick, chan, rest): - if (vars.LAST_PING and + if (vars.LAST_PING and vars.LAST_PING + timedelta(seconds=300) > datetime.now()): cli.notice(nick, ("This command is ratelimited. " + "Please wait a while before using it again.")) return - + vars.LAST_PING = datetime.now() vars.PINGING = True TO_PING = [] - + @hook("whoreply") def on_whoreply(cli, server, dunno, chan, dunno1, dunno2, dunno3, user, status, dunno4): if not vars.PINGING: return if user in (botconfig.NICK, nick): return # Don't ping self. - + if vars.PINGING and 'G' not in status and '+' not in status: # TODO: check if the user has AWAY'D himself TO_PING.append(user) - - + + @hook("endofwho") def do_ping(*args): if not vars.PINGING: return - + cli.msg(chan, "PING! "+" ".join(TO_PING)) vars.PINGING = False - + HOOKS.pop("whoreply") HOOKS.pop("endofwho") - + cli.send("WHO "+chan) - - + + @cmd("!sudo ping", admin_only=True) def fpinger(cli, nick, chan, rest): vars.LAST_PING = None pinger(cli, nick, chan, rest) - - + + @cmd("!join") def join(cli, nick, chan, rest): if vars.PHASE == "none": @@ -153,24 +153,24 @@ def join(cli, nick, chan, rest): vars.ROLES["person"].append(nick) cli.msg(chan, '\u0002{0}\u0002 has joined the game.'.format(nick)) - - + + @cmd("!stats") def stats(cli, nick, chan, rest): if vars.PHASE == "none": cli.notice(nick, "No game is currently running.") return - + pl = vars.list_players() if len(pl) > 1: cli.msg(chan, '{0}: \u0002{1}\u0002 players: {2}'.format(nick, len(pl), ", ".join(pl))) else: cli.msg(chan, '{0}: \u00021\u0002 player: {1}'.format(nick, pl[0])) - + if vars.PHASE == "join": return - + message = [] for role in ("wolf", "seer", "harlot"): count = len(vars.ROLES.get(role,[])) @@ -186,8 +186,47 @@ def stats(cli, nick, chan, rest): ", ".join(message[0:-1]), message[-1]), vb) + + + +def hurry_up(cli): + if vars.PHASE != "day": return + + chan = botconfig.CHANNEL + pl = vars.list_players() + avail = len(pl) - len(vars.WOUNDED) + votesneeded = avail // 2 + 1 + + found_dup = False + max = (0, "") + for votee, voters in iter(vars.VOTES.items()): + if len(voters) > max[0]: + max = (len(voters), votee) + found_dup = False + elif len(voters) == max[0]: + found_dup = True + if max[0] > 0 and not found_dup: + vars.VOTES[max[1]] = [None] * votesneeded + chk_decision(cli) # Induce a lynch + else: + cli.msg(chan, "The sun is almost setting.") + for plr in pl: + vars.VOTES[plr] = [None] * (votesneeded - 1) + +def chk_decision(cli): + chan = botconfig.CHANNEL + pl = vars.list_players() + avail = len(pl) - len(vars.WOUNDED) + votesneeded = avail // 2 + 1 + for votee, voters in iter(vars.VOTES.items()): + if len(voters) >= votesneeded: + cli.msg(botconfig.CHANNEL, + random.choice(vars.LYNCH_MESSAGES).format( + votee, vars.get_role(votee))) + if del_player(cli, votee, True): + transition_night(cli) + - @checks @cmd("!votes") @@ -198,39 +237,45 @@ def show_votes(cli, nick, chan, rest): elif vars.PHASE != "day": cli.notice(nick, "Voting is only during the day.") return + if None in [x for voter in vars.VOTES.values() for x in voter]: + cli.msg(chan, (nick+": Tiebreaker conditions. Whoever "+ + "receives the next vote will be lynched.")) + return + votelist = ["{0}: {1} ({2})".format(votee, len(vars.VOTES[votee]), " ".join(vars.VOTES[votee])) - for votee in vars.VOTES.keys()] + for votee in votes_copy.keys()] cli.msg(chan, "{0}: {1}".format(nick, ", ".join(votelist))) - + pl = vars.list_players() avail = len(pl) - len(vars.WOUNDED) votesneeded = avail // 2 + 1 cli.msg(chan, ("{0}: \u0002{1}\u0002 players, \u0002{2}\u0002 votes "+ "required to lynch, \u0002{3}\u0002 players available " + "to vote.").format(nick, len(pl), votesneeded, avail)) - -def del_player(cli, nick, died_in_game = True): + +def del_player(cli, nick, forced_death): cli.mode(botconfig.CHANNEL, "-v", "{0} {0}!*@*".format(nick)) - if vars.PHASE != "join" and died_in_game: + if vars.PHASE != "join": cli.mode(botconfig.CHANNEL, "+q", "{0} {0}!*@*".format(nick)) - vars.DEAD.append(nick) + vars.DEAD.append(nick) vars.del_player(nick) - + if vars.PHASE == "day" and not forced_death: # didn't die from lynching + chk_decision(cli) + return True + + - def leave(cli, what, nick): if nick not in vars.list_players(): # not playing return msg = "" - died_in_game = False if what in ("!quit", "!leave"): msg = ("\u0002{0}\u0002 died of an unknown disease. "+ "S/He was a \u0002{1}\u0002.") - died_in_game = True elif what == "part": msg = ("\u0002{0}\u0002 died due to eating poisonous berries. "+ "Appears (s)he was a \u0002{1}\u0002.") @@ -242,16 +287,16 @@ def leave(cli, what, nick): "Appears (s)he was a \u0002{1}\u0002.") msg = msg.format(nick, vars.get_role(nick)) cli.msg(botconfig.CHANNEL, msg) - del_player(cli, nick, died_in_game) + del_player(cli, nick, False) cmd("!leave")(lambda cli, nick, *rest: leave(cli, "!leave", nick)) cmd("!quit")(lambda cli, nick, *rest: leave(cli, "!quit", nick)) hook("part")(lambda cli, nick, *rest: leave(cli, "part", nick)) hook("quit")(lambda cli, nick, *rest: leave(cli, "quit", nick)) hook("kick")(lambda cli, nick, *rest: leave(cli, "kick", nick)) - - - + + + def transition_day(cli): vars.PHASE = "day" chan = botconfig.CHANNEL @@ -260,7 +305,7 @@ def transition_day(cli): td = vars.DAY_START_TIME - vars.NIGHT_START_TIME vars.NIGHT_TIMEDELTA += td min, sec = td.seconds // 60, td.seconds % 60 - + message = ("Night lasted \u0002{0:0>2}:{1:0>2}\u0002. It is now daytime. "+ "The villagers awake, thankful for surviving the night, "+ "and search the village... ").format(min, sec) @@ -277,26 +322,31 @@ def transition_day(cli): dead.append(vars.VICTIM) cli.msg(chan, message) # TODO: check if harlot also died - + for deadperson in dead: - del_player(cli, deadperson, True) - + if not del_player(cli, deadperson, True): + return + cli.msg(chan, ("The villagers must now vote for whom to lynch. "+ 'Use "!lynch " to cast your vote. 3 votes '+ 'are required to lynch.')) - - + if vars.DAY_TIME_LIMIT > 0: # Time limit enabled + t = threading.Timer(vars.DAY_TIME_LIMIT, hurry_up, [cli]) + vars.TIMERS[1] = t + t.start() + + def chk_nightdone(cli): - if (len(vars.SEEN) == len(vars.ROLES["seer"]) and + if (len(vars.SEEN) == len(vars.ROLES["seer"]) and vars.VICTIM and vars.PHASE == "night"): if vars.TIMERS[0]: vars.TIMERS[0].cancel() # cancel timer vars.TIMERS[0] = None if vars.PHASE == "night": # Double check transition_day(cli) - - + + @checks @cmd("!lynch", "!vote") def vote(cli, nick, chan, rest): @@ -310,10 +360,10 @@ def vote(cli, nick, chan, rest): if rest in pl_l: voted = pl[pl_l.index(rest)] candidates = vars.VOTES.keys() - for voters in candidates: # remove previous vote + for voters in list(candidates): # remove previous vote if nick in vars.VOTES[voters]: vars.VOTES[voters].remove(nick) - if not vars.VOTES[voters]: + if not vars.VOTES[voters] and voters != voted: del vars.VOTES[voters] if voted not in vars.VOTES.keys(): vars.VOTES[voted] = [nick] @@ -321,12 +371,14 @@ def vote(cli, nick, chan, rest): vars.VOTES[voted].append(nick) cli.msg(chan, ("\u0002{0}\u0002 votes for "+ "\u0002{1}\u0002.").format(nick, rest)) + chk_decision(cli) elif not rest: cli.notice(nick, "Not enough parameters.") else: cli.notice(nick, "\u0002{0}\u0002 is currently not playing.".format(rest)) - - + + + @checks @pmcmd("!kill", "kill") def kill(cli, nick, rest): @@ -352,8 +404,8 @@ def kill(cli, nick, rest): vars.VICTIM = victim cli.msg(nick, "You have selected \u0002{0}\u0002 to be killed".format(victim)) chk_nightdone(cli) - - + + @checks @pmcmd("see", "!see") def see(cli, nick, rest): @@ -383,9 +435,9 @@ def see(cli, nick, rest): "\u0002{1}\u0002!").format(victim, role)) vars.SEEN.append(nick) chk_nightdone(cli) - - - + + + @pmcmd("") def relay(cli, nick, rest): badguys = vars.ROLES.get("wolf", []) + vars.ROLES.get("traitor", []) @@ -395,24 +447,34 @@ def relay(cli, nick, rest): for badguy in badguys: cli.msg(badguy, "{0} says: {1}".format(nick, rest)) - - + + def transition_night(cli): vars.PHASE = "night" + + # Reset daytime variables + vars.VOTES = {} + if vars.TIMERS[1]: # cancel daytime-limit timer + vars.TIMERS[1].cancel() + vars.TIMERS[1] = None + vars.WOUNDED = "" + + # Reset nighttime variables vars.VICTIM = "" # nickname of cursed villager vars.SEEN = [] # list of seers that have had visions vars.NIGHT_START_TIME = datetime.now() - + chan = botconfig.CHANNEL cli.msg(chan, ("It is now nighttime. All players "+ "check for PMs from me for instructions. "+ "If you did not receive one, simply sit back, "+ "relax, and wait patiently for morning.")) - t = threading.Timer(vars.NIGHT_TIME_LIMIT, transition_day, [cli]) - vars.TIMERS[0] = t - t.start() - + if vars.NIGHT_TIME_LIMIT > 0: + t = threading.Timer(vars.NIGHT_TIME_LIMIT, transition_day, [cli]) + vars.TIMERS[0] = t + t.start() + # send PMs ps = vars.list_players() for wolf in vars.ROLES["wolf"]: @@ -437,8 +499,8 @@ def transition_night(cli): 'Use "see " to see the role of a player.')) cli.msg(seer, "Players: "+", ".join(pl)) - - + + @cmd("!start") def start(cli, nick, chan, rest): pl = vars.list_players() @@ -456,7 +518,7 @@ def start(cli, nick, chan, rest): if dur > 0: cli.msg(chan, "Please wait at least {0} more seconds.".format(dur)) return - + if len(pl) < 4: cli.msg(chan, "{0}: Four or more players are required to play.".format(nick)) return @@ -468,7 +530,7 @@ def start(cli, nick, chan, rest): ndrunk = 0 ncursed = 0 ntraitor = 0 - + for pcount in range(4, len(pl)+1): addroles = vars.ROLES_GUIDE.get(pcount) if addroles: @@ -478,7 +540,7 @@ def start(cli, nick, chan, rest): ndrunk += addroles[3] nharlots += addroles[4] ntraitor += addroles[5] - + seer = random.choice(pl) vars.ROLES["seer"] = [seer] pl.remove(seer) @@ -497,7 +559,7 @@ def start(cli, nick, chan, rest): vars.ROLES["village drunk"] = [drunk] pl.remove(drunk) vars.ROLES["villager"] = pl - + if ncursed: vars.CURSED = random.choice(vars.ROLES["villager"] + \ vars.ROLES.get("harlot", []) +\ @@ -507,19 +569,19 @@ def start(cli, nick, chan, rest): if ncursed: possible.remove(vars.CURSED) # Cursed traitors are not allowed vars.TRAITOR = random.choice(possible) - + cli.msg(chan, ("{0}: Welcome to Werewolf, the popular detective/social party "+ "game (a theme of Mafia).").format(", ".join(vars.list_players()))) cli.mode(chan, "+m") - + vars.ORIGINAL_ROLES = dict(vars.ROLES) # Make a copy vars.DAY_TIMEDELTA = timedelta(0) vars.NIGHT_TIMEDELTA = timedelta(0) vars.DEAD = [] transition_night(cli) - - + + @cmd("!wait") def wait(cli, nick, chan, rest): pl = vars.list_players()