diff options
| author | 2019-09-04 18:43:27 -0400 | |
|---|---|---|
| committer | 2019-09-04 18:43:27 -0400 | |
| commit | 26361e1a162a29a0cda8a8c5f3ea99c784fe8984 (patch) | |
| tree | d697e799070bd2c2a6f00ec992c08d67adfb69b3 /bot | |
| parent | Hacktober cog cleanup (diff) | |
Update Snakes and Ladders
* Fix missing await preventing a non-started game from cancelling when timing out when waiting for the game to start
* Remove restriction on minimum number of players required for the game to run. This applies to both starting the game and for ending the game if players leave; the game can now be played solo.
* Prevent cancellation of the game if the player who initiated the game leaves but there are still people playing
* Allow Moderation staff to cancel a game they are not part of
* Fix issue where a game is not being properly ended if all the players leave
Diffstat (limited to '')
| -rw-r--r-- | bot/seasons/evergreen/snakes/utils.py | 80 | 
1 files changed, 43 insertions, 37 deletions
diff --git a/bot/seasons/evergreen/snakes/utils.py b/bot/seasons/evergreen/snakes/utils.py index e8d2ee44..f6b5ea16 100644 --- a/bot/seasons/evergreen/snakes/utils.py +++ b/bot/seasons/evergreen/snakes/utils.py @@ -13,6 +13,8 @@ from PIL.ImageDraw import ImageDraw  from discord import File, Member, Reaction  from discord.ext.commands import Context +from bot.constants import Roles +  SNAKE_RESOURCES = Path("bot/resources/snakes").absolute()  h1 = r'''``` @@ -412,7 +414,6 @@ class SnakeAndLaddersGame:              "**Snakes and Ladders**: A new game is about to start!",              file=File(                  str(SNAKE_RESOURCES / "snakes_and_ladders" / "banner.jpg"), -                # os.path.join("bot", "resources", "snakes", "snakes_and_ladders", "banner.jpg"),                  filename='Snakes and Ladders.jpg'              )          ) @@ -435,8 +436,9 @@ class SnakeAndLaddersGame:                  if reaction.emoji == JOIN_EMOJI:                      await self.player_join(user)                  elif reaction.emoji == CANCEL_EMOJI: -                    if self.ctx.author == user: -                        await self.cancel_game(user) +                    if user == self.author or not self._mod_in_game_check(user): +                        # Allow game author or non-playing moderation staff to cancel a waiting game +                        await self.cancel_game()                          return                      else:                          await self.player_leave(user) @@ -451,7 +453,7 @@ class SnakeAndLaddersGame:              except asyncio.TimeoutError:                  log.debug("Snakes and Ladders timed out waiting for a reaction") -                self.cancel_game(self.author) +                await self.cancel_game()                  return  # We're done, no reactions for the last 5 minutes      async def _add_player(self, user: Member): @@ -488,20 +490,16 @@ class SnakeAndLaddersGame:              delete_after=10          ) -    async def player_leave(self, user: Member): +    async def player_leave(self, user: Member) -> bool:          """          Handle players leaving the game. -        Leaving is prevented if the user initiated the game or if they weren't part of it in the -        first place. +        Leaving is prevented if the user wesn't part of the game. + +        If the number of players reaches 0, the game is terminated. In this case, a sentinel boolean +        is returned True to prevent a game from continuing after it's destroyed.          """ -        if user == self.author: -            await self.channel.send( -                user.mention + " You are the author, and cannot leave the game. Execute " -                "`sal cancel` to cancel the game.", -                delete_after=10 -            ) -            return +        is_surrendered = False  # Sentinel value to assist with stopping a surrendered game          for p in self.players:              if user == p:                  self.players.remove(p) @@ -512,17 +510,18 @@ class SnakeAndLaddersGame:                      delete_after=10                  ) -                if self.state != 'waiting' and len(self.players) == 1: +                if self.state != 'waiting' and len(self.players) == 0:                      await self.channel.send("**Snakes and Ladders**: The game has been surrendered!") +                    is_surrendered = True                      self._destruct() -                return -        await self.channel.send(user.mention + " You are not in the match.", delete_after=10) -    async def cancel_game(self, user: Member): -        """Allow the game author to cancel the running game.""" -        if not user == self.author: -            await self.channel.send(user.mention + " Only the author of the game can cancel it.", delete_after=10) -            return +                return is_surrendered +        else: +            await self.channel.send(user.mention + " You are not in the match.", delete_after=10) +            return is_surrendered + +    async def cancel_game(self): +        """Cancel the running game."""          await self.channel.send("**Snakes and Ladders**: Game has been canceled.")          self._destruct() @@ -530,21 +529,16 @@ class SnakeAndLaddersGame:          """          Allow the game author to begin the game. -        The game cannot be started if there aren't enough players joined or if the game is in a -        waiting state. +        The game cannot be started if the game is in a waiting state.          """          if not user == self.author:              await self.channel.send(user.mention + " Only the author of the game can start it.", delete_after=10)              return -        if len(self.players) < 1: -            await self.channel.send( -                user.mention + " A minimum of 2 players is required to start the game.", -                delete_after=10 -            ) -            return +          if not self.state == 'waiting':              await self.channel.send(user.mention + " The game cannot be started at this time.", delete_after=10)              return +          self.state = 'starting'          player_list = ', '.join(user.mention for user in self.players)          await self.channel.send("**Snakes and Ladders**: The game is starting!\nPlayers: " + player_list) @@ -565,8 +559,6 @@ class SnakeAndLaddersGame:          self.state = 'roll'          for user in self.players:              self.round_has_rolled[user.id] = False -        # board_img = Image.open(os.path.join( -        #     "bot", "resources", "snakes", "snakes_and_ladders", "board.jpg"))          board_img = Image.open(str(SNAKE_RESOURCES / "snakes_and_ladders" / "board.jpg"))          player_row_size = math.ceil(MAX_PLAYERS / 2) @@ -612,6 +604,7 @@ class SnakeAndLaddersGame:          for emoji in GAME_SCREEN_EMOJI:              await self.positions.add_reaction(emoji) +        is_surrendered = False          while True:              try:                  reaction, user = await self.ctx.bot.wait_for( @@ -623,11 +616,12 @@ class SnakeAndLaddersGame:                  if reaction.emoji == ROLL_EMOJI:                      await self.player_roll(user)                  elif reaction.emoji == CANCEL_EMOJI: -                    if self.ctx.author == user: -                        await self.cancel_game(user) +                    if self._is_moderator(user) and not self._mod_in_game_check(user): +                        # Only allow non-playing moderation staff to cancel a running game +                        await self.cancel_game()                          return                      else: -                        await self.player_leave(user) +                        is_surrendered = await self.player_leave(user)                  await self.positions.remove_reaction(reaction.emoji, user) @@ -636,11 +630,14 @@ class SnakeAndLaddersGame:              except asyncio.TimeoutError:                  log.debug("Snakes and Ladders timed out waiting for a reaction") -                await self.cancel_game(self.author) +                await self.cancel_game()                  return  # We're done, no reactions for the last 5 minutes          # Round completed -        await self._complete_round() +        # Check to see if the game was surrendered before completing the round, without this +        # sentinel, the game object would be deleted but the next round still posted into purgatory +        if not is_surrendered: +            await self._complete_round()      async def player_roll(self, user: Member):          """Handle the player's roll.""" @@ -708,3 +705,12 @@ class SnakeAndLaddersGame:          if is_reversed:              x_level = 9 - x_level          return x_level, y_level + +    def _mod_in_game_check(self, user: Member) -> bool: +        """Return True if user is a Moderator and playing the currently running game.""" +        return all((self._is_moderator(user), user in self.players)) + +    @staticmethod +    def _is_moderator(user: Member) -> bool: +        """Return True if the user is a Moderator.""" +        return Roles.moderator in [role.id for role in user.roles]  |