from random import shuffle import time import asyncio import discord from .roles import Role, Doppelganger, Werewolf, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Tanner, Hunter, No_role from .players import Player, No_player class Werewolf_game: name = "One Night Ultimate Werewolf" def __init__(self, bot, channel): self.running = False self.bot = bot self.channel = channel self.player_list = [] self.role_list = [] self.discussion_time = 301 # seconds async def send(self, message): await self.channel.send(embed=discord.Embed(description=message, color=0x00ffff)) async def for_all_player(self, call): await asyncio.gather(*[call(p) for p in self.player_list]) async def set_players(self, msg): self.player_list = [await Player.make(mem, self) for mem in msg.mentions] await self.send(f"Players: {', '.join(p.name() for p in self.player_list)}") # send confirmation async def set_roles(self, suggestions): self.role_list = [Role.match(r) for r in suggestions] # raises ValueError await self.send(f"Roles: {', '.join(r.name() for r in self.role_list)}") # send confirmation async def set_time(self, msg): self.discussion_time = int(msg) * 60 + 1 await self.send(f"You have set the discussion time to {self.discussion_time//60} minutes") # send confirmation def check(self): if not 0 <= len(self.player_list) <= 10: raise ValueError(f"Invalid number of players: {len(self.player_list)}") if not len(self.role_list) == (len(self.player_list) + 3): raise ValueError(f"Invalid number of roles: {len(self.role_list)} with {len(self.player_list)} players") def setup(self): self.role = dict() # setting default value for r in Role.__subclasses__(): if r == No_role: continue if r in [Werewolf, Mason]: self.role[r] = [] else: r(self).add_yourself() self.voting_list = self.player_list + [No_player()] for c in self.voting_list: c.reset() def distribute_roles(self): shuffle(self.role_list) for i in range(len(self.player_list)): role_obj = self.role_list[i](self, self.player_list[i]) self.player_list[i].setRole(role_obj) role_obj.add_yourself() self.middle_card = [r(self) for r in self.role_list[-3:]] async def start_night(self): await self.for_all_player(lambda p: p.send_normal("*The night has begun*")) async def send_role(self): await self.for_all_player(lambda p: p.send_info(f"Your role: **{p.night_role.name()}**")) async def night_phases(self): # TODO: implement waiting if role in middle 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") def remaining_time_string(self): t = int(self.start_time + self.discussion_time - time.time()) return f"{t//60} minute(s) and {t%60} second(s)" async def discussion_timer(self): self.start_time = time.time() await self.send(f"You have {self.remaining_time_string()} to discuss") await asyncio.sleep(self.discussion_time / 2) await self.send(f"{self.remaining_time_string()} remaining") await asyncio.sleep(self.discussion_time / 2 - 60) await self.send(f"{self.remaining_time_string()} remaining") await asyncio.sleep(30) await self.send(f"{self.remaining_time_string()} remaining") await asyncio.sleep(30) async def early_vote(self): await self.for_all_player(lambda p: p.ready_to_vote()) async def discussion_finished(self): done, pending = await asyncio.wait([self.discussion_timer(), self.early_vote()], return_when=asyncio.FIRST_COMPLETED) for p in pending: p.cancel() await asyncio.wait(pending) async def vote(self): await self.send("Vote in DM") 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: p.vote.tally += 1 def who_dead(self): maxi = max(c.tally for c in self.voting_list) dead = [p for p in self.player_list if p.tally >= maxi] for d in dead: d.dead = True 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_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 tanner_won = False # could make it shorter using boolean algebra if no_dead: if werewolf_in_game: werewolf_won = True else: village_won = True else: if tanner_dead: tanner_won = True if werewolf_dead: village_won = True else: if werewolf_dead: village_won = True else: if minion_dead: if werewolf_in_game: werewolf_won = True else: village_won = True else: if minion_in_game: werewolf_won = True for p in self.player_list: if p.day_role.is_role(Werewolf) or p.day_role.is_role(Minion): p.won = werewolf_won elif p.day_role.is_role(Tanner): p.won = p.dead else: p.won = village_won return werewolf_won, village_won, tanner_won, dead async def result(self, werewolf_won, village_won, tanner_won, dead): winnning = [] if werewolf_won: winnning.append("Werewolves") if village_won: winnning.append("Village") if tanner_won: winnning.append(f"{sum(1 for d in dead if d.day_role.is_role(Tanner))} tanner") if len(winnning) == 0: winnning = ["No one"] embed = discord.Embed(title=f"{' and '.join(winnning)} won!", color=0x00ffff) for p in self.player_list: won_emoji = ":trophy:" if p.won else ":frowning2:" dead_emoji = ":skull:" if p.dead else ":no_mouth:" 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) def end(self): self.running = False async def round(self): try: self.check() self.setup() self.running = True self.distribute_roles() await self.start_night() await self.send_role() await self.night_phases() await self.start_day() await self.discussion_finished() await self.vote() self.tally() await self.result(*self.who_won(self.who_dead())) await self.send("Round ended") finally: self.end()