From fce32fc0262d7dae3c84d0229da06596bc4a45fc Mon Sep 17 00:00:00 2001 From: bibin Date: Fri, 27 Mar 2020 20:20:55 +0100 Subject: [PATCH] rewrite bot --- werewolve-bot.py | 737 +++++++++++++++++++++++++++-------------------- 1 file changed, 431 insertions(+), 306 deletions(-) diff --git a/werewolve-bot.py b/werewolve-bot.py index 0e088a1..551b476 100644 --- a/werewolve-bot.py +++ b/werewolve-bot.py @@ -8,56 +8,439 @@ from dotenv import load_dotenv load_dotenv() TOKEN = os.getenv('DISCORD_TOKEN') -class Role(Enum): - doppelganger = 1 - werewolve = 2 - minion = 3 - mason = 4 - seer = 5 - robber = 6 - troublemaker = 7 - drunk = 8 - insomniac = 9 - villiager = 10 - tanner = 11 - hunter = 12 + + +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: - def __init__(self, member, role): - self.member = member + @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 - self.vote = -1 - self.tally = 0 - async def create_dm(self): - self.dm = await self.member.create_dm() + 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 sendRole(self): - await self.send("Your role: " + self.night_role.name) + async def ask_choice(self, options): + await self.send('\n'.join( "(" + str(i) + ") " + str(options[i]) for i in range(len(options)) )) - async def sendPoll(self): - await self.send(Player.poll_message) + 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) - async def sendMiddle(self): - await self.send(Player.poll_middle) + return int((await self.game.bot.wait_for('message', timeout=30.0, check = check)).content) - async def receiveChoice(self): + async def get_choice(self, options): + await self.ask_choice(options) + return await self.receive_choice(options) - global bot + async def cast_vote(self, options): + self.vote = options[await self.get_choice(options)] - def check(vote): +class No_player(Player): - return vote.channel == self.dm and vote.content.isdigit() and 0 <= int(vote.content) < Player.size + def __init__(self): + self.day_role = No_role() - vote = await bot.wait_for('message', timeout=10.0, check = check) - await self.send("Received: " + vote.content) + def name(self): + return "no one" - return int(vote.content) + 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() @@ -68,7 +451,11 @@ async def on_ready(): -running = False +async def hello(message): + print("Hello") + await message.channel.send('Hello!:regional_indicator_a:') + + print(message.mentions) @bot.event async def on_message(message): @@ -80,292 +467,30 @@ async def on_message(message): if message.content.startswith('$hello'): - print("Hello") - await message.channel.send('Hello!:regional_indicator_a:') + await hello(message) + return - def check(vote): - return vote.content in ["a", "b"] and vote.channel == message.channel - vote = await bot.wait_for('message', timeout=10.0, check = check) - await message.channel.send("Received! " + vote.content) + if message.content.startswith('$logout'): + await bot.logout() + return if message.content.startswith('$werewolve'): # start (only one instance running) - if 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) - """ - if len(players) < 4: - await message.channel.send("To few players!") - return - - """ - - running = True - - - # setup - - members = [mem for mem in message.channel.members if not mem.bot] - - Player.size = len(members) - - - role_set = [Role.werewolve, Role.werewolve, Role.mason, Role.mason, Role.seer, Role.robber, Role.troublemaker] + (Player.size-4)*[Role.villiager] - shuffle(role_set) - - players = [] - - - werewolve = [] - minion = None - mason = [] - seer = None - robber = None - troublemaker = None - drunk = None - insomniac = None - villiager = [] - tanner = None - hunter = None - - for i in range(Player.size): - - players.append(Player(members[i], role_set[i])) - await players[i].create_dm() - - - if role_set[i] == Role.werewolve: - werewolve.append(players[i]) - - elif role_set[i] == Role.mason: - mason.append(players[i]) - - elif role_set[i] == Role.seer: - seer = players[i] - - elif role_set[i] == Role.robber: - robber = players[i] - - elif role_set[i] == Role.troublemaker: - troublemaker = players[i] - - elif role_set[i] == Role.drunk: - drunk = players[i] - - elif role_set[i] == Role.insomniac: - insomniac = players[i] - - elif role_set[i] == Role.villiager: - villiager.append(players[i]) - - elif role_set[i] == Role.tanner: - tanner = players[i] - - elif role_set[i] == Role.hunter: - hunter = players[i] - - middle_cards = role_set[-3:] - - - Player.poll_middle = "(0) left\n(1) middle\n(2) right" - - Player.poll_message = "" - for i in range(Player.size): - Player.poll_message += "(" + str(i) + ") " + players[i].member.name + "\n" - - # doing phase - - - #send role info to all - send_roles = [p.sendRole() for p in players] - await asyncio.gather(*send_roles) - - - - # query stuff - - #doppelganger stuff - - async def query_seer(): - if seer is None: - return - - await seer.send("Who do you want to look at?") - await seer.sendPoll() - - async def query_robber(): - if robber is None: - return - - await robber.send("Who do you want to rob?") - await robber.sendPoll() - - async def query_troublemaker(): - if troublemaker is None: - return - - await troublemaker.send("Who do you want to exchange?") - await troublemaker.sendPoll() - await troublemaker.sendPoll() - - async def query_drunk(): - if drunk is None: - return - - await drunk.send("Which card from the middle do you want to take?") - await drunk.sendMiddle() - - await asyncio.gather(query_seer(), query_robber(), query_troublemaker(), query_drunk()) - - - - #receive and confirm! - async def receive_seer(): - if seer is not None: - return await seer.receiveChoice() - - async def receive_robber(): - if robber is not None: - return await robber.receiveChoice() - - async def receive_troublemaker(): - if troublemaker is not None: - return await troublemaker.receiveChoice() - - async def receive_drunk(): - if drunk is not None: - return await drunk.receiveChoice() - - seerChoice, robberChoice, troublemakerChoice, drunkChoice = await asyncio.gather(receive_seer(), receive_robber(), receive_troublemaker(), receive_drunk()) - - - - # simulate - - #exchange robber - if robber is not None: - robber.day_role, players[robberChoice].day_role = players[robberChoice].day_role, robber.day_role - #exchange troublemaker - if troublemaker is not None: - A = players[troublemakerChoice[0]] - B = players[troublemakerChoice[1]] - A.day_role, B.day_role = B.day_role, A.day_role - #exchange drunk - if drunk is not None: - drunk.day_role, middle[drunkChoice] = middle[drunkChoice], drunk.day_role - - - - #send werewolves identity to werewolves and minion - async def send_werewolves(): - message = "" - for w in werewolve: - message += w.member.name + " " - - message += "were werewolves" - - sender = [bad.send(message) for bad in werewolve] - if minion is not None: - sender.append(minion.send(message)) - - await asyncio.gather(*sender) - - #send mason identity to masons - async def send_masons(): - message = "" - for m in mason: - message += m.member.name + " " - - message += " were masons" - - sender = [m.send(message) for m in mason] - await asyncio.gather(*sender) - - #send info to seer - async def send_seer(): - if seer is not None: - await seer.send(players[seerChoice].member.name + " was a " + players[seerChoice].night_role.name) - - #send info to robber - async def send_robber(): - if robber is not None: - await robber.send("You stole the role: " + players[robberChoice].night_role.name) - - #send insomniac new role to insomniac - async def send_insomniac(): - if insomniac is not None: - await insomniac.send("You are now a " + insomniac.day_role.name) - - await asyncio.gather(send_werewolves(), send_masons(), send_seer(), send_robber(), send_insomniac()) - - - - # discussion - - # vote - - def check_vote(vote): - return vote.content == "$vote" and vote.channel == message.channel - - await bot.wait_for('message', check = check_vote) - - await message.channel.send("Vote in DM") - - - send_votes = [p.sendPoll() for p in players] - await asyncio.gather(*send_votes) - - receive_votes = [p.receiveChoice() for p in players] - tmp = await asyncio.gather(*receive_votes) - for i in range(Player.size): - players[i].vote = tmp[i] - - for p in players: - players[p.vote].tally += 1 - - maxi = max( [p.tally for p in players] ) - - dead = [p for p in players if p.tally >= maxi] - - if hunter in dead: - dead.append(players[hunter.vote]) - - - - # result and end - # show day-role & night role - - msg = "" - for d in dead: - msg += d.member.name + " " - msg += "are dead!" - await message.channel.send(msg) - - if any(d in werewolve for d in dead): - msg = "The village won!" - else: - msg = "The werewolves won!" - - await message.channel.send(msg) - - msg = "" - for p in players: - msg += p.member.name + ": " + p.day_role.name + " (day) " + p.night_role.name + " (night) and voted for " + players[p.vote].member.name + "\n" - - await message.channel.send(msg) - - - - running = False + await werewolve_game.game() + return +werewolve_game = one_night(bot) bot.run(TOKEN) \ No newline at end of file