diff --git a/src/gamemodes.py b/src/gamemodes.py index b4c733e..2f3a350 100644 --- a/src/gamemodes.py +++ b/src/gamemodes.py @@ -16,6 +16,39 @@ def game_mode(name, minp, maxp, likelihood = 0): reset_roles = lambda i: OrderedDict([(role, (0,) * len(i)) for role in var.ROLE_GUIDE]) +def get_lovers(): + lovers = [] + pl = var.list_players() + for lover in var.LOVERS: + done = None + for i, lset in enumerate(lovers): + if lover in pl and lover in lset: + if done is not None: # plot twist! two clusters turn out to be linked! + done.update(lset) + for lvr in var.LOVERS[lover]: + if lvr in pl: + done.add(lvr) + + lset.clear() + continue + + for lvr in var.LOVERS[lover]: + if lvr in pl: + lset.add(lvr) + done = lset + + if done is None and lover in pl: + lovers.append(set()) + lovers[-1].add(lover) + for lvr in var.LOVERS[lover]: + if lvr in pl: + lovers[-1].add(lvr) + + while set() in lovers: + lovers.remove(set()) + + return lovers + class GameMode: def __init__(self, arg=""): if not arg: @@ -64,6 +97,23 @@ class GameMode: def teardown(self): pass + # Here so any game mode can use it + def lovers_chk_win(self, evt, var, lpl, lwolves, lrealwolves): + winner = evt.data["winner"] + if winner is not None and winner.startswith("@"): + return # fool won, lovers can't win even if they would + all_lovers = get_lovers() + if len(all_lovers) != 1: + return # we need exactly one cluster alive for this to trigger + + lovers = all_lovers[0] + + if len(lovers) == lpl: + evt.data["winner"] = "lovers" + evt.data["message"] = "Game over! All of the remaining villagers are in love with each other! All of them win." + evt.data["players"] = ["{0} ({1})".format(x, var.get_role(x)) for x in lovers] + evt.data["winners"] = list(lovers) + @game_mode("roles", minp = 4, maxp = 35) class ChangedRolesMode(GameMode): """Example: !fgame roles=wolf:1,seer:0,guardian angel:1""" @@ -398,6 +448,12 @@ class MatchmakerMode(GameMode): "mad scientist" : [i >= 18 for i in self.ROLE_INDEX], }) + def startup(self): + events.add_listener("chk_win", self.lovers_chk_win, 1) + + def teardown(self): + events.remove_listener("chk_win", self.lovers_chk_win, 1) + @game_mode("random", minp = 8, maxp = 24, likelihood = 0) class RandomMode(GameMode): """Completely random and hidden roles.""" @@ -430,9 +486,11 @@ class RandomMode(GameMode): def startup(self): events.add_listener("role_attribution", self.role_attribution, 1) + events.add_listener("chk_win", self.lovers_chk_win, 1) def teardown(self): events.remove_listener("role_attribution", self.role_attribution, 1) + events.remove_listener("chk_win", self.lovers_chk_win, 1) def role_attribution(self, evt, cli, chk_win_conditions, var, villagers): lpl = len(villagers) - 1 diff --git a/src/wolfgame.py b/src/wolfgame.py index 86aedd1..0b18190 100644 --- a/src/wolfgame.py +++ b/src/wolfgame.py @@ -2275,7 +2275,7 @@ def chk_traitor(cli): "frightened as they hear a loud howl. The wolves are "+ "not gone!\u0002") -def stop_game(cli, winner = "", abort = False): +def stop_game(cli, winner = "", abort = False, winners = None): chan = botconfig.CHANNEL if abort: cli.msg(chan, "The role attribution failed 3 times. Game was canceled.") @@ -2369,7 +2369,8 @@ def stop_game(cli, winner = "", abort = False): # Only update if someone actually won, "" indicates everyone died or abnormal game stop if winner != "": plrl = {} - winners = [] + if winners is None: + winners = [] for role,ppl in var.ORIGINAL_ROLES.items(): if role in var.TEMPLATE_RESTRICTIONS.keys(): continue @@ -2426,7 +2427,7 @@ def stop_game(cli, winner = "", abort = False): iwon = False elif rol == "fool" and "@" + splr == winner: iwon = True - elif splr in var.LOVERS and splr in survived and len([x for x in var.LOVERS[splr] if x in survived]) > 0: + elif winner != "lovers" and splr in var.LOVERS and splr in survived and len([x for x in var.LOVERS[splr] if x in survived]) > 0: for lvr in var.LOVERS[splr]: if lvr not in survived: # cannot win with dead lover (if splr in survived and lvr is not, that means lvr idled out) @@ -2622,7 +2623,7 @@ def chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ltraitors, l ltraitors = len(var.ROLES.get("traitor", ())) return chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ltraitors, lpipers, cli, end_game) - event = Event("chk_win", {"winner": winner, "message": message}) + event = Event("chk_win", {"winner": winner, "message": message, "players": [], "winners": None}) event.dispatch(var, lpl, lwolves, lrealwolves) winner = event.data["winner"] message = event.data["message"] @@ -2631,7 +2632,7 @@ def chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ltraitors, l return False if end_game: - players = [] + players = event.data["players"] if winner == "monsters": for plr in var.ROLES["monster"]: players.append("{0} ({1})".format(plr, var.get_role(plr))) @@ -2648,7 +2649,7 @@ def chk_win_conditions(lpl, lwolves, lcubs, lrealwolves, lmonsters, ltraitors, l debuglog("WIN:", winner) debuglog("PLAYERS:", ", ".join(players)) cli.msg(chan, message) - stop_game(cli, winner) + stop_game(cli, winner, winners=event.data["winners"]) return True def del_player(cli, nick, forced_death = False, devoice = True, end_game = True, death_triggers = True, killer_role = "", deadlist = [], original = "", cmode = [], ismain = True):