diff --git a/src/werewolf_bot.py b/src/werewolf_bot.py index 1816add..7ee894e 100644 --- a/src/werewolf_bot.py +++ b/src/werewolf_bot.py @@ -21,7 +21,7 @@ bot.remove_command('help') @bot.event async def on_ready(): - await bot.change_presence(status=discord.Status.idle, activity=discord.Game('One Night Ultimate Werewolf')) + await bot.change_presence(status=discord.Status.online, activity=discord.Game('One Night Ultimate Werewolf')) print('We have logged in as {0.user}'.format(bot)) diff --git a/src/werewolf_game.py b/src/werewolf_game.py index 5cf0b55..8abf581 100644 --- a/src/werewolf_game.py +++ b/src/werewolf_game.py @@ -1,7 +1,7 @@ from random import shuffle import asyncio import discord -from werewolf_roles import Role, Werewolf, Mason +from werewolf_roles import Role, Doppelganger, Werewolf, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Tanner, Hunter, No_role from werewolf_players import Player, No_player @@ -37,13 +37,14 @@ class Game: def setup(self): self.role = dict() for r in Role.__subclasses__(): - if r not in [Werewolf, Mason]: + if r not in [Werewolf, Mason, No_role]: r(self) self.role[Werewolf] = [] self.role[Mason] = [] self.voting_list = self.player_list + [No_player()] for c in self.voting_list: c.tally = 0 + c.won = c.dead = False def distribute_roles(self): shuffle(self.role_list) @@ -58,11 +59,17 @@ class Game: await self.for_all_player(lambda p: p.send_info(f"Your role: **{p.night_role.name()}**")) async def night_phases(self): - await asyncio.gather(*[r.phase1() for r in self.active_role]) - await asyncio.gather(*[r.phase2() for r in self.active_role]) - await asyncio.gather(*[r.phase3() for r in self.active_role]) - await asyncio.gather(*[r.phase4() for r in self.active_role]) - await asyncio.gather(*[r.phase5() for r in self.active_role]) + await asyncio.gather(*[self.role[r].query() for r in [Doppelganger, Seer, Robber, Troublemaker, Drunk]]) # slow + await self.role[Doppelganger].send_copy_info() + await self.role[Doppelganger].simulate() # slow + await asyncio.gather(*[w.phase() for w in self.role[Werewolf]]) # slow + await asyncio.gather(*[w.send_info() for w in [self.role[Minion]] + self.role[Mason] + [self.role[Seer]]]) + await self.role[Robber].simulate() + await self.role[Robber].send_info() + await self.role[Troublemaker].simulate() + await self.role[Drunk].simulate() + await self.role[Insomniac].send_info() + await self.role[Doppelganger].insomniac() async def start_day(self): await self.send("The day has started") @@ -73,7 +80,7 @@ class Game: # replace with dm: await self.receive('$vote') await self.send("Vote in DM") - await self.for_all_player(lambda p: p.cast_vote(self.voting_list)) + await self.for_all_player(lambda p: p.cast_vote("Who do you want to kill?", self.voting_list)) def tally(self): for p in self.player_list: @@ -84,18 +91,18 @@ class Game: dead = [p for p in self.player_list if p.tally >= maxi] for d in dead: d.dead = True - if d.day_role.is_Hunter: + if d.day_role.is_role(Hunter): dead.append(d.vote) return dead def who_won(self, dead): no_dead = (len(dead) == 0) - tanner_dead = any(d.day_role.is_Tanner for d in dead) - werewolf_dead = any(d.day_role.is_Werewolf for d in dead) - werewolf_in_game = any(p.day_role.is_Werewolf for p in self.player_list) - minion_dead = any(d.day_role.is_Minion for d in dead) - minion_in_game = any(p.day_role.is_Minion for p in self.player_list) + tanner_dead = any(d.day_role.is_role(Tanner) for d in dead) + werewolf_dead = any(d.day_role.is_role(Werewolf) for d in dead) + werewolf_in_game = any(p.day_role.is_role(Werewolf) for p in self.player_list) + minion_dead = any(d.day_role.is_role(Minion) for d in dead) + minion_in_game = any(p.day_role.is_role(Minion) for p in self.player_list) werewolf_won = False village_won = False @@ -128,9 +135,9 @@ class Game: werewolf_won = True for p in self.player_list: - if p.day_role.is_Werewolf or p.day_role.is_Minion: + if p.day_role.is_role(Werewolf) or p.day_role.is_role(Minion): p.won = werewolf_won - elif p.day_role.is_Tanner: + elif p.day_role.is_role(Tanner): p.won = p.dead else: p.won = village_won @@ -144,14 +151,14 @@ class Game: if village_won: winnning = ["Village won!"] if tanner_won: - winnning.append(f"{sum(1 for d in dead if d.day_role.is_Tanner)} tanner won") + winnning.append(f"{sum(1 for d in dead if d.day_role.is_role(Tanner))} tanner won") embed = discord.Embed(title=' and '.join(winnning), color=0x00ffff) for p in self.player_list: - won_emoji = ":trophy:" if p.won else "frowning2" + won_emoji = ":trophy:" if p.won else ":frowning2:" dead_emoji = ":skull:" if p.dead else ":no_mouth:" - embed.add_field(name="", value=f"{won_emoji} {dead_emoji} {p.tally}:ballot_box: {str(p)} :sunrise_over_mountains:{str(p.day_role)} (:full_moon:{str(p.night_role)}) :point_right:{str(p.vote)}", inline=False) - embed.add_field(name="Middle cards", value=', '.join(str(r) for r in self.middle_card)) + embed.add_field(name=str(p), value=f"{won_emoji} {dead_emoji} {p.tally}:ballot_box: role: {str(p.day_role)} (was: {str(p.night_role)}) :point_right: {str(p.vote)}", inline=False) + embed.add_field(name="Middle cards", value=', '.join(r.name() for r in self.middle_card), inline=False) await self.channel.send(embed=embed) diff --git a/src/werewolf_players.py b/src/werewolf_players.py index 9e1b3e5..5c7f89e 100644 --- a/src/werewolf_players.py +++ b/src/werewolf_players.py @@ -45,24 +45,26 @@ class Player: text = f"{question}\n" + f"{'='*len(question)}\n\n" + '\n'.join(f"[{str(i)}]({str(options[i])})" for i in range(len(options))) await self.dm.send(f"```md\n{text}```") - async def check_num(self, choice, N): + def check_num(self, choice, N): if not choice.isdigit(): - await self.send_wrong(f"Your choice {choice} is not a number") - return False + raise ValueError(f"Your choice {choice} is not a number") if not 0 <= int(choice) < N: - await self.send_wrong(f"Your choice {choice} is not in range 0 - {N-1}") - return False + raise ValueError(f"Your choice {choice} is not in range 0 - {N-1}") async def receive_choice(self, options, n_ans=1): while True: def check(choice): - return choice.channel == self.dm + return choice.channel == self.dm and choice.author == self.member choice = (await self.game.bot.wait_for('message', timeout=30.0, check=check)).content.split() if not len(choice) == n_ans: await self.send_wrong(f"Please give {n_ans} numbers not {len(choice)}") continue - if not all(self.check_num(c, len(options)) for c in choice): + try: + for c in choice: + self.check_num(c, len(options)) + except ValueError as error: + await self.send_wrong(str(error)) continue await self.send_confirmation(f"Received: {', '.join(choice)}") @@ -70,7 +72,7 @@ class Player: async def get_choice(self, question, options): await self.ask_choice(question, options) - return await self.receive_choice(options)[0] + return (await self.receive_choice(options))[0] async def get_double_choice(self, question, options): await self.ask_choice(question, options) diff --git a/src/werewolf_roles.py b/src/werewolf_roles.py index 40b6d20..0c5b213 100644 --- a/src/werewolf_roles.py +++ b/src/werewolf_roles.py @@ -1,3 +1,4 @@ +import functools from werewolf_players import No_player @@ -7,13 +8,15 @@ class Role: self.player = player self.player.setRole(self) self.add_yourself() - self.is_Hunter = self.is_Tanner = self.is_Werewolf = self.is_Minion = False def add_yourself(self): self.game.role[type(self)] = self async def send_role_list(self, cls): - await self.player.send_info(f"{cls}: {', '.join(str(p) for p in self.game.role[cls])}") + await self.player.send_info(f"{cls.name()}: {', '.join(r.player.name() for r in self.game.role[cls])}") + + def is_role(self, cls): + return isinstance(self, cls) @staticmethod def match(message): @@ -22,6 +25,14 @@ class Role: return role_class raise ValueError(f"Invalid role: {message}") + @staticmethod + def no_player(func): + @functools.wraps(func) + async def wrapper(self, *args, **kwargs): + if not isinstance(self.player, No_player): + return await func(self, *args, **kwargs) + return wrapper + @classmethod def name(cls): return cls.__name__.casefold() @@ -31,17 +42,45 @@ class Role: class Doppelganger(Role): - pass + @Role.no_player + async def query(self): + self.choice = await self.player.get_choice("Which player role do you want to copy?", self.player.other()) + + @Role.no_player + async def send_copy_info(self): + self.copy_role = type(self.player.other()[self.choice].day_role) + await self.send_info(f"You copied: {self.copy_role}") + + @Role.no_player + async def simulate(self): + if self.copy_role in [Werewolf, Mason]: + self.copy_role.add_yourself(self) + if self.copy_role == Werewolf: + await self.copy_role.phase(self) + if self.copy_role in [Mason, Minion]: + await self.copy_role.send_info(self) + + if self.copy_role in [Seer, Robber, Troublemaker, Drunk]: + await self.copy_role.query(self) + if self.copy_role in [Robber, Troublemaker, Drunk]: + self.copy_role.simulate(self) + if self.copy_role in [Seer, Robber]: + await self.copy_role.send_info(self) + + @Role.no_player + async def insomniac(self): + if self.copy_role == Insomniac: + self.copy_role.send_info(self) + + def is_role(self, cls): + return self.copy_role == cls class Werewolf(Role): - def __init__(self, game, player=No_player()): - super().__init__(game, player) - self.is_Werewolf = True - def add_yourself(self): self.game.role[Werewolf].append(self) + @Role.no_player async def phase(self): if len(self.game.role[Werewolf]) >= 2: await self.send_role_list(Werewolf) @@ -53,12 +92,9 @@ class Werewolf(Role): class Minion(Role): - def __init__(self, game, player=No_player()): - super().__init__(game, player) - self.is_Minion = True - + @Role.no_player async def send_info(self): - if len(self.game.werewolf_list) == 0: + if len(self.game.role[Werewolf]) == 0: await self.player.send_info("There were no werewolves, so you need to kill a villager!") else: await self.send_role_list(Werewolf) @@ -68,57 +104,61 @@ class Mason(Role): def add_yourself(self): self.game.role[Mason].append(self) + @Role.no_player async def send_info(self): await self.send_role_list(Mason) class Seer(Role): + @Role.no_player async def query(self): self.choice = await self.player.get_choice("Which 1 player card or 2 middle cards do you want to look at?", self.player.other() + ["left & middle", "middle & right", "left & right"]) + @Role.no_player async def send_info(self): if self.choice < len(self.player.other()): await self.player.send_info(self.player.other()[self.choice].night_role) else: - self.choice -= len(self.player.other()) - if self.choice == 0: - a, b = 0, 1 - elif self.choice == 1: - a, b = 1, 2 - else: - a, b = 0, 2 - + a, b = [(0, 1), (1, 2), (0, 2)][self.choice - len(self.player.other())] await self.player.send_info(f"{self.game.middle_card[a]} {self.game.middle_card[b]}") class Robber(Role): + @Role.no_player async def query(self): self.choice = await self.player.get_choice("Which player do you want to rob?", self.player.other()) - async def simulate(self): + @Role.no_player + def simulate(self): self.player.swap(self.player.other()[self.choice]) + @Role.no_player async def send_info(self): await self.player.send_info(f"You robbed: {self.player.day_role}") class Troublemaker(Role): + @Role.no_player async def query(self): self.A, self.B = await self.player.get_double_choice("Who do you want to exchange? (send two numbers)", self.player.other()) - async def simulate(self): + @Role.no_player + def simulate(self): self.player.other()[self.A].swap(self.player.other()[self.B]) class Drunk(Role): + @Role.no_player async def query(self): self.choice = await self.player.get_choice("Which card from the middle do you want to take?", ["left", "middle", "right"]) - async def simulate(self): + @Role.no_player + def simulate(self): self.player.day_role, self.game.middle_card[self.choice] = self.game.middle_card[self.choice], self.player.day_role # swap class Insomniac(Role): + @Role.no_player async def send_info(self): await self.player.send_info(f"You are now: {self.player.day_role}") @@ -128,15 +168,11 @@ class Villager(Role): class Tanner(Role): - def __init__(self, game, player=No_player()): - super().__init__(game, player) - self.is_Tanner = True + pass class Hunter(Role): - def __init__(self, game, player=No_player()): - super().__init__(game, player) - self.is_Hunter = True + pass class No_role(Role):