diff options
Diffstat (limited to 'bot/seasons/evergreen')
| -rw-r--r-- | bot/seasons/evergreen/8bitify.py | 6 | ||||
| -rw-r--r-- | bot/seasons/evergreen/minesweeper.py | 10 | ||||
| -rw-r--r-- | bot/seasons/evergreen/showprojects.py | 2 | ||||
| -rw-r--r-- | bot/seasons/evergreen/snakes/utils.py | 62 |
4 files changed, 40 insertions, 40 deletions
diff --git a/bot/seasons/evergreen/8bitify.py b/bot/seasons/evergreen/8bitify.py index 54db71db..60062fc1 100644 --- a/bot/seasons/evergreen/8bitify.py +++ b/bot/seasons/evergreen/8bitify.py @@ -13,17 +13,17 @@ class EightBitify(commands.Cog): @staticmethod def pixelate(image: Image) -> Image: - """Takes an image and pixelates it""" + """Takes an image and pixelates it.""" return image.resize((32, 32)).resize((1024, 1024)) @staticmethod def quantize(image: Image) -> Image: - """Reduces colour palette to 256 colours""" + """Reduces colour palette to 256 colours.""" return image.quantize(colors=32) @commands.command(name="8bitify") async def eightbit_command(self, ctx: commands.Context) -> None: - """Pixelates your avatar and changes the palette to an 8bit one""" + """Pixelates your avatar and changes the palette to an 8bit one.""" async with ctx.typing(): image_bytes = await ctx.author.avatar_url.read() avatar = Image.open(BytesIO(image_bytes)) diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py index 9dadb9f0..015b09df 100644 --- a/bot/seasons/evergreen/minesweeper.py +++ b/bot/seasons/evergreen/minesweeper.py @@ -175,7 +175,7 @@ class Minesweeper(commands.Cog): @commands.dm_only() @minesweeper_group.command(name="flag") async def flag_command(self, ctx: commands.Context, *coordinates: CoordinateConverter) -> None: - """Place multiple flags on the board""" + """Place multiple flags on the board.""" board: GameBoard = self.games[ctx.author.id].revealed for x, y in coordinates: if board[y][x] == "hidden": @@ -185,14 +185,14 @@ class Minesweeper(commands.Cog): @staticmethod def reveal_bombs(revealed: GameBoard, board: GameBoard) -> None: - """Reveals all the bombs""" + """Reveals all the bombs.""" for y, row in enumerate(board): for x, cell in enumerate(row): if cell == "bomb": revealed[y][x] = cell async def lost(self, ctx: commands.Context) -> None: - """The player lost the game""" + """The player lost the game.""" game = self.games[ctx.author.id] self.reveal_bombs(game.revealed, game.board) await ctx.author.send(":fire: You lost! :fire:") @@ -200,7 +200,7 @@ class Minesweeper(commands.Cog): await game.chat_msg.channel.send(f":fire: {ctx.author.mention} just lost Minesweeper! :fire:") async def won(self, ctx: commands.Context) -> None: - """The player won the game""" + """The player won the game.""" game = self.games[ctx.author.id] await ctx.author.send(":tada: You won! :tada:") if game.activated_on_server: @@ -252,7 +252,7 @@ class Minesweeper(commands.Cog): @commands.dm_only() @minesweeper_group.command(name="reveal") async def reveal_command(self, ctx: commands.Context, *coordinates: CoordinateConverter) -> None: - """Reveal multiple cells""" + """Reveal multiple cells.""" game = self.games[ctx.author.id] revealed: GameBoard = game.revealed board: GameBoard = game.board diff --git a/bot/seasons/evergreen/showprojects.py b/bot/seasons/evergreen/showprojects.py index 2804bdbe..d41132aa 100644 --- a/bot/seasons/evergreen/showprojects.py +++ b/bot/seasons/evergreen/showprojects.py @@ -9,7 +9,7 @@ log = logging.getLogger(__name__) class ShowProjects(commands.Cog): - """Cog that reacts to posts in the #show-your-projects""" + """Cog that reacts to posts in the #show-your-projects.""" def __init__(self, bot: commands.Bot): self.bot = bot diff --git a/bot/seasons/evergreen/snakes/utils.py b/bot/seasons/evergreen/snakes/utils.py index 24e71227..76809bd4 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 Cog, 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 (self._is_moderator(user) and user not in self.players): + # 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) -> None: @@ -492,16 +494,12 @@ class SnakeAndLaddersGame: """ 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 wasn'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,11 +510,10 @@ 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) -> None: """Allow the game author to cancel the running game.""" @@ -530,21 +527,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 +557,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 +602,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 +614,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 user not in self.players: + # 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 +628,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) -> None: """Handle the player's roll.""" @@ -708,3 +703,8 @@ class SnakeAndLaddersGame: if is_reversed: x_level = 9 - x_level return x_level, y_level + + @staticmethod + def _is_moderator(user: Member) -> bool: + """Return True if the user is a Moderator.""" + return any(Roles.moderator == role.id for role in user.roles) |