From 8c7e0526472ca2c703bad2d9110e95dc00cac65d Mon Sep 17 00:00:00 2001 From: Bobloy Date: Wed, 11 Apr 2018 17:01:49 -0400 Subject: [PATCH] choices --- werewolf/game.py | 68 ++++++++++++++++--- werewolf/role.py | 19 ++++-- werewolf/roles/seer.py | 107 ++++++++++++++++++++++++++++++ werewolf/roles/vanillawerewolf.py | 19 ++++-- werewolf/roles/villager.py | 23 +++++-- werewolf/votegroup.py | 3 +- werewolf/votegroups/wolfvote.py | 9 ++- werewolf/werewolf.py | 59 +++++++++++++--- 8 files changed, 273 insertions(+), 34 deletions(-) create mode 100644 werewolf/roles/seer.py diff --git a/werewolf/game.py b/werewolf/game.py index daead30..66b5245 100644 --- a/werewolf/game.py +++ b/werewolf/game.py @@ -119,7 +119,7 @@ class Game: print("Pre-cycle") await asyncio.sleep(1) - await self._cycle() # Start the loop + asyncio.ensure_future(self._cycle()) # Start the loop ############START Notify structure############ async def _cycle(self): @@ -276,9 +276,15 @@ class Game: await asyncio.sleep(15) await self._at_day_start() - + + async def _at_visit(self, target, source): # ID 8 + if self.game_over: + return + data = {"target": target, "source": source} + await self._notify(8, data) + async def _notify(self, event, data=None): - for i in range(8): + for i in range(1,7): # action guide 1-6 (0 is no action) tasks = [] # Role priorities role_order = [role for role in self.roles if role.action_list[event][1]==i] @@ -288,8 +294,8 @@ class Game: vote_order = [vg for vg in self.vote_groups.values() if vg.action_list[event][1]==i] for vote_group in vote_order: tasks.append(asyncio.ensure_future(vote_group.on_event(event, data), loop=self.loop)) - - await asyncio.gather(*tasks) + if tasks: + await asyncio.gather(*tasks) # Run same-priority task simultaneously ############END Notify structure############ @@ -353,9 +359,37 @@ class Game: self.players = [player for player in self.players if player.member != member] await channel.send("{} chickened out, player count is now **{}**".format(member.mention, len(self.players))) + async def choose(self, ctx, data): + """ + Arbitrary decision making + Example: seer picking target to see + """ + player = await self.get_player_by_member(ctx.author) + + if player is None: + await ctx.send("You're not in this game!") + return + + if not player.alive: + await ctx.send("**Corpses** can't vote...") + return + + if player.blocked: + await ctx.send("Something is preventing you from doing this...") + return + + # Let role do target validation, might be alternate targets + # I.E. Go on alert? y/n + + await player.choose(ctx, data) + + + + async def vote(self, author, id, channel): """ Member attempts to cast a vote (usually to lynch) + Also used in vote groups """ player = await self.get_player_by_member(author) @@ -393,8 +427,8 @@ class Game: elif self.p_channels[channel.name]["votegroup"] is not None: await self.vote_groups[channel.name].vote(target, author, id) else: # Private channel voting, send to role - await self.player.role.vote(target, id) - + # await self.player.role.vote(target, id) + # I'll think of something later @@ -420,13 +454,20 @@ class Game: async def eval_results(self, target, source=None, method = None): return "{} was found dead".format(target.member.display_name) - async def kill(self, target, source=None, method: str=None): + async def kill(self, target_id, source=None, method: str=None): """ Attempt to kill a target Source allows admin override Be sure to remove permissions appropriately Important to finish execution before triggering notify """ + target = await self.get_night_target(target_id, source) + if source is not None: + if source.blocked: + # Do nothing if blocked, blocker handles text + return + else: + if not target.protected: target.alive = False await self._at_kill(target) @@ -436,16 +477,23 @@ class Game: else: target.protected = False - async def lynch(self, target): + async def lynch(self, target_id): """ Attempt to lynch a target Important to finish execution before triggering notify """ + target = await self.get_day_target(target_id) target.alive = False await self._at_hang(target) if not target.alive: # Still dead after notifying await self.dead_perms(target.member) - + + async def get_night_target(self, target_id, source=None): + return self.players[target_id] # For now + + async def get_day_target(self, target_id, source=None): + return self.player[target_id] # For now + async def get_roles(self, game_code=None): if game_code is not None: self.game_code=game_code diff --git a/werewolf/role.py b/werewolf/role.py index ecf8903..d4c73f2 100644 --- a/werewolf/role.py +++ b/werewolf/role.py @@ -26,7 +26,7 @@ class Role: _at_night_start 0. No Action 1. Detain actions (Jailer/Kidnapper) - 2. Group discussions and Pick targets + 2. Group discussions and choose targets _at_night_end 0. No Action @@ -34,7 +34,7 @@ class Role: 2. Target switching and role blocks (bus driver, witch, escort) 3. Protection / Preempt actions (bodyguard/framer) 4. Non-disruptive actions (seer/silencer) - 5. Disruptive actions (werewolf kill) + 5. Disruptive actions (Killing) 6. Role altering actions (Cult / Mason) """ @@ -63,7 +63,8 @@ class Role: (self._at_hang, 0), (self._at_day_end, 0), (self._at_night_start, 0), - (self._at_night_end, 0) + (self._at_night_end, 0), + (self._at_visit, 0) ] async def on_event(self, event, data): @@ -124,6 +125,16 @@ class Role: async def _at_night_end(self, data=None): pass - async def vote(self, target, id): + async def _at_visit(self, data=None): + pass + + async def visit(self, source): + """ + Called whenever a night action targets you + Source is the player who visited you + """ + pass + + async def choose(self, ctx, data): """Handle night actions""" pass \ No newline at end of file diff --git a/werewolf/roles/seer.py b/werewolf/roles/seer.py new file mode 100644 index 0000000..b03894e --- /dev/null +++ b/werewolf/roles/seer.py @@ -0,0 +1,107 @@ +import asyncio + +from werewolf.role import Role + +class Seer(Role): + + rand_choice = False # Determines if it can be picked as a random role (False for unusually disruptive roles) + category = [1,2] # List of enrolled categories (listed above) + allignment = 1 # 1: Town, 2: Werewolf, 3: Neutral + channel_id = "" # Empty for no private channel + unique = False # Only one of this role per game + game_start_message=( + "Your role is **Seer**\n" + "You win by lynching all evil in the town\n" + "Lynch players during the day with `[p]ww vote `\n" + "Check for werewolves at night with `[p]ww choose `" + ) + + def __init__(self, game): + super().__init__() + # self.game = game + # self.player = None + # self.blocked = False + # self.properties = {} # Extra data for other roles (i.e. arsonist) + self.see_target = None + self.action_list = [ + (self._at_game_start, 0), # (Action, Priority) + (self._at_day_start, 0), + (self._at_voted, 0), + (self._at_kill, 0), + (self._at_hang, 0), + (self._at_day_end, 0), + (self._at_night_start, 2), + (self._at_night_end, 4) + ] + + # async def on_event(self, event, data): + # """ + # See Game class for event guide + # """ + + # await self.action_list[event][0](data) + + + # async def assign_player(self, player): + # """ + # Give this role a player + # Can be used after the game has started (Cult, Mason, other role swap) + # """ + + # player.role = self + # self.player = player + + async def _get_role(self, source=None): + """ + Interaction for powerful access of role + Unlikely to be able to deceive this + """ + return "Villager" + + async def _see_role(self, source=None): + """ + Interaction for investigative roles. + More common to be able to deceive these roles + """ + return "Villager" + + # async def _at_game_start(self, data=None): + # pass + + # async def _at_day_start(self, data=None): + # pass + + # async def _at_voted(self, target=None): + # pass + + # async def _at_kill(self, target=None): + # pass + + # async def _at_hang(self, target=None): + # pass + + # async def _at_day_end(self): + # pass + + async def _at_night_start(self): + await self.game.generate_targets(self.player.member) + await self.player.member.send("{}\n**Pick a target to see tonight**\n") + + + async def _at_night_end(self): + + + async def choose(self, ctx, data): + """Handle night actions""" + id = int(data) + try: + target = game.players[id] + except IndexError: + target = None + + if target is None: + await ctx.send("Not a valid ID") + return + + self.see_target = id + await ctx.send("**You will attempt to see the role of {} tonight...**".format(target.member.display_name)) diff --git a/werewolf/roles/vanillawerewolf.py b/werewolf/roles/vanillawerewolf.py index 6d0c26b..5bccea7 100644 --- a/werewolf/roles/vanillawerewolf.py +++ b/werewolf/roles/vanillawerewolf.py @@ -32,7 +32,8 @@ class VanillaWerewolf(Role): (self._at_hang, 0), (self._at_day_end, 0), (self._at_night_start, 2), # Get vote priority - (self._at_night_end, 0) + (self._at_night_end, 0), + (self._at_visit, 0) ] self.killer = None # Added killer @@ -93,7 +94,17 @@ class VanillaWerewolf(Role): # async def _at_night_end(self, data=None): # super()._at_night_end(data) - - async def vote(self, target, id): + + # async def _at_visit(self, data=None): + # pass + + # async def visit(self, source): + # """ + # Called whenever a night action targets you + # Source is the player who visited you + # """ + # pass + + async def choose(self, ctx, data): """Handle night actions""" - await self.player.member.send("Use this command in your wolf channel at night") + await self.player.member.send("Use `[p]ww vote` in your werewolf channel") diff --git a/werewolf/roles/villager.py b/werewolf/roles/villager.py index 3e1ac22..396a8e8 100644 --- a/werewolf/roles/villager.py +++ b/werewolf/roles/villager.py @@ -5,14 +5,14 @@ from werewolf.role import Role class Villager(Role): rand_choice = False # Determines if it can be picked as a random role (False for unusually disruptive roles) - category = [0] # List of enrolled categories (listed above) - allignment = 0 # 1: Town, 2: Werewolf, 3: Neutral + category = [1] # List of enrolled categories (listed above) + allignment = 1 # 1: Town, 2: Werewolf, 3: Neutral channel_id = "" # Empty for no private channel unique = False # Only one of this role per game game_start_message=( "Your role is **Villager**\n" "You win by lynching all evil in the town\n" - "Lynch players during the day with `[p]ww vote `\n" + "Lynch players during the day with `[p]ww vote `" ) def __init__(self, game): @@ -30,7 +30,8 @@ class Villager(Role): # (self._at_hang, 0), # (self._at_day_end, 0), # (self._at_night_start, 0), - # (self._at_night_end, 0) + # (self._at_night_end, 0), + # (self._at_visit, 0) # ] # async def on_event(self, event, data): @@ -87,3 +88,17 @@ class Villager(Role): # async def _at_night_end(self): # pass + + # async def _at_visit(self, data=None): + # pass + + # async def visit(self, source): + # """ + # Called whenever a night action targets you + # Source is the player who visited you + # """ + # pass + + # async def choose(self, ctx, data): + # """Handle night actions""" + # pass \ No newline at end of file diff --git a/werewolf/votegroup.py b/werewolf/votegroup.py index 7349846..a4eb768 100644 --- a/werewolf/votegroup.py +++ b/werewolf/votegroup.py @@ -29,7 +29,8 @@ class VoteGroup: (self._at_hang, 0), (self._at_day_end, 0), (self._at_night_start, 2), - (self._at_night_end, 0) + (self._at_night_end, 0), + (self._at_visit, 0) ] async def on_event(self, event, data): diff --git a/werewolf/votegroups/wolfvote.py b/werewolf/votegroups/wolfvote.py index 51d8d8b..80bc2a8 100644 --- a/werewolf/votegroups/wolfvote.py +++ b/werewolf/votegroups/wolfvote.py @@ -83,13 +83,16 @@ class WolfVote(VoteGroup): vote_list = list(self.vote_results.values()) if vote_list: - target = max(set(vote_list), key=vote_list.count) + target_id = max(set(vote_list), key=vote_list.count) if target and self.killer: - await self.game.kill(target, self.killer, random.choice(self.kill_messages)) + await self.game.kill(target_id, self.killer, random.choice(self.kill_messages)) else: await self.channel.send("**No kill will be attempted tonight...**") - + + # async def _at_visit(self, data=None): + # pass + async def vote(self, target, author, id): """ Receive vote from game diff --git a/werewolf/werewolf.py b/werewolf/werewolf.py index 2672805..6a3ade3 100644 --- a/werewolf/werewolf.py +++ b/werewolf/werewolf.py @@ -26,7 +26,7 @@ class Werewolf: self.config.register_guild(**default_guild) self.games = {} # Active games stored here, id is per guild - + @commands.group() async def ww(self, ctx: commands.Context): """ @@ -34,7 +34,8 @@ class Werewolf: """ if ctx.invoked_subcommand is None: await ctx.send_help() - + + @guild_only() @ww.command() async def new(self, ctx, game_code): """ @@ -49,7 +50,7 @@ class Werewolf: await ctx.send("New game has started") - + @guild_only() @ww.command() async def join(self, ctx): """ @@ -63,7 +64,8 @@ class Werewolf: return await game.join(ctx.author, ctx.channel) - + + @guild_only() @ww.command() async def quit(self, ctx): """ @@ -74,6 +76,7 @@ class Werewolf: await game.quit(ctx.author, ctx.channel) + @guild_only() @ww.command() async def start(self, ctx): """ @@ -85,6 +88,7 @@ class Werewolf: await game.setup(ctx) + @guild_only() @ww.command() async def stop(self, ctx): """ @@ -96,7 +100,7 @@ class Werewolf: game.game_over = True - + @guild_only() @ww.command() async def vote(self, ctx, id: int): """ @@ -110,11 +114,24 @@ class Werewolf: if id is None: await ctx.send("`id` must be an integer") return - + + # if ctx.guild is None: + # # DM nonsense, find their game + # # If multiple games, panic + # for game in self.games.values(): + # if await game.get_player_by_member(ctx.author): + # break #game = game + # else: + # await ctx.send("You're not part of any werewolf game") + # return + # else: + game = self._get_game(ctx.guild) - if not game: - await ctx.send("No game running, cannot vote") + if game is None: + await ctx.send("No game running, cannot vote") + return + # Game handles response now channel = ctx.channel if channel == game.village_channel: @@ -123,8 +140,34 @@ class Werewolf: await game.vote(ctx.author, id, channel) else: await ctx.send("Nothing to vote for in this channel") + + @ww.command() + async def choose(self, ctx, data): + """ + Arbitrary decision making + Handled by game+role + Can be received by DM + """ + if ctx.guild is not None: + await ctx.send("This action is only available in DM's") + return + + # DM nonsense, find their game + # If multiple games, panic + for game in self.games.values(): + if await game.get_player_by_member(ctx.author): + break #game = game + else: + await ctx.send("You're not part of any werewolf game") + return + + await game.choose(ctx, data) + def _get_game(self, guild, game_code=None): + if guild is None: + # Private message, can't get guild + return None if guild.id not in self.games: if not game_code: return None