import os import discord from enum import Enum from random import shuffle import asyncio from dotenv import load_dotenv load_dotenv() TOKEN = os.getenv('DISCORD_TOKEN') class Role: def __init__(self, game): self.game = game self.copy = self def setPlayer(self, player): self.player = player async def phase1(self): # query stuff + doppelganger simulation pass async def phase2(self): # werewolve stuff + seer info pass async def phase3(self): # robber simulation & info pass async def phase4(self): # troublemaker simulation pass async def phase5(self): # mostly sending info + drunk simulation pass @staticmethod def match(message, game): for role_class in Role.role_set: if message.casefold() == role_class.name(): return role_class(game) @classmethod def name(cls): return cls.__name__.casefold() def __str__(self): return self.name() class Doppelganger(Role): order = 1 class Werewolve(Role): order = 2 def setPlayer(self, player): super().setPlayer(player) self.game.werewolve_list.append(player) async def phase2(self): if len(self.game.werewolve_list) >= 2: await self.player.send("Werewolves: " + str(self.game.werewolve_list)) else: await self.player.send("You are the only werewolve") await self.player.send("Which card in the middle do you want to look at?") self.choice = await self.player.get_choice(["left", "middle", "right"]) await self.player.send("A card in the middle is: " + self.game.middle_card[self.choice].name()) class Minion(Role): order = 3 async def phase2(self): if len(self.game.werewolve_list) == 0: await self.player.send("There were no werewolves so you became one") else: await self.player.send("Werewolves: " + str(self.game.werewolve_list)) class Mason(Role): order = 4 def setPlayer(self, player): super().setPlayer(player) self.game.mason_list.append(player) async def phase2(self): await self.player.send("Mason " + str(self.game.mason_list)) class Seer(Role): order = 5 async def phase1(self): await self.player.send("Which 1 player card or 2 middle cards do you want to look at?") self.choice = await self.player.get_choice(self.player.other() + ["left & middle", "middle & right", "left & right"]) async def phase2(self): if self.choice < len(self.player.other()): await self.player.send(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 await self.player.send(str(self.game.middle_card[a]) + " " + str(self.game.middle_card[b])) class Robber(Role): order = 6 async def phase1(self): await self.player.send("Which player do you want to rob?") self.choice = await self.player.get_choice(self.player.other()) async def phase3(self): Player.swap(self.player, self.player.other()[self.choice]) await self.player.send("You robbed: " + str(self.player.day_role)) class Troublemaker(Role): order = 7 async def phase1(self): await self.player.send("Who do you want to exchange? (send two separate numbers)") self.A = await self.player.get_choice(self.player.other()) self.B = await self.player.get_choice(self.player.other()) async def phase4(self): Player.swap(self.player.other()[self.A], self.player.other()[self.B]) # receive conformation await self.player.send("Received " + str(self.A) + " " + str(self.B)) class Drunk(Role): order = 8 async def phase1(self): await self.player.send("Which card from the middle do you want to take?") self.choice = await self.player.get_choice(["left", "middle", "right"]) async def phase5(self): self.player.day_role, self.game.middle_card[self.choice] = self.game.middle_card[self.choice], self.player.day_role #receive conformation await self.player.send("Received " + str(self.choice)) class Insomniac(Role): order = 9 async def phase5(self): await self.player.send("You are now: " + str(self.player.day_role)) class Villiager(Role): order = 10 class Tanner(Role): order = 11 class Hunter(Role): order = 12 class No_role(Role): order = 1000 Role.role_set = [Doppelganger, Werewolve, Minion, Mason, Seer, Robber, Troublemaker, Drunk, Insomniac, Villiager, Tanner, Hunter] class Player: @staticmethod async def make(member, game): p = Player() p.member = member p.dm = member.dm_channel or await member.create_dm() p.game = game return p @staticmethod def swap(player_A, player_B): player_A.day_role, player_B.day_role = player_B.day_role, player_A.day_role def setRole(self, role): self.night_role = role self.day_role = role def name(self): return self.member.name def __repr__(self): return self.name() def other(self): return [p for p in self.game.player_list if p != self] async def send(self, message): await self.dm.send(message) async def ask_choice(self, options): await self.send('\n'.join( "(" + str(i) + ") " + str(options[i]) for i in range(len(options)) )) async def receive_choice(self, options): def check(choice): return choice.channel == self.dm and choice.content.isdigit() and 0 <= int(choice.content) < len(options) return int((await self.game.bot.wait_for('message', timeout=30.0, check = check)).content) async def get_choice(self, options): await self.ask_choice(options) return await self.receive_choice(options) async def cast_vote(self, options): self.vote = options[await self.get_choice(options)] class No_player(Player): def __init__(self): self.day_role = No_role() def name(self): return "no one" def __str__(self): return self.name() class one_night: def __init__(self, bot): self.running = False self.bot = bot self.player_list = [] self.role_list = Role.role_set def set_channel(self, channel): self.channel = channel async def send(self, message): await self.channel.send(message) async def receive(self, command): def check(msg): return msg.channel == self.channel and msg.content.startswith(command) return await bot.wait_for('message', check = check) def setup(self): self.werewolve_list = [] self.mason_list = [] async def set_players(self): await self.send("Who is playing?") msg = await self.receive('$players') # use info from last round otherwise if not msg.content.startswith('$players last'): self.player_list = [await Player.make(mem, self) for mem in msg.mentions] # check conditions if not 0 <= len(self.player_list) <= 10: raise ValueError("Invalid number of players: " + str(len(self.player_list))) # send confirmation await self.send("Players: " + str([p.name() for p in self.player_list])) async def set_roles(self): await self.send("With which roles do you want to play?") msg = await self.receive('$roles') # use info from last round otherwise if not msg.content.startswith('$roles last'): tmp_role = [Role.match(r, self) for r in msg.content.split()[1:]] # invalid input if None in tmp_role: raise ValueError("Invalid list of roles: " + str(tmp_role)) self.role_list = tmp_role # check condition if not len(self.role_list) == (len(self.player_list) + 3): raise ValueError("Invalid number of roles: " + str(len(self.role_list)) + " with " + str(len(self.player_list)) + " players") # send confirmation await self.send("Roles: " + str([r.name() for r in self.role_list])) def distribute_roles(self): shuffle(self.role_list) for i in range(len(self.player_list)): self.player_list[i].setRole(self.role_list[i]) self.role_list[i].setPlayer(self.player_list[i]) self.middle_card = self.role_list[-3:] self.active_role = sorted(self.role_list[:-3], key = lambda x: x.order) #necessary? async def start_night(self): await asyncio.gather( *[p.send("The night has begun") for p in self.player_list] ) async def send_role(self): await asyncio.gather( *[p.send("Your role: " + p.night_role.name()) for p in self.player_list] ) 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] ) async def start_day(self): await self.send("The day has started") async def vote(self, options): # vote await self.receive('$vote') await self.send("Vote in DM") await asyncio.gather( *[p.cast_vote(options) for p in self.player_list] ) await self.send("Votes\n\n" + '\n'.join(str(p) + " :arrow_right: " + str(p.vote) for p in self.player_list)) def tally(self, options): for o in options: o.tally = 0 for p in self.player_list: p.vote.tally += 1 def who_dead(self, options): maxi = max(o.tally for o in options) dead = [p for p in self.player_list if p.tally >= maxi] for d in dead: d.dead = True if isinstance(d.day_role.copy, Hunter): dead.append(d.vote) return dead def who_won(self, dead): no_dead = (len(dead) == 0) tanner_dead = any(isinstance(d.day_role.copy, Tanner) for d in dead) werewolve_dead = any(isinstance(d.day_role.copy, Werewolve) for d in dead) werewolve_in_game = any(isinstance(p.day_role.copy, Werewolve) for p in self.player_list) minion_dead = any(isinstance(d.day_role.copy, Minion) for d in dead) minion_in_game = any(isinstance(p.day_role.copy, Minion) for p in self.player_list) werewolve_won = False village_won = False tanner_won = False # could make it shorter using boolean algebra if no_dead: if werewolve_in_game: werewolve_won = True else: village_won = True else: if tanner_dead: tanner_won = True if werewolve_dead: village_won = True else: if werewolve_dead: village_won = True else: if minion_dead: if werewolve_in_game: werewolve_won = True else: village_won = True else: if minion_in_game: werewolve_won = True return werewolve_won, village_won, tanner_won, dead async def result(self, werewolve_won, village_won, tanner_won, dead): if werewolve_won: await self.send("Werewolves won!") if village_won: await self.send("Village won!") for d in dead: if isinstance(d.day_role.copy, Tanner): await self.send(str(p) + " won a tanner") await self.send("Dead: " + ', '.join(str(d) for d in dead)) await self.send('\n'.join(str(p.tally) + " votes for " + str(p) + " who is " + str(p.day_role) + " (was " + str(p.night_role) + ") " for p in self.player_list)) await self.send("Middle cards: " + ', '.join(str(r) for r in self.middle_card)) # debug await self.send("Success") def end(self): self.running = False async def game(self): try: self.setup() await self.set_players() await self.set_roles() self.distribute_roles() await self.start_night() await self.send_role() await self.night_phases() await self.start_day() #discussion timer options = self.player_list + [No_role(self)] await self.vote(options) self.tally(options) await self.result(*self.who_won(self.who_dead(options))) except ValueError as error: await self.send(error) except asyncio.TimeoutError: await self.send("Error: I got bored waiting for your input") finally: self.end() await self.send("Game ended") bot = discord.Client() @bot.event async def on_ready(): print('We have logged in as {0.user}'.format(bot)) async def hello(message): print("Hello") await message.channel.send('Hello!:regional_indicator_a:') print(message.mentions) @bot.event async def on_message(message): global running if message.author == bot.user: return if message.content.startswith('$hello'): await hello(message) return if message.content.startswith('$logout'): await bot.logout() return if message.content.startswith('$werewolve'): # start (only one instance running) if werewolve_game.running: await message.channel.send("Sorry! A game is already running") return werewolve_game.running = True werewolve_game.set_channel(message.channel) await werewolve_game.game() return werewolve_game = one_night(bot) bot.run(TOKEN)