aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--bot/seasons/evergreen/minesweeper.py93
1 files changed, 59 insertions, 34 deletions
diff --git a/bot/seasons/evergreen/minesweeper.py b/bot/seasons/evergreen/minesweeper.py
index 19486033..d43233d4 100644
--- a/bot/seasons/evergreen/minesweeper.py
+++ b/bot/seasons/evergreen/minesweeper.py
@@ -36,14 +36,13 @@ class CoordinateConverter(commands.Converter):
if not 2 <= len(coordinate) <= 3:
raise commands.ArgumentParsingError()
- value1 = coordinate[0]
- value2 = coordinate[1:]
+ digit, letter = sorted(coordinate.lower())
- if not value2.isdigit():
+ if not letter.isdigit():
raise commands.ArgumentParsingError()
- x = ord(value1) - 97
- y = int(value2) - 1
+ x = ord(letter.lower()) - 97
+ y = int(digit) - 1
if (not 0 <= x <= 9) or (not 0 <= y <= 9):
raise commands.ArgumentParsingError()
@@ -61,6 +60,7 @@ class Game:
revealed: GameBoard
dm_msg: discord.message
chat_msg: discord.message
+ activated_on_server: bool
GamesDict = typing.Dict[int, Game]
@@ -72,8 +72,13 @@ class Minesweeper(commands.Cog):
def __init__(self, bot: commands.Bot) -> None:
self.games: GamesDict = {} # Store the currently running games
+ @commands.group(name='minesweeper', aliases=('ms',), invoke_without_command=True)
+ async def minesweeper_group(self, ctx: commands.Context):
+ """Commands for Playing Minesweeper"""
+ await ctx.send_help(ctx.command)
+
@staticmethod
- def get_neighbours(x: int, y: int) -> typing.Generator:
+ def get_neighbours(x: int, y: int) -> typing.Generator[typing.Tuple[int, int], None, None]:
"""Get all the neighbouring x and y including it self."""
for x_ in [x - 1, x, x + 1]:
for y_ in [y - 1, y, y + 1]:
@@ -82,7 +87,12 @@ class Minesweeper(commands.Cog):
def generate_board(self, bomb_chance: float) -> GameBoard:
"""Generate a 2d array for the board."""
- board: GameBoard = [["bomb" if random() <= bomb_chance else "number" for _ in range(10)] for _ in range(10)]
+ board: GameBoard = [
+ [
+ "bomb" if random() <= bomb_chance else "number"
+ for _ in range(10)
+ ] for _ in range(10)
+ ]
for y, row in enumerate(board):
for x, cell in enumerate(row):
if cell == "number":
@@ -102,22 +112,20 @@ class Minesweeper(commands.Cog):
":regional_indicator_d::regional_indicator_e::regional_indicator_f::regional_indicator_g:"
":regional_indicator_h::regional_indicator_i::regional_indicator_j:\n\n"
)
- rows: typing.List[str] = []
+ rows = []
for row_number, row in enumerate(board):
- new_row = MESSAGE_MAPPING[row_number + 1] + " "
- for cell in row:
- new_row += MESSAGE_MAPPING[cell]
+ new_row = f"{MESSAGE_MAPPING[row_number + 1]} "
+ new_row += "".join(MESSAGE_MAPPING[cell] for cell in row)
rows.append(new_row)
discord_msg += "\n".join(rows)
return discord_msg
- @commands.command(name="minesweeper")
- async def minesweeper_command(self, ctx: commands.Context, bomb_chance: float = .2) -> None:
+ @minesweeper_group.command(name="start")
+ async def start_command(self, ctx: commands.Context, bomb_chance: float = .2) -> None:
"""Start a game of Minesweeper."""
if ctx.author.id in self.games: # Player is already playing
- msg = await ctx.send(f"{ctx.author.mention} you already have a game running!")
- await msg.delete(delay=2)
+ await ctx.send(f"{ctx.author.mention} you already have a game running!")
await ctx.message.delete(delay=2)
return
@@ -125,13 +133,15 @@ class Minesweeper(commands.Cog):
board: GameBoard = self.generate_board(bomb_chance)
revealed_board: GameBoard = [["hidden"] * 10 for _ in range(10)]
- await ctx.send(f"{ctx.author.mention} is playing Minesweeper")
- chat_msg = await ctx.send(self.format_for_discord(revealed_board))
+ if ctx.guild:
+ await ctx.send(f"{ctx.author.mention} is playing Minesweeper")
+ chat_msg = await ctx.send(self.format_for_discord(revealed_board))
+ else:
+ chat_msg = None
await ctx.author.send(
f"Play by typing: `{Client.prefix}reveal xy [xy]` or `{Client.prefix}flag xy [xy]` \n"
f"Close the game with `{Client.prefix}end`\n"
- "Coordinates must be in format `<letter><number>`"
)
dm_msg = await ctx.author.send(self.format_for_discord(revealed_board))
@@ -139,7 +149,8 @@ class Minesweeper(commands.Cog):
board=board,
revealed=revealed_board,
dm_msg=dm_msg,
- chat_msg=chat_msg
+ chat_msg=chat_msg,
+ activated_on_server=ctx.guild is not None
)
async def update_boards(self, ctx: commands.Context) -> None:
@@ -147,10 +158,11 @@ class Minesweeper(commands.Cog):
game = self.games[ctx.author.id]
await game.dm_msg.delete()
game.dm_msg = await ctx.author.send(self.format_for_discord(game.revealed))
- await game.chat_msg.edit(content=self.format_for_discord(game.revealed))
+ if game.activated_on_server:
+ await game.chat_msg.edit(content=self.format_for_discord(game.revealed))
@commands.dm_only()
- @commands.command(name="flag")
+ @minesweeper_group.command(name="flag")
async def flag_command(self, ctx: commands.Context, *coordinates: CoordinateConverter) -> None:
"""Place multiple flags on the board"""
board: GameBoard = self.games[ctx.author.id].revealed
@@ -165,14 +177,16 @@ class Minesweeper(commands.Cog):
game = self.games[ctx.author.id]
game.revealed = game.board
await ctx.author.send(":fire: You lost! :fire:")
- await game.chat_msg.channel.send(f":fire: {ctx.author.mention} just lost Minesweeper! :fire:")
+ if game.activated_on_server:
+ 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"""
game = self.games[ctx.author.id]
game.revealed = game.board
await ctx.author.send(":tada: You won! :tada:")
- await game.chat_msg.channel.send(f":tada: {ctx.author.mention} just won Minesweeper! :tada:")
+ if game.activated_on_server:
+ await game.chat_msg.channel.send(f":tada: {ctx.author.mention} just won Minesweeper! :tada:")
def reveal_zeros(self, revealed: GameBoard, board: GameBoard, x: int, y: int) -> None:
"""Recursively reveal adjacent cells when a 0 cell is encountered."""
@@ -185,15 +199,24 @@ class Minesweeper(commands.Cog):
async def check_if_won(self, ctx, revealed: GameBoard, board: GameBoard) -> bool:
"""Checks if a player has won"""
- for x in range(10):
- for y in range(10):
- if revealed[y][x] == "hidden":
- return False
+ if any(
+ revealed[y][x] in ["hidden", "flag"] and board[y][x] != "bomb"
+ for x in range(10)
+ for y in range(10)
+ ):
+ return False
else:
await self.won(ctx)
return True
- async def reveal_one(self, ctx: commands.Context, revealed: GameBoard, board: GameBoard, x: int, y: int) -> bool:
+ async def reveal_one(
+ self,
+ ctx: commands.Context,
+ revealed: GameBoard,
+ board: GameBoard,
+ x: int,
+ y: int
+ ) -> bool:
"""
Reveal one square.
@@ -202,13 +225,13 @@ class Minesweeper(commands.Cog):
revealed[y][x] = board[y][x]
if board[y][x] == "bomb":
await self.lost(ctx)
- return True # game ended
+ return True
elif board[y][x] == 0:
self.reveal_zeros(revealed, board, x, y)
return await self.check_if_won(ctx, revealed, board)
@commands.dm_only()
- @commands.command(name="reveal")
+ @minesweeper_group.command(name="reveal")
async def reveal_command(self, ctx: commands.Context, *coordinates: CoordinateConverter) -> None:
"""Reveal multiple cells"""
game = self.games[ctx.author.id]
@@ -216,22 +239,24 @@ class Minesweeper(commands.Cog):
board: GameBoard = game.board
for x, y in coordinates:
- if await self.reveal_one(ctx, revealed, board, x, y): # game ended
+ # reveal_one returns True if the revealed cell is a bomb or the player won., ending the game
+ if await self.reveal_one(ctx, revealed, board, x, y):
await self.update_boards(ctx)
del self.games[ctx.author.id]
break
else:
await self.update_boards(ctx)
- @commands.command(name="end")
+ @minesweeper_group.command(name="end")
async def end_command(self, ctx: commands.Context):
- """End the current game"""
+ """End your current game"""
game = self.games[ctx.author.id]
game.revealed = game.board
await self.update_boards(ctx)
new_msg = f":no_entry: Game canceled :no_entry:\n{game.dm_msg.content}"
await game.dm_msg.edit(content=new_msg)
- await game.chat_msg.edit(content=new_msg)
+ if game.activated_on_server:
+ await game.chat_msg.edit(content=new_msg)
del self.games[ctx.author.id]