From 3616302122f72c19b6ab4877fdb6a5236967110d Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Tue, 19 Jan 2021 06:37:09 +0530 Subject: Add connect four cog supporting player vs player and player vs ai --- bot/exts/evergreen/connect_four.py | 377 +++++++++++++++++++++++++++++++++++++ 1 file changed, 377 insertions(+) create mode 100644 bot/exts/evergreen/connect_four.py (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py new file mode 100644 index 00000000..9519f124 --- /dev/null +++ b/bot/exts/evergreen/connect_four.py @@ -0,0 +1,377 @@ +import asyncio +import random +import typing +from functools import partial + +import discord +from discord.ext import commands + +EMOJIS = [":white_circle:", ":blue_circle:", ":red_circle:"] +NUMBERS = [ + ":one:", + ":two:", + ":three:", + ":four:", + ":five:", + ":six:", + ":seven:", + ":eight:", + ":nine:" +] +UNICODE_NUMBERS = [ + "\u0031\u20e3", + "\u0032\u20e3", + "\u0033\u20e3", + "\u0034\u20e3", + "\u0035\u20e3", + "\u0036\u20e3", + "\u0037\u20e3", + "\u0038\u20e3", + "\u0039\u20e3", +] +CROSS_EMOJI = "\u274e" +HAND_RAISED_EMOJI = "\U0001f64b" +Coordinate = typing.Optional[typing.Tuple[int, int]] + + +class Game: + """A Connect 4 Game.""" + + def __init__( + self, + bot: commands.Bot, + channel: discord.TextChannel, + player1: discord.Member, + player2: discord.Member = None, + size: int = 7, + ) -> None: + + self.bot = bot + self.channel = channel + self.player1 = player1 + self.player2 = player2 or AI(game=self) + + self.grid = self.generate_board(size) + self.grid_size = size + + self.unicode_numbers = UNICODE_NUMBERS[:self.grid_size] + + self.message = None + + self.turn = None + self.next = None + + @staticmethod + def generate_board(size: int) -> typing.List[typing.List[int]]: + """Generate the connect 4 board.""" + return [[0 for _ in range(size)] for _ in range(size)] + + async def print_grid(self) -> None: + """Formats and outputs the Connect Four grid to the channel.""" + rows = [" ".join(EMOJIS[s] for s in row) for row in self.grid] + first_row = " ".join(x for x in NUMBERS[:self.grid_size]) + formatted_grid = "\n".join([first_row] + rows) + embed = discord.Embed(title="Connect Four Board", description=formatted_grid) + + if self.message: + await self.message.edit(embed=embed) + else: + self.message = await self.channel.send(embed=embed) + for emoji in self.unicode_numbers: + await self.message.add_reaction(emoji) + + async def start_game(self) -> None: + """Begins the game.""" + self.turn, self.next = self.player1, self.player2 + + while True: + await self.print_grid() + if isinstance(self.turn, AI): + coords = self.turn.play() + else: + coords = await self.player_turn() + + if not coords: + return + + if self.check_win(coords, 1 if self.turn == self.player1 else 2): + if isinstance(self.turn, AI): + await self.channel.send(f"Game Over! {self.turn.mention} lost against AI") + else: + if isinstance(self.next, AI): + await self.channel.send(f"Game Over! {self.turn.mention} won against AI") + else: + await self.channel.send(f"Game Over! {self.turn.mention} won against {self.next.mention}") + await self.print_grid() + return + + self.turn, self.next = self.next, self.turn + + def predicate(self, reaction: discord.Reaction, user: discord.Member) -> bool: + """The predicate to check for the player's reaction.""" + return ( + reaction.message.id == self.message.id + and user.id == self.turn.id + and str(reaction.emoji) in self.unicode_numbers + ) + + async def player_turn(self) -> Coordinate: + """Initiate the player's turn.""" + message = await self.channel.send( + f"{self.turn.mention}, it's your turn! React with a column you want to place your token" + ) + player_num = 1 if self.turn == self.player1 else 2 + while True: + full_column = False + try: + reaction, user = await self.bot.wait_for("reaction_add", check=self.predicate, timeout=30.0) + except asyncio.TimeoutError: + await self.channel.send(f"{self.turn.mention}, you took too long. Game over!") + return + else: + await message.delete() + await self.message.remove_reaction(reaction, user) + column_num = self.unicode_numbers.index(str(reaction.emoji)) + + column = [row[column_num] for row in self.grid] + + for row_num, square in reversed(list(enumerate(column))): + if not square: + self.grid[row_num][column_num] = player_num + coords = row_num, column_num + break + else: + await self.channel.send(f"Column {column_num + 1} is full. Try again") + full_column = True + if not full_column: + break + return coords + + def check_win(self, coords: Coordinate, player_num: int) -> bool: + """Check that placing a counter here would cause the player to win.""" + vertical = [(-1, 0), (1, 0)] + horizontal = [(0, 1), (0, -1)] + forward_diag = [(-1, 1), (1, -1)] + backward_diag = [(-1, -1), (1, 1)] + axes = [vertical, horizontal, forward_diag, backward_diag] + + for axis in axes: + in_a_row = 1 # The initial counter that is compared to + for (row_incr, column_incr) in axis: + row, column = coords + row += row_incr + column += column_incr + + while 0 <= row < self.grid_size and 0 <= column < self.grid_size: + if self.grid[row][column] == player_num: + in_a_row += 1 + row += row_incr + column += column_incr + else: + break + if in_a_row >= 4: + return True + return False + + +class AI: + """The Computer Player for Single-Player games.""" + + def __init__(self, game: Game) -> None: + self.game = game + + def get_possible_places(self) -> typing.List[Coordinate]: + """Gets all the coordinates where the AI could possibly place a counter.""" + possible_coords = [] + for column_num in range(self.game.grid_size): + column = [row[column_num] for row in self.game.grid] + for row_num, square in reversed(list(enumerate(column))): + if not square: + possible_coords.append((row_num, column_num)) + break + return possible_coords + + def check_ai_win(self, coord_list: typing.List[Coordinate]) -> typing.Optional[Coordinate]: + """Check if placing a counter in any possible coordinate would cause the AI to win.""" + if random.randint(1, 10) == 1: # 10% chance of not winning + return + for coords in coord_list: + if self.game.check_win(coords, 2): + return coords + + def check_player_win(self, coord_list: typing.List[Coordinate]) -> typing.Optional[Coordinate]: + """Check if placing a counter in any possible coordinate would stop the player from winning.""" + if random.randint(1, 4) == 1: # 25% chance of not blocking the player + return + for coords in coord_list: + if self.game.check_win(coords, 1): + return coords + + @staticmethod + def random_coords(coord_list: typing.List[Coordinate]) -> Coordinate: + """Picks a random coordinate from the possible ones.""" + return random.choice(coord_list) + + def play(self) -> Coordinate: + """The AI's turn.""" + possible_coords = self.get_possible_places() + + coords = self.check_ai_win(possible_coords) # Win + if not coords: + coords = self.check_player_win(possible_coords) # Try to stop P1 from winning + if not coords: + coords = self.random_coords(possible_coords) + + row, column = coords + self.game.grid[row][column] = 2 + return coords + + +class ConnectFour(commands.Cog): + """Connect Four. The Classic Vertical Four-in-a-row Game!""" + + def __init__(self, bot: commands.Bot) -> None: + self.bot = bot + self.games: typing.List[Game] = [] + self.waiting: typing.List[discord.Member] = [] + + self.max_board_size = 9 + self.min_board_size = 5 + + def get_player( + self, + ctx: commands.Context, + announcement: discord.Message, + reaction: discord.Reaction, + user: discord.Member + ) -> bool: + """Predicate checking the criteria for the announcement message.""" + if self.already_playing(ctx.author): # If they've joined a game since requesting a player 2 + return True # Is dealt with later on + if ( + user.id not in (ctx.me.id, ctx.author.id) + and str(reaction.emoji) == HAND_RAISED_EMOJI + and reaction.message.id == announcement.id + ): + if self.already_playing(user): + self.bot.loop.create_task(ctx.send(f"{user.mention} You're already playing a game!")) + self.bot.loop.create_task(announcement.remove_reaction(reaction, user)) + return False + + if user in self.waiting: + self.bot.loop.create_task(ctx.send( + f"{user.mention} Please cancel your game first before joining another one." + )) + self.bot.loop.create_task(announcement.remove_reaction(reaction, user)) + return False + + return True + + if ( + user.id == ctx.author.id + and str(reaction.emoji) == CROSS_EMOJI + and reaction.message.id == announcement.id + ): + return True + return False + + def already_playing(self, player: discord.Member) -> bool: + """Check if someone is already in a game.""" + return any(player in (game.player1, game.player2) for game in self.games) + + async def _play_game(self, ctx: commands.Context, user: typing.Optional[discord.Member], board_size: int) -> None: + """Helper for playing a game of connect four.""" + try: + game = Game(self.bot, ctx.channel, ctx.author, user, size=board_size) + self.games.append(game) + await game.start_game() + self.games.remove(game) + except Exception: + # End the game in the event of an unforseen error so the players aren't stuck in a game + await ctx.send(f"{ctx.author.mention} {user.mention if user else ''} An error occurred. Game failed") + self.games.remove(game) + raise + + @commands.group(invoke_without_command=True, aliases=[ + "4inarow", "4-in-a-row", "4_in_a_row", "connect4", "connect-four", "connect_four" + ]) + @commands.guild_only() + async def connectfour(self, ctx: commands.Context, board_size: int = 7) -> None: + """ + Play the classic game of Connect Four with someone! + + Sets up a message waiting for someone else to react and play along. + The game will start once someone has reacted. + All inputs will be through reactions. + """ + if self.already_playing(ctx.author): + await ctx.send("You're already playing a game!") + return + + if ctx.author in self.waiting: + await ctx.send("You've already sent out a request for a player 2") + return + + if board_size > self.max_board_size or board_size < self.min_board_size: + await ctx.send(f"{board_size} is not a valid board size. A valid board size it " + f"between `{self.min_board_size}` to `{self.max_board_size}`") + return + + announcement = await ctx.send( + "**Connect Four**: A new game is about to start!\n" + f"Press {HAND_RAISED_EMOJI} to play against {ctx.author.mention}!\n" + f"(Cancel the game with {CROSS_EMOJI}.)" + ) + self.waiting.append(ctx.author) + await announcement.add_reaction(HAND_RAISED_EMOJI) + await announcement.add_reaction(CROSS_EMOJI) + + try: + reaction, user = await self.bot.wait_for( + "reaction_add", + check=partial(self.get_player, ctx, announcement), + timeout=60.0 + ) + except asyncio.TimeoutError: + self.waiting.remove(ctx.author) + await announcement.delete() + await ctx.send(f"{ctx.author.mention} Seems like there's no one here to play" + f"Use `{ctx.prefix}{ctx.invoked_with} ai` to play against a computer." + ) + return + + if str(reaction.emoji) == CROSS_EMOJI: + self.waiting.remove(ctx.author) + await announcement.delete() + await ctx.send(f"{ctx.author.mention} Game cancelled.") + return + + await announcement.delete() + self.waiting.remove(ctx.author) + if self.already_playing(ctx.author): + return + + await self._play_game(ctx, user, board_size) + + @connectfour.command(aliases=["AI", "CPU", "computer", "cpu", "Computer"]) + async def ai(self, ctx: commands.Context, board_size: int = 7) -> None: + """Play Connect Four against a computer player.""" + if self.already_playing(ctx.author): + await ctx.send("You're already playing a game!") + return + + if ctx.author in self.waiting: + await ctx.send("You've already sent out a request for a player 2") + return + + if board_size > self.max_board_size or board_size < self.min_board_size: + await ctx.send(f"{board_size} is not a valid board size. A valid board size it " + f"between `{self.min_board_size}` to `{self.max_board_size}`") + return + + await self._play_game(ctx, user=None, board_size=board_size) + + +def setup(bot: commands.Bot) -> None: + """Load ConnectFour Cog.""" + bot.add_cog(ConnectFour(bot)) -- cgit v1.2.3 From f44a39ddc14ee23dd9394deac4a90cf0300ebbab Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Wed, 20 Jan 2021 05:28:10 +0530 Subject: fix grammar and spacing --- bot/exts/evergreen/connect_four.py | 62 ++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 30 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 9519f124..2592bf3f 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -58,8 +58,8 @@ class Game: self.message = None - self.turn = None - self.next = None + self.player_active = None + self.player_inactive = None @staticmethod def generate_board(size: int) -> typing.List[typing.List[int]]: @@ -82,51 +82,51 @@ class Game: async def start_game(self) -> None: """Begins the game.""" - self.turn, self.next = self.player1, self.player2 + self.player_active, self.player_inactive = self.player1, self.player2 while True: await self.print_grid() - if isinstance(self.turn, AI): - coords = self.turn.play() + if isinstance(self.player_active, AI): + coords = self.player_active.play() else: coords = await self.player_turn() if not coords: return - if self.check_win(coords, 1 if self.turn == self.player1 else 2): - if isinstance(self.turn, AI): - await self.channel.send(f"Game Over! {self.turn.mention} lost against AI") + if self.check_win(coords, 1 if self.player_active == self.player1 else 2): + if isinstance(self.player_active, AI): + await self.channel.send(f"Game Over! {self.player_active.mention} lost against AI") else: - if isinstance(self.next, AI): - await self.channel.send(f"Game Over! {self.turn.mention} won against AI") + if isinstance(self.player_inactive, AI): + await self.channel.send(f"Game Over! {self.player_active.mention} won against AI") else: - await self.channel.send(f"Game Over! {self.turn.mention} won against {self.next.mention}") + await self.channel.send(f"Game Over! {self.player_active.mention} won against {self.player_inactive.mention}") await self.print_grid() return - self.turn, self.next = self.next, self.turn + self.player_active, self.player_inactive = self.player_inactive, self.player_active def predicate(self, reaction: discord.Reaction, user: discord.Member) -> bool: """The predicate to check for the player's reaction.""" return ( reaction.message.id == self.message.id - and user.id == self.turn.id + and user.id == self.player_active.id and str(reaction.emoji) in self.unicode_numbers ) async def player_turn(self) -> Coordinate: """Initiate the player's turn.""" message = await self.channel.send( - f"{self.turn.mention}, it's your turn! React with a column you want to place your token" + f"{self.turn.mention}, it's your turn! React with the column you want to place your token in." ) - player_num = 1 if self.turn == self.player1 else 2 + player_num = 1 if self.player_active == self.player1 else 2 while True: full_column = False try: reaction, user = await self.bot.wait_for("reaction_add", check=self.predicate, timeout=30.0) except asyncio.TimeoutError: - await self.channel.send(f"{self.turn.mention}, you took too long. Game over!") + await self.channel.send(f"{self.player_active.mention}, you took too long. Game over!") return else: await message.delete() @@ -156,7 +156,7 @@ class Game: axes = [vertical, horizontal, forward_diag, backward_diag] for axis in axes: - in_a_row = 1 # The initial counter that is compared to + counters_in_a_row = 1 # The initial counter that is compared to for (row_incr, column_incr) in axis: row, column = coords row += row_incr @@ -164,12 +164,12 @@ class Game: while 0 <= row < self.grid_size and 0 <= column < self.grid_size: if self.grid[row][column] == player_num: - in_a_row += 1 + counters_in_a_row += 1 row += row_incr column += column_incr else: break - if in_a_row >= 4: + if counters_in_a_row >= 4: return True return False @@ -287,16 +287,17 @@ class ConnectFour(commands.Cog): await game.start_game() self.games.remove(game) except Exception: - # End the game in the event of an unforseen error so the players aren't stuck in a game + # End the game in the event of an unforeseen error so the players aren't stuck in a game await ctx.send(f"{ctx.author.mention} {user.mention if user else ''} An error occurred. Game failed") self.games.remove(game) raise - @commands.group(invoke_without_command=True, aliases=[ - "4inarow", "4-in-a-row", "4_in_a_row", "connect4", "connect-four", "connect_four" - ]) + @commands.group( + invoke_without_command=True, + aliases=["4inarow", "connect4", "connectfour", "c4"] + ) @commands.guild_only() - async def connectfour(self, ctx: commands.Context, board_size: int = 7) -> None: + async def connect_four(self, ctx: commands.Context, board_size: int = 7) -> None: """ Play the classic game of Connect Four with someone! @@ -313,8 +314,8 @@ class ConnectFour(commands.Cog): return if board_size > self.max_board_size or board_size < self.min_board_size: - await ctx.send(f"{board_size} is not a valid board size. A valid board size it " - f"between `{self.min_board_size}` to `{self.max_board_size}`") + await ctx.send(f"{board_size} is not a valid board size. A valid board size is " + f"between `{self.min_board_size}` and `{self.max_board_size}`.") return announcement = await ctx.send( @@ -335,9 +336,10 @@ class ConnectFour(commands.Cog): except asyncio.TimeoutError: self.waiting.remove(ctx.author) await announcement.delete() - await ctx.send(f"{ctx.author.mention} Seems like there's no one here to play" - f"Use `{ctx.prefix}{ctx.invoked_with} ai` to play against a computer." - ) + await ctx.send( + f"{ctx.author.mention} Seems like there's no one here to play. " + f"Use `{ctx.prefix}{ctx.invoked_with} ai` to play against a computer." + ) return if str(reaction.emoji) == CROSS_EMOJI: @@ -353,7 +355,7 @@ class ConnectFour(commands.Cog): await self._play_game(ctx, user, board_size) - @connectfour.command(aliases=["AI", "CPU", "computer", "cpu", "Computer"]) + @connectfour.command(aliases=["bot", "computer", "cpu"]) async def ai(self, ctx: commands.Context, board_size: int = 7) -> None: """Play Connect Four against a computer player.""" if self.already_playing(ctx.author): -- cgit v1.2.3 From 24e7f9508b61675d17f8fabf58f2fef4e7f97084 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Wed, 20 Jan 2021 05:45:26 +0530 Subject: COrrect annotations and improve docstrings ; make code more pythonic --- bot/exts/evergreen/connect_four.py | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 2592bf3f..1d2c82a2 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -42,7 +42,7 @@ class Game: bot: commands.Bot, channel: discord.TextChannel, player1: discord.Member, - player2: discord.Member = None, + player2: typing.Optional[discord.Member], size: int = 7, ) -> None: @@ -64,7 +64,7 @@ class Game: @staticmethod def generate_board(size: int) -> typing.List[typing.List[int]]: """Generate the connect 4 board.""" - return [[0 for _ in range(size)] for _ in range(size)] + return [[0]*size]*size async def print_grid(self) -> None: """Formats and outputs the Connect Four grid to the channel.""" @@ -101,7 +101,9 @@ class Game: if isinstance(self.player_inactive, AI): await self.channel.send(f"Game Over! {self.player_active.mention} won against AI") else: - await self.channel.send(f"Game Over! {self.player_active.mention} won against {self.player_inactive.mention}") + await self.channel.send( + f"Game Over! {self.player_active.mention} won against {self.player_inactive.mention}" + ) await self.print_grid() return @@ -213,14 +215,22 @@ class AI: return random.choice(coord_list) def play(self) -> Coordinate: - """The AI's turn.""" + """ + Plays for the AI. + + Gets all possible coords, and determins the move: + 1. coords where it can win. + 2. coords where the player can win. + 3. Random coord + The first possible value is choosen. + """ possible_coords = self.get_possible_places() - coords = self.check_ai_win(possible_coords) # Win - if not coords: - coords = self.check_player_win(possible_coords) # Try to stop P1 from winning - if not coords: - coords = self.random_coords(possible_coords) + coords = ( + self.check_ai_win(possible_coords) + or self.check_player_win(possible_coords) + or self.random_coords(possible_coords) + ) row, column = coords self.game.grid[row][column] = 2 @@ -296,7 +306,6 @@ class ConnectFour(commands.Cog): invoke_without_command=True, aliases=["4inarow", "connect4", "connectfour", "c4"] ) - @commands.guild_only() async def connect_four(self, ctx: commands.Context, board_size: int = 7) -> None: """ Play the classic game of Connect Four with someone! @@ -313,7 +322,7 @@ class ConnectFour(commands.Cog): await ctx.send("You've already sent out a request for a player 2") return - if board_size > self.max_board_size or board_size < self.min_board_size: + if not self.min_board_size <= board_size <= self.max_board_size: await ctx.send(f"{board_size} is not a valid board size. A valid board size is " f"between `{self.min_board_size}` and `{self.max_board_size}`.") return @@ -355,7 +364,7 @@ class ConnectFour(commands.Cog): await self._play_game(ctx, user, board_size) - @connectfour.command(aliases=["bot", "computer", "cpu"]) + @connect_four.command(aliases=["bot", "computer", "cpu"]) async def ai(self, ctx: commands.Context, board_size: int = 7) -> None: """Play Connect Four against a computer player.""" if self.already_playing(ctx.author): -- cgit v1.2.3 From d1acfa3ecc37b63b21a1889d38ae1405b917238f Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Wed, 20 Jan 2021 07:02:08 +0530 Subject: Change Ai to bot's user and add stop game functionality, remove redundant code, and DRY --- bot/exts/evergreen/connect_four.py | 68 ++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 29 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 1d2c82a2..1e5d6fac 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -49,7 +49,7 @@ class Game: self.bot = bot self.channel = channel self.player1 = player1 - self.player2 = player2 or AI(game=self) + self.player2 = player2 or AI(self.bot, game=self) self.grid = self.generate_board(size) self.grid_size = size @@ -64,7 +64,7 @@ class Game: @staticmethod def generate_board(size: int) -> typing.List[typing.List[int]]: """Generate the connect 4 board.""" - return [[0]*size]*size + return [[0 for _ in range(size)] for _ in range(size)] async def print_grid(self) -> None: """Formats and outputs the Connect Four grid to the channel.""" @@ -79,6 +79,7 @@ class Game: self.message = await self.channel.send(embed=embed) for emoji in self.unicode_numbers: await self.message.add_reaction(emoji) + await self.message.add_reaction(CROSS_EMOJI) async def start_game(self) -> None: """Begins the game.""" @@ -96,10 +97,12 @@ class Game: if self.check_win(coords, 1 if self.player_active == self.player1 else 2): if isinstance(self.player_active, AI): - await self.channel.send(f"Game Over! {self.player_active.mention} lost against AI") + await self.channel.send(f"Game Over! {self.player_inactive.mention} lost against" + f" {self.bot.user.mention}") else: if isinstance(self.player_inactive, AI): - await self.channel.send(f"Game Over! {self.player_active.mention} won against AI") + await self.channel.send(f"Game Over! {self.player_active.mention} won against" + f" {self.bot.user.mention}") else: await self.channel.send( f"Game Over! {self.player_active.mention} won against {self.player_inactive.mention}" @@ -114,13 +117,13 @@ class Game: return ( reaction.message.id == self.message.id and user.id == self.player_active.id - and str(reaction.emoji) in self.unicode_numbers + and str(reaction.emoji) in (*self.unicode_numbers, CROSS_EMOJI) ) async def player_turn(self) -> Coordinate: """Initiate the player's turn.""" message = await self.channel.send( - f"{self.turn.mention}, it's your turn! React with the column you want to place your token in." + f"{self.player_active.mention}, it's your turn! React with the column you want to place your token in." ) player_num = 1 if self.player_active == self.player1 else 2 while True: @@ -131,6 +134,13 @@ class Game: await self.channel.send(f"{self.player_active.mention}, you took too long. Game over!") return else: + if str(reaction.emoji) == CROSS_EMOJI: + await message.delete() + await self.channel.send( + f"{user.mention} has abandoned the game :(" + ) + return + await message.delete() await self.message.remove_reaction(reaction, user) column_num = self.unicode_numbers.index(str(reaction.emoji)) @@ -179,8 +189,9 @@ class Game: class AI: """The Computer Player for Single-Player games.""" - def __init__(self, game: Game) -> None: + def __init__(self, bot: commands.Bot, game: Game) -> None: self.game = game + self.mention = bot.user.mention def get_possible_places(self) -> typing.List[Coordinate]: """Gets all the coordinates where the AI could possibly place a counter.""" @@ -248,6 +259,23 @@ class ConnectFour(commands.Cog): self.max_board_size = 9 self.min_board_size = 5 + async def check_author(self, ctx: commands.Context, board_size: int) -> bool: + """Check if the requester is free and the board size is correct.""" + if self.already_playing(ctx.author): + await ctx.send("You're already playing a game!") + return False + + if ctx.author in self.waiting: + await ctx.send("You've already sent out a request for a player 2") + return False + + if not self.min_board_size <= board_size <= self.max_board_size: + await ctx.send(f"{board_size} is not a valid board size. A valid board size is " + f"between `{self.min_board_size}` and `{self.max_board_size}`.") + return False + + return True + def get_player( self, ctx: commands.Context, @@ -314,17 +342,8 @@ class ConnectFour(commands.Cog): The game will start once someone has reacted. All inputs will be through reactions. """ - if self.already_playing(ctx.author): - await ctx.send("You're already playing a game!") - return - - if ctx.author in self.waiting: - await ctx.send("You've already sent out a request for a player 2") - return - - if not self.min_board_size <= board_size <= self.max_board_size: - await ctx.send(f"{board_size} is not a valid board size. A valid board size is " - f"between `{self.min_board_size}` and `{self.max_board_size}`.") + check_author_result = await self.check_author(ctx, board_size) + if not check_author_result: return announcement = await ctx.send( @@ -367,17 +386,8 @@ class ConnectFour(commands.Cog): @connect_four.command(aliases=["bot", "computer", "cpu"]) async def ai(self, ctx: commands.Context, board_size: int = 7) -> None: """Play Connect Four against a computer player.""" - if self.already_playing(ctx.author): - await ctx.send("You're already playing a game!") - return - - if ctx.author in self.waiting: - await ctx.send("You've already sent out a request for a player 2") - return - - if board_size > self.max_board_size or board_size < self.min_board_size: - await ctx.send(f"{board_size} is not a valid board size. A valid board size it " - f"between `{self.min_board_size}` to `{self.max_board_size}`") + check_author_result = await self.check_author(ctx, board_size) + if not check_author_result: return await self._play_game(ctx, user=None, board_size=board_size) -- cgit v1.2.3 From 46e594c390cce07f522329b5fc012b886bcdbb81 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Thu, 21 Jan 2021 07:40:53 +0530 Subject: Add loading message before finishing all reactions and improve embed title --- bot/exts/evergreen/connect_four.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 1e5d6fac..a4f29172 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -68,18 +68,24 @@ class Game: async def print_grid(self) -> None: """Formats and outputs the Connect Four grid to the channel.""" + title = ( + f'Connect 4: {self.player1.display_name}' + f'VS {self.player2.display_name}' + ) + rows = [" ".join(EMOJIS[s] for s in row) for row in self.grid] first_row = " ".join(x for x in NUMBERS[:self.grid_size]) formatted_grid = "\n".join([first_row] + rows) - embed = discord.Embed(title="Connect Four Board", description=formatted_grid) + embed = discord.Embed(title=title, description=formatted_grid) if self.message: await self.message.edit(embed=embed) else: - self.message = await self.channel.send(embed=embed) + self.message = await self.channel.send(content='Loading ....') for emoji in self.unicode_numbers: await self.message.add_reaction(emoji) await self.message.add_reaction(CROSS_EMOJI) + await self.message.edit(content=None, embed=embed) async def start_game(self) -> None: """Begins the game.""" -- cgit v1.2.3 From 089baf161da449995b475971c5e0689a5b24c7e8 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Thu, 21 Jan 2021 09:59:11 +0530 Subject: Improve embeds and docstrings --- bot/exts/evergreen/connect_four.py | 58 +++++++++++++++++++++++--------------- 1 file changed, 35 insertions(+), 23 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index a4f29172..38647f8e 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -38,12 +38,12 @@ class Game: """A Connect 4 Game.""" def __init__( - self, - bot: commands.Bot, - channel: discord.TextChannel, - player1: discord.Member, - player2: typing.Optional[discord.Member], - size: int = 7, + self, + bot: commands.Bot, + channel: discord.TextChannel, + player1: discord.Member, + player2: typing.Optional[discord.Member], + size: int = 7, ) -> None: self.bot = bot @@ -70,7 +70,7 @@ class Game: """Formats and outputs the Connect Four grid to the channel.""" title = ( f'Connect 4: {self.player1.display_name}' - f'VS {self.player2.display_name}' + f'VS {self.bot.user.display_name if isinstance(self.player2, AI) else self.player2.display_name}' ) rows = [" ".join(EMOJIS[s] for s in row) for row in self.grid] @@ -211,16 +211,26 @@ class AI: return possible_coords def check_ai_win(self, coord_list: typing.List[Coordinate]) -> typing.Optional[Coordinate]: - """Check if placing a counter in any possible coordinate would cause the AI to win.""" - if random.randint(1, 10) == 1: # 10% chance of not winning + """ + Check AI win. + + Check if placing a counter in any possible coordinate would cause the AI to win + with 10% chance of not winning and returning None + """ + if random.randint(1, 10) == 1: return for coords in coord_list: if self.game.check_win(coords, 2): return coords def check_player_win(self, coord_list: typing.List[Coordinate]) -> typing.Optional[Coordinate]: - """Check if placing a counter in any possible coordinate would stop the player from winning.""" - if random.randint(1, 4) == 1: # 25% chance of not blocking the player + """ + Check Player win. + + Check if placing a counter in possible coordinates would stop the player + from winning with 25% of not blocking them and returning None. + """ + if random.randint(1, 4) == 1: return for coords in coord_list: if self.game.check_win(coords, 1): @@ -283,19 +293,20 @@ class ConnectFour(commands.Cog): return True def get_player( - self, - ctx: commands.Context, - announcement: discord.Message, - reaction: discord.Reaction, - user: discord.Member + self, + ctx: commands.Context, + announcement: discord.Message, + reaction: discord.Reaction, + user: discord.Member ) -> bool: """Predicate checking the criteria for the announcement message.""" if self.already_playing(ctx.author): # If they've joined a game since requesting a player 2 return True # Is dealt with later on + if ( - user.id not in (ctx.me.id, ctx.author.id) - and str(reaction.emoji) == HAND_RAISED_EMOJI - and reaction.message.id == announcement.id + user.id not in (ctx.me.id, ctx.author.id) + and str(reaction.emoji) == HAND_RAISED_EMOJI + and reaction.message.id == announcement.id ): if self.already_playing(user): self.bot.loop.create_task(ctx.send(f"{user.mention} You're already playing a game!")) @@ -312,9 +323,9 @@ class ConnectFour(commands.Cog): return True if ( - user.id == ctx.author.id - and str(reaction.emoji) == CROSS_EMOJI - and reaction.message.id == announcement.id + user.id == ctx.author.id + and str(reaction.emoji) == CROSS_EMOJI + and reaction.message.id == announcement.id ): return True return False @@ -333,7 +344,8 @@ class ConnectFour(commands.Cog): except Exception: # End the game in the event of an unforeseen error so the players aren't stuck in a game await ctx.send(f"{ctx.author.mention} {user.mention if user else ''} An error occurred. Game failed") - self.games.remove(game) + if game in self.games: + self.games.remove(game) raise @commands.group( -- cgit v1.2.3 From a972cd19e993e5ea9a1120432d7523329cb22d9b Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Thu, 21 Jan 2021 11:32:41 +0530 Subject: Add ability to get custom tokens from the bot --- bot/exts/evergreen/connect_four.py | 67 +++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 23 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 38647f8e..6ae14f53 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -6,7 +6,7 @@ from functools import partial import discord from discord.ext import commands -EMOJIS = [":white_circle:", ":blue_circle:", ":red_circle:"] +EMOJIS = None NUMBERS = [ ":one:", ":two:", @@ -38,12 +38,12 @@ class Game: """A Connect 4 Game.""" def __init__( - self, - bot: commands.Bot, - channel: discord.TextChannel, - player1: discord.Member, - player2: typing.Optional[discord.Member], - size: int = 7, + self, + bot: commands.Bot, + channel: discord.TextChannel, + player1: discord.Member, + player2: typing.Optional[discord.Member], + size: int = 7, ) -> None: self.bot = bot @@ -293,20 +293,20 @@ class ConnectFour(commands.Cog): return True def get_player( - self, - ctx: commands.Context, - announcement: discord.Message, - reaction: discord.Reaction, - user: discord.Member + self, + ctx: commands.Context, + announcement: discord.Message, + reaction: discord.Reaction, + user: discord.Member ) -> bool: """Predicate checking the criteria for the announcement message.""" if self.already_playing(ctx.author): # If they've joined a game since requesting a player 2 return True # Is dealt with later on if ( - user.id not in (ctx.me.id, ctx.author.id) - and str(reaction.emoji) == HAND_RAISED_EMOJI - and reaction.message.id == announcement.id + user.id not in (ctx.me.id, ctx.author.id) + and str(reaction.emoji) == HAND_RAISED_EMOJI + and reaction.message.id == announcement.id ): if self.already_playing(user): self.bot.loop.create_task(ctx.send(f"{user.mention} You're already playing a game!")) @@ -323,9 +323,9 @@ class ConnectFour(commands.Cog): return True if ( - user.id == ctx.author.id - and str(reaction.emoji) == CROSS_EMOJI - and reaction.message.id == announcement.id + user.id == ctx.author.id + and str(reaction.emoji) == CROSS_EMOJI + and reaction.message.id == announcement.id ): return True return False @@ -334,9 +334,18 @@ class ConnectFour(commands.Cog): """Check if someone is already in a game.""" return any(player in (game.player1, game.player2) for game in self.games) - async def _play_game(self, ctx: commands.Context, user: typing.Optional[discord.Member], board_size: int) -> None: + async def _play_game( + self, + ctx: commands.Context, + user: typing.Optional[discord.Member], + board_size: int, + emoji1: str, + emoji2: str + ) -> None: """Helper for playing a game of connect four.""" try: + global EMOJIS + EMOJIS = [":white_circle:", str(emoji1), str(emoji2)] game = Game(self.bot, ctx.channel, ctx.author, user, size=board_size) self.games.append(game) await game.start_game() @@ -352,7 +361,13 @@ class ConnectFour(commands.Cog): invoke_without_command=True, aliases=["4inarow", "connect4", "connectfour", "c4"] ) - async def connect_four(self, ctx: commands.Context, board_size: int = 7) -> None: + async def connect_four( + self, + ctx: commands.Context, + board_size: int = 7, + emoji1: str = ":blue_circle:", + emoji2: str = ":red_circle:" + ) -> None: """ Play the classic game of Connect Four with someone! @@ -399,16 +414,22 @@ class ConnectFour(commands.Cog): if self.already_playing(ctx.author): return - await self._play_game(ctx, user, board_size) + await self._play_game(ctx, user, board_size, emoji1, emoji2) @connect_four.command(aliases=["bot", "computer", "cpu"]) - async def ai(self, ctx: commands.Context, board_size: int = 7) -> None: + async def ai( + self, + ctx: commands.Context, + board_size: int = 7, + emoji1: str = ":blue_circle:", + emoji2: str = ":red_circle:" + ) -> None: """Play Connect Four against a computer player.""" check_author_result = await self.check_author(ctx, board_size) if not check_author_result: return - await self._play_game(ctx, user=None, board_size=board_size) + await self._play_game(ctx, None, board_size, emoji1, emoji2) def setup(bot: commands.Bot) -> None: -- cgit v1.2.3 From 8175bc0fbe1fad08372a27b1fb340baf8f20062b Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Thu, 21 Jan 2021 14:01:45 +0530 Subject: Remove redundant code --- bot/exts/evergreen/connect_four.py | 13 +++---------- 1 file changed, 3 insertions(+), 10 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 6ae14f53..e6d4b41a 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -133,7 +133,6 @@ class Game: ) player_num = 1 if self.player_active == self.player1 else 2 while True: - full_column = False try: reaction, user = await self.bot.wait_for("reaction_add", check=self.predicate, timeout=30.0) except asyncio.TimeoutError: @@ -149,21 +148,15 @@ class Game: await message.delete() await self.message.remove_reaction(reaction, user) - column_num = self.unicode_numbers.index(str(reaction.emoji)) + column_num = self.unicode_numbers.index(str(reaction.emoji)) column = [row[column_num] for row in self.grid] for row_num, square in reversed(list(enumerate(column))): if not square: self.grid[row_num][column_num] = player_num - coords = row_num, column_num - break - else: - await self.channel.send(f"Column {column_num + 1} is full. Try again") - full_column = True - if not full_column: - break - return coords + return row_num, column_num + message = await self.channel.send(f"Column {column_num + 1} is full. Try again") def check_win(self, coords: Coordinate, player_num: int) -> bool: """Check that placing a counter here would cause the player to win.""" -- cgit v1.2.3 From 35b2984674c3a1645476a8c2172deadd51472f0f Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Thu, 21 Jan 2021 14:14:15 +0530 Subject: Check if the emoji given by user is available --- bot/exts/evergreen/connect_four.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index e6d4b41a..a44d6dbf 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -358,8 +358,8 @@ class ConnectFour(commands.Cog): self, ctx: commands.Context, board_size: int = 7, - emoji1: str = ":blue_circle:", - emoji2: str = ":red_circle:" + emoji1: discord.Emoji = ":blue_circle:", + emoji2: discord.Emoji = ":red_circle:" ) -> None: """ Play the classic game of Connect Four with someone! @@ -407,6 +407,9 @@ class ConnectFour(commands.Cog): if self.already_playing(ctx.author): return + emoji1 = str(emoji1) + emoji2 = str(emoji2) + await self._play_game(ctx, user, board_size, emoji1, emoji2) @connect_four.command(aliases=["bot", "computer", "cpu"]) @@ -414,14 +417,17 @@ class ConnectFour(commands.Cog): self, ctx: commands.Context, board_size: int = 7, - emoji1: str = ":blue_circle:", - emoji2: str = ":red_circle:" + emoji1: discord.Emoji = ":blue_circle:", + emoji2: discord.Emoji = ":red_circle:" ) -> None: """Play Connect Four against a computer player.""" check_author_result = await self.check_author(ctx, board_size) if not check_author_result: return + emoji1 = str(emoji1) + emoji2 = str(emoji2) + await self._play_game(ctx, None, board_size, emoji1, emoji2) -- cgit v1.2.3 From db53775344e0ceef247fb0e045bc515c9d9591b8 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sat, 23 Jan 2021 05:35:09 +0530 Subject: Fix user given emoji check --- bot/exts/evergreen/connect_four.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index a44d6dbf..be370a83 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -6,7 +6,7 @@ from functools import partial import discord from discord.ext import commands -EMOJIS = None +EMOJI_TOKENS = [":white_circle:", ":blue_circle:", ":red_circle:"] NUMBERS = [ ":one:", ":two:", @@ -32,6 +32,7 @@ UNICODE_NUMBERS = [ CROSS_EMOJI = "\u274e" HAND_RAISED_EMOJI = "\U0001f64b" Coordinate = typing.Optional[typing.Tuple[int, int]] +EMOJI_CHECK = typing.Union[discord.Emoji, str] class Game: @@ -336,9 +337,10 @@ class ConnectFour(commands.Cog): emoji2: str ) -> None: """Helper for playing a game of connect four.""" + global EMOJIS + EMOJIS = [":white_circle:", str(emoji1), str(emoji2)] + try: - global EMOJIS - EMOJIS = [":white_circle:", str(emoji1), str(emoji2)] game = Game(self.bot, ctx.channel, ctx.author, user, size=board_size) self.games.append(game) await game.start_game() @@ -358,8 +360,8 @@ class ConnectFour(commands.Cog): self, ctx: commands.Context, board_size: int = 7, - emoji1: discord.Emoji = ":blue_circle:", - emoji2: discord.Emoji = ":red_circle:" + emoji1: EMOJI_CHECK = ":blue_circle:", + emoji2: EMOJI_CHECK = ":red_circle:" ) -> None: """ Play the classic game of Connect Four with someone! @@ -368,6 +370,11 @@ class ConnectFour(commands.Cog): The game will start once someone has reacted. All inputs will be through reactions. """ + if isinstance(emoji1, str) and len(emoji1) > 1: + raise commands.EmojiNotFound(emoji1) + if isinstance(emoji2, str) and len(emoji2) > 1: + raise commands.EmojiNotFound(emoji2) + check_author_result = await self.check_author(ctx, board_size) if not check_author_result: return @@ -407,28 +414,22 @@ class ConnectFour(commands.Cog): if self.already_playing(ctx.author): return - emoji1 = str(emoji1) - emoji2 = str(emoji2) - - await self._play_game(ctx, user, board_size, emoji1, emoji2) + await self._play_game(ctx, user, board_size, str(emoji1), str(emoji2)) @connect_four.command(aliases=["bot", "computer", "cpu"]) async def ai( self, ctx: commands.Context, board_size: int = 7, - emoji1: discord.Emoji = ":blue_circle:", - emoji2: discord.Emoji = ":red_circle:" + emoji1: EMOJI_CHECK = ":blue_circle:" ) -> None: """Play Connect Four against a computer player.""" + if isinstance(emoji1, str) and len(emoji1) > 1: + raise commands.EmojiNotFound(emoji1) check_author_result = await self.check_author(ctx, board_size) if not check_author_result: return - - emoji1 = str(emoji1) - emoji2 = str(emoji2) - - await self._play_game(ctx, None, board_size, emoji1, emoji2) + await self._play_game(ctx, None, board_size, str(emoji1), ":red_circle:") def setup(bot: commands.Bot) -> None: -- cgit v1.2.3 From f701e93cfb768f7a04c786437cdbe3a7105d4bc9 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 24 Jan 2021 16:58:07 +0530 Subject: Send a message on draw (was catching a error earlier) ; Improve send game over (winner/loser) code ; add case_insensitive alias --- bot/exts/evergreen/connect_four.py | 57 ++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 27 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index be370a83..7c5261c5 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -71,7 +71,7 @@ class Game: """Formats and outputs the Connect Four grid to the channel.""" title = ( f'Connect 4: {self.player1.display_name}' - f'VS {self.bot.user.display_name if isinstance(self.player2, AI) else self.player2.display_name}' + f' VS {self.bot.user.display_name if isinstance(self.player2, AI) else self.player2.display_name}' ) rows = [" ".join(EMOJIS[s] for s in row) for row in self.grid] @@ -88,14 +88,23 @@ class Game: await self.message.add_reaction(CROSS_EMOJI) await self.message.edit(content=None, embed=embed) + async def game_over(self, winner: discord.user, loser: discord.user) -> None: + """Removes games from list of current games and announces to public chat.""" + await self.channel.send(f"Game Over! {winner.mention} won against {loser.mention}") + await self.print_grid() + async def start_game(self) -> None: """Begins the game.""" self.player_active, self.player_inactive = self.player1, self.player2 while True: await self.print_grid() + if isinstance(self.player_active, AI): coords = self.player_active.play() + if not coords: + await self.channel.send(f"Game Over! Its's A Draw :tada:") + await self.print_grid() else: coords = await self.player_turn() @@ -103,18 +112,10 @@ class Game: return if self.check_win(coords, 1 if self.player_active == self.player1 else 2): - if isinstance(self.player_active, AI): - await self.channel.send(f"Game Over! {self.player_inactive.mention} lost against" - f" {self.bot.user.mention}") - else: - if isinstance(self.player_inactive, AI): - await self.channel.send(f"Game Over! {self.player_active.mention} won against" - f" {self.bot.user.mention}") - else: - await self.channel.send( - f"Game Over! {self.player_active.mention} won against {self.player_inactive.mention}" - ) - await self.print_grid() + await self.game_over( + self.bot.user if isinstance(self.player_active, AI) else {self.player_active}, + self.bot.user if isinstance(self.player_inactive, AI) else {self.player_inactive}, + ) return self.player_active, self.player_inactive = self.player_inactive, self.player_active @@ -122,9 +123,9 @@ class Game: def predicate(self, reaction: discord.Reaction, user: discord.Member) -> bool: """The predicate to check for the player's reaction.""" return ( - reaction.message.id == self.message.id - and user.id == self.player_active.id - and str(reaction.emoji) in (*self.unicode_numbers, CROSS_EMOJI) + reaction.message.id == self.message.id + and user.id == self.player_active.id + and str(reaction.emoji) in (*self.unicode_numbers, CROSS_EMOJI) ) async def player_turn(self) -> Coordinate: @@ -142,9 +143,7 @@ class Game: else: if str(reaction.emoji) == CROSS_EMOJI: await message.delete() - await self.channel.send( - f"{user.mention} has abandoned the game :(" - ) + await self.channel.send(f"{self.player_active.user} surrendered. Game over!") return await message.delete() @@ -235,7 +234,7 @@ class AI: """Picks a random coordinate from the possible ones.""" return random.choice(coord_list) - def play(self) -> Coordinate: + def play(self) -> typing.Union[Coordinate, bool]: """ Plays for the AI. @@ -247,10 +246,13 @@ class AI: """ possible_coords = self.get_possible_places() + if not possible_coords: + return False + coords = ( - self.check_ai_win(possible_coords) - or self.check_player_win(possible_coords) - or self.random_coords(possible_coords) + self.check_ai_win(possible_coords) + or self.check_player_win(possible_coords) + or self.random_coords(possible_coords) ) row, column = coords @@ -354,14 +356,15 @@ class ConnectFour(commands.Cog): @commands.group( invoke_without_command=True, - aliases=["4inarow", "connect4", "connectfour", "c4"] + aliases=["4inarow", "connect4", "connectfour", "c4"], + case_insensitive=True ) async def connect_four( self, ctx: commands.Context, board_size: int = 7, - emoji1: EMOJI_CHECK = ":blue_circle:", - emoji2: EMOJI_CHECK = ":red_circle:" + emoji1: EMOJI_CHECK = "\U0001f535", + emoji2: EMOJI_CHECK = "\U0001f534" ) -> None: """ Play the classic game of Connect Four with someone! @@ -421,7 +424,7 @@ class ConnectFour(commands.Cog): self, ctx: commands.Context, board_size: int = 7, - emoji1: EMOJI_CHECK = ":blue_circle:" + emoji1: EMOJI_CHECK = "\U0001f535" ) -> None: """Play Connect Four against a computer player.""" if isinstance(emoji1, str) and len(emoji1) > 1: -- cgit v1.2.3 From 914f2a8340c8c5636589f4e11de880f48cac52e2 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 24 Jan 2021 17:02:11 +0530 Subject: Fix lint issues --- bot/exts/evergreen/connect_four.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 7c5261c5..51d1adc3 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -103,7 +103,7 @@ class Game: if isinstance(self.player_active, AI): coords = self.player_active.play() if not coords: - await self.channel.send(f"Game Over! Its's A Draw :tada:") + await self.channel.send("Game Over! It's A Draw :tada:") await self.print_grid() else: coords = await self.player_turn() @@ -123,9 +123,9 @@ class Game: def predicate(self, reaction: discord.Reaction, user: discord.Member) -> bool: """The predicate to check for the player's reaction.""" return ( - reaction.message.id == self.message.id - and user.id == self.player_active.id - and str(reaction.emoji) in (*self.unicode_numbers, CROSS_EMOJI) + reaction.message.id == self.message.id + and user.id == self.player_active.id + and str(reaction.emoji) in (*self.unicode_numbers, CROSS_EMOJI) ) async def player_turn(self) -> Coordinate: @@ -250,9 +250,9 @@ class AI: return False coords = ( - self.check_ai_win(possible_coords) - or self.check_player_win(possible_coords) - or self.random_coords(possible_coords) + self.check_ai_win(possible_coords) + or self.check_player_win(possible_coords) + or self.random_coords(possible_coords) ) row, column = coords -- cgit v1.2.3 From a563d3ae23c1c1eff01a284e0510bdc364cc415a Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sat, 30 Jan 2021 17:21:11 +0530 Subject: Fix misleading game_over docstring --- bot/exts/evergreen/connect_four.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 51d1adc3..19067277 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -89,7 +89,7 @@ class Game: await self.message.edit(content=None, embed=embed) async def game_over(self, winner: discord.user, loser: discord.user) -> None: - """Removes games from list of current games and announces to public chat.""" + """Announces to public chat.""" await self.channel.send(f"Game Over! {winner.mention} won against {loser.mention}") await self.print_grid() -- cgit v1.2.3 From 79fb946c55b9f3049b0e2f0c5fd38f2ee2ee5add Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sat, 30 Jan 2021 17:22:45 +0530 Subject: Line 114, 115 was cuasing error as a set was being sent to it instead discord.member object --- bot/exts/evergreen/connect_four.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 19067277..858caf78 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -113,8 +113,8 @@ class Game: if self.check_win(coords, 1 if self.player_active == self.player1 else 2): await self.game_over( - self.bot.user if isinstance(self.player_active, AI) else {self.player_active}, - self.bot.user if isinstance(self.player_inactive, AI) else {self.player_inactive}, + self.bot.user if isinstance(self.player_active, AI) else self.player_active, + self.bot.user if isinstance(self.player_inactive, AI) else self.player_inactive, ) return -- cgit v1.2.3 From 9675434e1437b4c182e1eccbeeba9aa064ce779b Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sat, 30 Jan 2021 17:23:37 +0530 Subject: REmove repeating code --- bot/exts/evergreen/connect_four.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 858caf78..30eb0ff9 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -141,12 +141,11 @@ class Game: await self.channel.send(f"{self.player_active.mention}, you took too long. Game over!") return else: + await message.delete() if str(reaction.emoji) == CROSS_EMOJI: - await message.delete() await self.channel.send(f"{self.player_active.user} surrendered. Game over!") return - - await message.delete() + await self.message.remove_reaction(reaction, user) column_num = self.unicode_numbers.index(str(reaction.emoji)) -- cgit v1.2.3 From 630f46a197c89a137e89c0bba526090c8f0ace33 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sat, 30 Jan 2021 17:25:45 +0530 Subject: Error while sending surrender message as it was taking .user instead of .mention --- bot/exts/evergreen/connect_four.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 30eb0ff9..7ad0d723 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -82,7 +82,7 @@ class Game: if self.message: await self.message.edit(embed=embed) else: - self.message = await self.channel.send(content='Loading ....') + self.message = await self.channel.send(content='Loading...') for emoji in self.unicode_numbers: await self.message.add_reaction(emoji) await self.message.add_reaction(CROSS_EMOJI) @@ -143,7 +143,7 @@ class Game: else: await message.delete() if str(reaction.emoji) == CROSS_EMOJI: - await self.channel.send(f"{self.player_active.user} surrendered. Game over!") + await self.channel.send(f"{self.player_active.mention} surrendered. Game over!") return await self.message.remove_reaction(reaction, user) -- cgit v1.2.3 From 46ca17d5fe41e704e40e22839e9d8b21c9a7b659 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 31 Jan 2021 04:55:57 +0530 Subject: Let game_over function handle all gane over instances --- bot/exts/evergreen/connect_four.py | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 7ad0d723..31b85bbe 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -88,9 +88,14 @@ class Game: await self.message.add_reaction(CROSS_EMOJI) await self.message.edit(content=None, embed=embed) - async def game_over(self, winner: discord.user, loser: discord.user) -> None: + async def game_over(self, action: str, player1: discord.user, player2: discord.user) -> None: """Announces to public chat.""" - await self.channel.send(f"Game Over! {winner.mention} won against {loser.mention}") + if action == "win": + await self.channel.send(f"Game Over! {player1.mention} won against {player2.mention}") + elif action == "draw": + await self.channel.send(f"Game Over! {player1.mention} {player2.mention} It's A Draw :tada:") + elif action == "quit": + await self.channel.send(f"{self.player1.mention} surrendered. Game over!") await self.print_grid() async def start_game(self) -> None: @@ -103,8 +108,11 @@ class Game: if isinstance(self.player_active, AI): coords = self.player_active.play() if not coords: - await self.channel.send("Game Over! It's A Draw :tada:") - await self.print_grid() + await self.game_over( + "draw", + self.bot.user if isinstance(self.player_active, AI) else self.player_active, + self.bot.user if isinstance(self.player_inactive, AI) else self.player_inactive, + ) else: coords = await self.player_turn() @@ -113,6 +121,7 @@ class Game: if self.check_win(coords, 1 if self.player_active == self.player1 else 2): await self.game_over( + "win", self.bot.user if isinstance(self.player_active, AI) else self.player_active, self.bot.user if isinstance(self.player_inactive, AI) else self.player_inactive, ) @@ -143,9 +152,9 @@ class Game: else: await message.delete() if str(reaction.emoji) == CROSS_EMOJI: - await self.channel.send(f"{self.player_active.mention} surrendered. Game over!") + await self.game_over("quit", self.player_active, self.player_inactive) return - + await self.message.remove_reaction(reaction, user) column_num = self.unicode_numbers.index(str(reaction.emoji)) -- cgit v1.2.3 From 259686206e71496131872788a746186ee34920d8 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Sun, 31 Jan 2021 05:05:54 +0530 Subject: Add ability to choose ai token while playing against ai --- bot/exts/evergreen/connect_four.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 31b85bbe..50632c1b 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -338,6 +338,15 @@ class ConnectFour(commands.Cog): """Check if someone is already in a game.""" return any(player in (game.player1, game.player2) for game in self.games) + @staticmethod + def check_emojis(e1: EMOJI_CHECK, e2: EMOJI_CHECK) -> typing.Tuple[bool, typing.Optional[str]]: + """Validate the emojis, the user put.""" + if isinstance(e1, str) and len(e1) > 1: + return False, e1 + if isinstance(e2, str) and len(e2) > 1: + return False, e2 + return True, None + async def _play_game( self, ctx: commands.Context, @@ -381,10 +390,9 @@ class ConnectFour(commands.Cog): The game will start once someone has reacted. All inputs will be through reactions. """ - if isinstance(emoji1, str) and len(emoji1) > 1: - raise commands.EmojiNotFound(emoji1) - if isinstance(emoji2, str) and len(emoji2) > 1: - raise commands.EmojiNotFound(emoji2) + check, emoji = self.check_emojis(emoji1, emoji2) + if not check: + raise commands.EmojiNotFound(emoji) check_author_result = await self.check_author(ctx, board_size) if not check_author_result: @@ -432,15 +440,19 @@ class ConnectFour(commands.Cog): self, ctx: commands.Context, board_size: int = 7, - emoji1: EMOJI_CHECK = "\U0001f535" + emoji1: EMOJI_CHECK = "\U0001f535", + emoji2: EMOJI_CHECK = "\U0001f534" ) -> None: """Play Connect Four against a computer player.""" - if isinstance(emoji1, str) and len(emoji1) > 1: - raise commands.EmojiNotFound(emoji1) + check, emoji = self.check_emojis(emoji1, emoji2) + if not check: + raise commands.EmojiNotFound(emoji) + check_author_result = await self.check_author(ctx, board_size) if not check_author_result: return - await self._play_game(ctx, None, board_size, str(emoji1), ":red_circle:") + + await self._play_game(ctx, None, board_size, str(emoji1), str(emoji2)) def setup(bot: commands.Bot) -> None: -- cgit v1.2.3 From 29c84ad7cc6729c0887f49206ade8f98e2655fd1 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 1 Feb 2021 13:37:04 +0530 Subject: Remove the use of globals and instead use class variables --- bot/exts/evergreen/connect_four.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 50632c1b..48473d30 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -6,7 +6,6 @@ from functools import partial import discord from discord.ext import commands -EMOJI_TOKENS = [":white_circle:", ":blue_circle:", ":red_circle:"] NUMBERS = [ ":one:", ":two:", @@ -44,13 +43,15 @@ class Game: channel: discord.TextChannel, player1: discord.Member, player2: typing.Optional[discord.Member], - size: int = 7, + tokens: typing.List[str], + size: int = 7 ) -> None: self.bot = bot self.channel = channel self.player1 = player1 self.player2 = player2 or AI(self.bot, game=self) + self.tokens = tokens self.grid = self.generate_board(size) self.grid_size = size @@ -74,7 +75,7 @@ class Game: f' VS {self.bot.user.display_name if isinstance(self.player2, AI) else self.player2.display_name}' ) - rows = [" ".join(EMOJIS[s] for s in row) for row in self.grid] + rows = [" ".join(self.tokens[s] for s in row) for row in self.grid] first_row = " ".join(x for x in NUMBERS[:self.grid_size]) formatted_grid = "\n".join([first_row] + rows) embed = discord.Embed(title=title, description=formatted_grid) @@ -276,6 +277,8 @@ class ConnectFour(commands.Cog): self.games: typing.List[Game] = [] self.waiting: typing.List[discord.Member] = [] + self.tokens = [":white_circle:", ":blue_circle:", ":red_circle:"] + self.max_board_size = 9 self.min_board_size = 5 @@ -356,11 +359,10 @@ class ConnectFour(commands.Cog): emoji2: str ) -> None: """Helper for playing a game of connect four.""" - global EMOJIS - EMOJIS = [":white_circle:", str(emoji1), str(emoji2)] + self.tokens = [":white_circle:", str(emoji1), str(emoji2)] try: - game = Game(self.bot, ctx.channel, ctx.author, user, size=board_size) + game = Game(self.bot, ctx.channel, ctx.author, user, self.tokens, size=board_size) self.games.append(game) await game.start_game() self.games.remove(game) -- cgit v1.2.3 From 4b8f5789aa0c9f6ce005273ed1affdbc3a87c16a Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 1 Feb 2021 14:46:55 +0530 Subject: Add check if game is not intialized --- bot/exts/evergreen/connect_four.py | 1 + 1 file changed, 1 insertion(+) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 48473d30..bf604e2a 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -360,6 +360,7 @@ class ConnectFour(commands.Cog): ) -> None: """Helper for playing a game of connect four.""" self.tokens = [":white_circle:", str(emoji1), str(emoji2)] + game = None # if game fails to intialize in try...except try: game = Game(self.bot, ctx.channel, ctx.author, user, self.tokens, size=board_size) -- cgit v1.2.3 From 8321add8d6a55132c96a620da2aac5c4bc44c284 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Mon, 8 Feb 2021 06:59:24 +0530 Subject: Use emojis library to do the check instead of checking len() == 1 since some emojis are made of len() > 1 --- Pipfile | 1 + Pipfile.lock | 55 ++++++++++++++++++++++---------------- bot/exts/evergreen/connect_four.py | 9 ++++--- 3 files changed, 39 insertions(+), 26 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/Pipfile b/Pipfile index c382902f..e7e01a31 100644 --- a/Pipfile +++ b/Pipfile @@ -14,6 +14,7 @@ sentry-sdk = "~=0.19" PyYAML = "~=5.3.1" "discord.py" = {extras = ["voice"], version = "~=1.5.1"} async-rediscache = {extras = ["fakeredis"], version = "~=0.1.4"} +emojis = "~=0.6.0" [dev-packages] flake8 = "~=3.8" diff --git a/Pipfile.lock b/Pipfile.lock index be6f9574..cca89cf9 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "9be419062bd9db364ac9dddfcd50aef9c932384b45850363e482591fe7d12403" + "sha256": "b4aaaacbab13179145e36d7b86c736db512286f6cce8e513cc30c48d68fe3810" }, "pipfile-spec": 6, "requires": { @@ -119,6 +119,7 @@ "sha256:6bc25fc545a6b3d57b5f8618e59fc13d3a3a68431e8ca5fd4c13241cd70d0009", "sha256:798caa2a2384b1cbe8a2a139d80734c9db54f9cc155c99d7cc92441a23871c03", "sha256:7c6b1dece89874d9541fc974917b631406233ea0440d0bdfbb8e03bf39a49b3b", + "sha256:7ef7d4ced6b325e92eb4d3502946c78c5367bc416398d387b39591532536734e", "sha256:840793c68105fe031f34d6a086eaea153a0cd5c491cde82a74b420edd0a2b909", "sha256:8d6603078baf4e11edc4168a514c5ce5b3ba6e3e9c374298cb88437957960a53", "sha256:9cc46bc107224ff5b6d04369e7c595acb700c3613ad7bcf2e2012f62ece80c35", @@ -160,6 +161,14 @@ "index": "pypi", "version": "==1.5.1" }, + "emojis": { + "hashes": [ + "sha256:7da34c8a78ae262fd68cef9e2c78a3c1feb59784489eeea0f54ba1d4b7111c7c", + "sha256:bf605d1f1a27a81cd37fe82eb65781c904467f569295a541c33710b97e4225ec" + ], + "index": "pypi", + "version": "==0.6.0" + }, "fakeredis": { "hashes": [ "sha256:01cb47d2286825a171fb49c0e445b1fa9307087e07cbb3d027ea10dbff108b6a", @@ -229,11 +238,11 @@ }, "idna": { "hashes": [ - "sha256:b307872f855b18632ce0c21c5e45be78c0ea7ae4c15c828c20788b26921eb3f6", - "sha256:b97d804b1e9b523befed77c48dacec60e6dcb0b5391d57af6a65a312a90648c0" + "sha256:5205d03e7bcbb919cc9c19885f9920d622ca52448306f2377daede5cf3faac16", + "sha256:c5b02147e01ea9920e6b0a3f1f7bb833612d507592c837a6c49552768f4054e1" ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==2.10" + "markers": "python_version >= '3.4'", + "version": "==3.1" }, "multidict": { "hashes": [ @@ -436,11 +445,11 @@ }, "urllib3": { "hashes": [ - "sha256:19188f96923873c92ccb987120ec4acaa12f0461fa9ce5d3d0772bc965a39e08", - "sha256:d8ff90d979214d7b4f8ce956e80f4028fc6860e4431f731ea4a8c08f23f99473" + "sha256:1b465e494e3e0d8939b50680403e3aedaa2bc434b7d5af64dfd3c958d7f5ae80", + "sha256:de3eedaad74a2683334e282005cd8d7f22f4d55fa690a2a1020a416cb0a47e73" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4' and python_version < '4'", - "version": "==1.26.2" + "version": "==1.26.3" }, "yarl": { "hashes": [ @@ -514,11 +523,11 @@ }, "flake8-annotations": { "hashes": [ - "sha256:0bcebb0792f1f96d617ded674dca7bf64181870bfe5dace353a1483551f8e5f1", - "sha256:bebd11a850f6987a943ce8cdff4159767e0f5f89b3c88aca64680c2175ee02df" + "sha256:3a377140556aecf11fa9f3bb18c10db01f5ea56dc79a730e2ec9b4f1f49e2055", + "sha256:e17947a48a5b9f632fe0c72682fc797c385e451048e7dfb20139f448a074cb3e" ], "index": "pypi", - "version": "==2.4.1" + "version": "==2.5.0" }, "flake8-bugbear": { "hashes": [ @@ -576,11 +585,11 @@ }, "identify": { "hashes": [ - "sha256:943cd299ac7f5715fcb3f684e2fc1594c1e0f22a90d15398e5888143bd4144b5", - "sha256:cc86e6a9a390879dcc2976cef169dd9cc48843ed70b7380f321d1b118163c60e" + "sha256:70b638cf4743f33042bebb3b51e25261a0a10e80f978739f17e7fd4837664a66", + "sha256:9dfb63a2e871b807e3ba62f029813552a24b5289504f5b071dea9b041aee9fe4" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.5.10" + "version": "==1.5.13" }, "mccabe": { "hashes": [ @@ -606,11 +615,11 @@ }, "pre-commit": { "hashes": [ - "sha256:6c86d977d00ddc8a60d68eec19f51ef212d9462937acf3ea37c7adec32284ac0", - "sha256:ee784c11953e6d8badb97d19bc46b997a3a9eded849881ec587accd8608d74a4" + "sha256:16212d1fde2bed88159287da88ff03796863854b04dc9f838a55979325a3d20e", + "sha256:399baf78f13f4de82a29b649afd74bef2c4e28eb4f021661fc7f29246e8c7a3a" ], "index": "pypi", - "version": "==2.9.3" + "version": "==2.10.1" }, "pycodestyle": { "hashes": [ @@ -665,10 +674,10 @@ }, "snowballstemmer": { "hashes": [ - "sha256:209f257d7533fdb3cb73bdbd24f436239ca3b2fa67d56f6ff88e86be08cc5ef0", - "sha256:df3bac3df4c2c01363f3dd2cfa78cce2840a79b9f1c2d2de9ce8d31683992f52" + "sha256:b51b447bea85f9968c13b650126a888aabd4cb4463fca868ec596826325dedc2", + "sha256:e997baa4f2e9139951b6f4c631bad912dfd3c792467e2f03d7239464af90e914" ], - "version": "==2.0.0" + "version": "==2.1.0" }, "toml": { "hashes": [ @@ -680,11 +689,11 @@ }, "virtualenv": { "hashes": [ - "sha256:54b05fc737ea9c9ee9f8340f579e5da5b09fb64fd010ab5757eb90268616907c", - "sha256:b7a8ec323ee02fb2312f098b6b4c9de99559b462775bc8fe3627a73706603c1b" + "sha256:147b43894e51dd6bba882cf9c282447f780e2251cd35172403745fc381a0a80d", + "sha256:2be72df684b74df0ea47679a7df93fd0e04e72520022c57b479d8f881485dbe3" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==20.2.2" + "version": "==20.4.2" } } } diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index bf604e2a..02e876f4 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -4,6 +4,7 @@ import typing from functools import partial import discord +import emojis from discord.ext import commands NUMBERS = [ @@ -342,11 +343,13 @@ class ConnectFour(commands.Cog): return any(player in (game.player1, game.player2) for game in self.games) @staticmethod - def check_emojis(e1: EMOJI_CHECK, e2: EMOJI_CHECK) -> typing.Tuple[bool, typing.Optional[str]]: + def check_emojis( + e1: EMOJI_CHECK, e2: EMOJI_CHECK + ) -> typing.Tuple[bool, typing.Optional[str]]: """Validate the emojis, the user put.""" - if isinstance(e1, str) and len(e1) > 1: + if isinstance(e1, str) and emojis.count(e1) != 1: return False, e1 - if isinstance(e2, str) and len(e2) > 1: + if isinstance(e2, str) and emojis.count(e2) != 1: return False, e2 return True, None -- cgit v1.2.3 From 13dad6bf796a6009db4ff768795a3d45d044ff0e Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Tue, 23 Feb 2021 16:46:08 +0530 Subject: Fix pipfile conflicts, and add/use emojis from constants.py --- Pipfile.lock | 21 +++++++++++++++------ bot/constants.py | 5 ++++- bot/exts/evergreen/connect_four.py | 37 +++++++++---------------------------- 3 files changed, 28 insertions(+), 35 deletions(-) (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/Pipfile.lock b/Pipfile.lock index bd894ffa..ec801979 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "9be419062bd9db364ac9dddfcd50aef9c932384b45850363e482591fe7d12403" + "sha256": "b4aaaacbab13179145e36d7b86c736db512286f6cce8e513cc30c48d68fe3810" }, "pipfile-spec": 6, "requires": { @@ -161,6 +161,14 @@ "index": "pypi", "version": "==1.5.1" }, + "emojis": { + "hashes": [ + "sha256:7da34c8a78ae262fd68cef9e2c78a3c1feb59784489eeea0f54ba1d4b7111c7c", + "sha256:bf605d1f1a27a81cd37fe82eb65781c904467f569295a541c33710b97e4225ec" + ], + "index": "pypi", + "version": "==0.6.0" + }, "fakeredis": { "hashes": [ "sha256:01cb47d2286825a171fb49c0e445b1fa9307087e07cbb3d027ea10dbff108b6a", @@ -406,10 +414,11 @@ }, "sentry-sdk": { "hashes": [ - "sha256:3693cb47ba8d90c004ac002425770b32aaf0c83a846ec48e2d1364e7db1d072d" + "sha256:4ae8d1ced6c67f1c8ea51d82a16721c166c489b76876c9f2c202b8a50334b237", + "sha256:e75c8c58932bda8cd293ea8e4b242527129e1caaec91433d21b8b2f20fee030b" ], "index": "pypi", - "version": "==0.20.1" + "version": "==0.20.3" }, "six": { "hashes": [ @@ -576,11 +585,11 @@ }, "identify": { "hashes": [ - "sha256:70b638cf4743f33042bebb3b51e25261a0a10e80f978739f17e7fd4837664a66", - "sha256:9dfb63a2e871b807e3ba62f029813552a24b5289504f5b071dea9b041aee9fe4" + "sha256:de7129142a5c86d75a52b96f394d94d96d497881d2aaf8eafe320cdbe8ac4bcc", + "sha256:e0dae57c0397629ce13c289f6ddde0204edf518f557bfdb1e56474aa143e77c3" ], "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.5.13" + "version": "==1.5.14" }, "mccabe": { "hashes": [ diff --git a/bot/constants.py b/bot/constants.py index bb538487..682ccf6f 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -165,6 +165,7 @@ class Emojis: envelope = "\U0001F4E8" trashcan = "<:trashcan:637136429717389331>" ok_hand = ":ok_hand:" + hand_raised = "\U0001f64b" dice_1 = "<:dice_1:755891608859443290>" dice_2 = "<:dice_2:755891608741740635>" @@ -179,7 +180,6 @@ class Emojis: pull_request_closed = "<:PRClosed:629695470519713818>" merge = "<:PRMerged:629695470570176522>" - # TicTacToe Emojis number_emojis = { 1: "\u0031\ufe0f\u20e3", 2: "\u0032\ufe0f\u20e3", @@ -191,8 +191,11 @@ class Emojis: 8: "\u0038\ufe0f\u20e3", 9: "\u0039\ufe0f\u20e3" } + confirmation = "\u2705" decline = "\u274c" + incident_unactioned = "<:incident_unactioned:719645583245180960>" + x = "\U0001f1fd" o = "\U0001f1f4" diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 02e876f4..65458dbc 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -7,30 +7,11 @@ import discord import emojis from discord.ext import commands -NUMBERS = [ - ":one:", - ":two:", - ":three:", - ":four:", - ":five:", - ":six:", - ":seven:", - ":eight:", - ":nine:" -] -UNICODE_NUMBERS = [ - "\u0031\u20e3", - "\u0032\u20e3", - "\u0033\u20e3", - "\u0034\u20e3", - "\u0035\u20e3", - "\u0036\u20e3", - "\u0037\u20e3", - "\u0038\u20e3", - "\u0039\u20e3", -] -CROSS_EMOJI = "\u274e" -HAND_RAISED_EMOJI = "\U0001f64b" +from bot.constants import Emojis + +NUMBERS = list(Emojis.number_emojis.values()) +CROSS_EMOJI = Emojis.incident_unactioned + Coordinate = typing.Optional[typing.Tuple[int, int]] EMOJI_CHECK = typing.Union[discord.Emoji, str] @@ -57,7 +38,7 @@ class Game: self.grid = self.generate_board(size) self.grid_size = size - self.unicode_numbers = UNICODE_NUMBERS[:self.grid_size] + self.unicode_numbers = NUMBERS[:self.grid_size] self.message = None @@ -313,7 +294,7 @@ class ConnectFour(commands.Cog): if ( user.id not in (ctx.me.id, ctx.author.id) - and str(reaction.emoji) == HAND_RAISED_EMOJI + and str(reaction.emoji) == Emojis.hand_raised and reaction.message.id == announcement.id ): if self.already_playing(user): @@ -406,11 +387,11 @@ class ConnectFour(commands.Cog): announcement = await ctx.send( "**Connect Four**: A new game is about to start!\n" - f"Press {HAND_RAISED_EMOJI} to play against {ctx.author.mention}!\n" + f"Press {Emojis.hand_raised} to play against {ctx.author.mention}!\n" f"(Cancel the game with {CROSS_EMOJI}.)" ) self.waiting.append(ctx.author) - await announcement.add_reaction(HAND_RAISED_EMOJI) + await announcement.add_reaction(Emojis.hand_raised) await announcement.add_reaction(CROSS_EMOJI) try: -- cgit v1.2.3 From b70a3fb551600fc34ea4d9e5111957149dceb1a2 Mon Sep 17 00:00:00 2001 From: Shivansh-007 Date: Wed, 24 Feb 2021 17:28:08 +0530 Subject: Make connect 4 and its sub command ai guild only --- bot/exts/evergreen/connect_four.py | 3 + bot/exts/evergreen/error_handler.py | 129 ------------------------------------ 2 files changed, 3 insertions(+), 129 deletions(-) delete mode 100644 bot/exts/evergreen/error_handler.py (limited to 'bot/exts/evergreen/connect_four.py') diff --git a/bot/exts/evergreen/connect_four.py b/bot/exts/evergreen/connect_four.py index 65458dbc..7e3ec42b 100644 --- a/bot/exts/evergreen/connect_four.py +++ b/bot/exts/evergreen/connect_four.py @@ -6,6 +6,7 @@ from functools import partial import discord import emojis from discord.ext import commands +from discord.ext.commands import guild_only from bot.constants import Emojis @@ -358,6 +359,7 @@ class ConnectFour(commands.Cog): self.games.remove(game) raise + @guild_only() @commands.group( invoke_without_command=True, aliases=["4inarow", "connect4", "connectfour", "c4"], @@ -422,6 +424,7 @@ class ConnectFour(commands.Cog): await self._play_game(ctx, user, board_size, str(emoji1), str(emoji2)) + @guild_only() @connect_four.command(aliases=["bot", "computer", "cpu"]) async def ai( self, diff --git a/bot/exts/evergreen/error_handler.py b/bot/exts/evergreen/error_handler.py deleted file mode 100644 index 99af1519..00000000 --- a/bot/exts/evergreen/error_handler.py +++ /dev/null @@ -1,129 +0,0 @@ -import logging -import math -import random -from typing import Iterable, Union - -from discord import Embed, Message -from discord.ext import commands -from sentry_sdk import push_scope - -from bot.constants import Colours, ERROR_REPLIES, NEGATIVE_REPLIES -from bot.utils.decorators import InChannelCheckFailure, InMonthCheckFailure -from bot.utils.exceptions import UserNotPlayingError - -log = logging.getLogger(__name__) - - -class CommandErrorHandler(commands.Cog): - """A error handler for the PythonDiscord server.""" - - def __init__(self, bot: commands.Bot): - self.bot = bot - - @staticmethod - def revert_cooldown_counter(command: commands.Command, message: Message) -> None: - """Undoes the last cooldown counter for user-error cases.""" - if command._buckets.valid: - bucket = command._buckets.get_bucket(message) - bucket._tokens = min(bucket.rate, bucket._tokens + 1) - logging.debug("Cooldown counter reverted as the command was not used correctly.") - - @staticmethod - def error_embed(message: str, title: Union[Iterable, str] = ERROR_REPLIES) -> Embed: - """Build a basic embed with red colour and either a random error title or a title provided.""" - embed = Embed(colour=Colours.soft_red) - if isinstance(title, str): - embed.title = title - else: - embed.title = random.choice(title) - embed.description = message - return embed - - @commands.Cog.listener() - async def on_command_error(self, ctx: commands.Context, error: commands.CommandError) -> None: - """Activates when a command opens an error.""" - if getattr(error, 'handled', False): - logging.debug(f"Command {ctx.command} had its error already handled locally; ignoring.") - return - - error = getattr(error, 'original', error) - logging.debug( - f"Error Encountered: {type(error).__name__} - {str(error)}, " - f"Command: {ctx.command}, " - f"Author: {ctx.author}, " - f"Channel: {ctx.channel}" - ) - - if isinstance(error, commands.CommandNotFound): - return - - if isinstance(error, (InChannelCheckFailure, InMonthCheckFailure)): - await ctx.send(embed=self.error_embed(str(error), NEGATIVE_REPLIES), delete_after=7.5) - return - - if isinstance(error, commands.UserInputError): - self.revert_cooldown_counter(ctx.command, ctx.message) - embed = self.error_embed( - f"Your input was invalid: {error}\n\nUsage:\n```{ctx.prefix}{ctx.command} {ctx.command.signature}```" - ) - await ctx.send(embed=embed) - return - - if isinstance(error, commands.CommandOnCooldown): - mins, secs = divmod(math.ceil(error.retry_after), 60) - embed = self.error_embed( - f"This command is on cooldown:\nPlease retry in {mins} minutes {secs} seconds.", - NEGATIVE_REPLIES - ) - await ctx.send(embed=embed, delete_after=7.5) - return - - if isinstance(error, commands.DisabledCommand): - await ctx.send(embed=self.error_embed("This command has been disabled.", NEGATIVE_REPLIES)) - return - - if isinstance(error, commands.NoPrivateMessage): - await ctx.send(embed=self.error_embed("This command can only be used in the server.", NEGATIVE_REPLIES)) - return - - if isinstance(error, commands.BadArgument): - self.revert_cooldown_counter(ctx.command, ctx.message) - embed = self.error_embed( - "The argument you provided was invalid: " - f"{error}\n\nUsage:\n```{ctx.prefix}{ctx.command} {ctx.command.signature}```" - ) - await ctx.send(embed=embed) - return - - if isinstance(error, commands.CheckFailure): - await ctx.send(embed=self.error_embed("You are not authorized to use this command.", NEGATIVE_REPLIES)) - return - - if isinstance(error, UserNotPlayingError): - await ctx.send("Game not found.") - return - - with push_scope() as scope: - scope.user = { - "id": ctx.author.id, - "username": str(ctx.author) - } - - scope.set_tag("command", ctx.command.qualified_name) - scope.set_tag("message_id", ctx.message.id) - scope.set_tag("channel_id", ctx.channel.id) - - scope.set_extra("full_message", ctx.message.content) - - if ctx.guild is not None: - scope.set_extra( - "jump_to", - f"https://discordapp.com/channels/{ctx.guild.id}/{ctx.channel.id}/{ctx.message.id}" - ) - - log.exception(f"Unhandled command error: {str(error)}", exc_info=error) - - -def setup(bot: commands.Bot) -> None: - """Error handler Cog load.""" - bot.add_cog(CommandErrorHandler(bot)) -- cgit v1.2.3