From 019c2cea32ae4fcc8a257e7ac57ec223cd6ea788 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 11:02:57 +0300 Subject: (TicTacToe): Created initial empty cog with loading. --- bot/exts/evergreen/tic_tac_toe.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 bot/exts/evergreen/tic_tac_toe.py (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py new file mode 100644 index 00000000..d4c95728 --- /dev/null +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -0,0 +1,15 @@ +from discord.ext.commands import Cog + +from bot.bot import SeasonalBot + + +class TicTacToe(Cog): + """TicTacToe cog contains tic-tac-toe game commands.""" + + def __init__(self, bot: SeasonalBot): + self.bot = bot + + +def setup(bot: SeasonalBot) -> None: + """Load TicTacToe Cog.""" + bot.add_cog(TicTacToe(bot)) -- cgit v1.2.3 From b6dc1207fd63564bee9814ca46a955c1067fe5db Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 11:06:48 +0300 Subject: (TicTacToe): Created `Player` class --- bot/exts/evergreen/tic_tac_toe.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index d4c95728..1927e021 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -1,8 +1,16 @@ +import discord from discord.ext.commands import Cog from bot.bot import SeasonalBot +class Player: + """Class that contains information about player and functions that interact with player.""" + + def __init__(self, user: discord.User): + self.user = user + + class TicTacToe(Cog): """TicTacToe cog contains tic-tac-toe game commands.""" -- cgit v1.2.3 From 7d5103c157fa36ba7cc75fa67a0ee062633b4148 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 11:08:57 +0300 Subject: (TicTacToe): Created `Game` class --- bot/exts/evergreen/tic_tac_toe.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 1927e021..98d793ba 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -1,5 +1,7 @@ +import typing as t + import discord -from discord.ext.commands import Cog +from discord.ext.commands import Cog, Context from bot.bot import SeasonalBot @@ -11,6 +13,15 @@ class Player: self.user = user +class Game: + """Class that contains information and functions about Tic Tac Toe game.""" + + def __init__(self, channel: discord.TextChannel, players: t.List[discord.User], ctx: Context): + self.channel = channel + self.players = players + self.ctx = ctx + + class TicTacToe(Cog): """TicTacToe cog contains tic-tac-toe game commands.""" -- cgit v1.2.3 From 103ddfe12af937c50ece9bd651b5d4da8cb29079 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 11:48:34 +0300 Subject: (TicTacToe): Added `ctx` variable to `Player` class. --- bot/exts/evergreen/tic_tac_toe.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 98d793ba..376cb3e9 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -9,8 +9,9 @@ from bot.bot import SeasonalBot class Player: """Class that contains information about player and functions that interact with player.""" - def __init__(self, user: discord.User): + def __init__(self, user: discord.User, ctx: Context): self.user = user + self.ctx = ctx class Game: -- cgit v1.2.3 From 3362619c919b2094994439efa0dc0564d82594f0 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 11:49:33 +0300 Subject: (TicTacToe): Replaced `discord.User` with `Player` in `Game` class signature. --- bot/exts/evergreen/tic_tac_toe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 376cb3e9..2e21b43a 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -17,7 +17,7 @@ class Player: class Game: """Class that contains information and functions about Tic Tac Toe game.""" - def __init__(self, channel: discord.TextChannel, players: t.List[discord.User], ctx: Context): + def __init__(self, channel: discord.TextChannel, players: t.List[Player], ctx: Context): self.channel = channel self.players = players self.ctx = ctx -- cgit v1.2.3 From 04fe88be9b105522aec5185ab771a73c9ea1a818 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 11:51:19 +0300 Subject: (TicTacToe): Added new player-about variables to `Game` class. --- bot/exts/evergreen/tic_tac_toe.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 2e21b43a..0fa1902b 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -22,6 +22,12 @@ class Game: self.players = players self.ctx = ctx + self.current = self.players[0] + self.next = self.players[1] + + self.winner: t.Optional[Player] = None + self.loser: t.Optional[Player] = None + class TicTacToe(Cog): """TicTacToe cog contains tic-tac-toe game commands.""" -- cgit v1.2.3 From 3d92f7e907fcee767c1428a52bb503314b8f6b19 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 14:14:14 +0300 Subject: (TicTacToe): Added `get_confirmation` function to `Game` class to make sure that opponent want to play. --- bot/exts/evergreen/tic_tac_toe.py | 41 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 0fa1902b..7d82e084 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -1,9 +1,16 @@ +import asyncio import typing as t import discord from discord.ext.commands import Cog, Context from bot.bot import SeasonalBot +from bot.constants import Emojis + +CONFIRMATION_MESSAGE = ( + "{opponent}, {requester} want to play Tic-Tac-Toe against you. React to this message with " + f"{Emojis.confirmation} to accept or with {Emojis.decline} to decline." +) class Player: @@ -28,6 +35,40 @@ class Game: self.winner: t.Optional[Player] = None self.loser: t.Optional[Player] = None + async def get_confirmation(self) -> t.Tuple[bool, t.Optional[str]]: + """Ask does user want to play TicTacToe against requester. First player is always requester.""" + confirm_message = await self.ctx.send( + CONFIRMATION_MESSAGE.format( + opponent=self.players[1].user.mention, + requester=self.players[0].user.mention + ) + ) + await confirm_message.add_reaction(Emojis.confirmation) + await confirm_message.add_reaction(Emojis.decline) + + def confirm_check(reaction: discord.Reaction, user: discord.User) -> bool: + return ( + reaction.emoji in (Emojis.confirmation, Emojis.decline) + and reaction.message.id == confirm_message.id + and user == self.players[1].user + ) + + try: + reaction, user = await self.ctx.bot.wait_for( + "reaction_add", + timeout=60.0, + check=confirm_check + ) + except asyncio.TimeoutError: + await confirm_message.delete() + return False, "Running out of time... Cancelled game." + + await confirm_message.delete() + if reaction.emoji == Emojis.confirmation: + return True, None + else: + return False, "User declined" + class TicTacToe(Cog): """TicTacToe cog contains tic-tac-toe game commands.""" -- cgit v1.2.3 From cab121e93829258251154c0f00b1b9d8f5dca2e4 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 14:17:35 +0300 Subject: (TicTacToe): Created helper function `add_reactions` to `Game` class to add all number reactions to message. --- bot/exts/evergreen/tic_tac_toe.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 7d82e084..24021842 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -69,6 +69,11 @@ class Game: else: return False, "User declined" + async def add_reactions(self, msg: discord.Message) -> None: + """Add number emojis to message.""" + for nr in Emojis.number_emojis: + await msg.add_reaction(nr) + class TicTacToe(Cog): """TicTacToe cog contains tic-tac-toe game commands.""" -- cgit v1.2.3 From fa5ba85d3c9da611c10412cc2ee75eeff3a434f9 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 14:18:33 +0300 Subject: (TicTacToe): Added new variable `games` to `TicTacToe` cog. --- bot/exts/evergreen/tic_tac_toe.py | 1 + 1 file changed, 1 insertion(+) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 24021842..28f48e11 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -80,6 +80,7 @@ class TicTacToe(Cog): def __init__(self, bot: SeasonalBot): self.bot = bot + self.games: t.List[Game] = [] def setup(bot: SeasonalBot) -> None: -- cgit v1.2.3 From 88762008d4fc36744f1dc5507a90015a37a0a9eb Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 14:39:01 +0300 Subject: (TicTacToe): Created check `is_channel_free`. --- bot/exts/evergreen/tic_tac_toe.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 28f48e11..301cb9ff 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -2,7 +2,7 @@ import asyncio import typing as t import discord -from discord.ext.commands import Cog, Context +from discord.ext.commands import Cog, Context, check from bot.bot import SeasonalBot from bot.constants import Emojis @@ -82,6 +82,13 @@ class TicTacToe(Cog): self.bot = bot self.games: t.List[Game] = [] + @staticmethod + def is_channel_free() -> t.Callable: + """Check is channel where command will be invoked free.""" + async def predicate(ctx: Context) -> bool: + return all(game.channel != ctx.channel for game in ctx.cog.games) + return check(predicate) + def setup(bot: SeasonalBot) -> None: """Load TicTacToe Cog.""" -- cgit v1.2.3 From 88a121db968de07880037ffcc05462ef65cfaa2e Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 17:02:16 +0300 Subject: (TicTacToe): Created check `is_requester_free`. --- bot/exts/evergreen/tic_tac_toe.py | 7 +++++++ 1 file changed, 7 insertions(+) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 301cb9ff..90f916ef 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -89,6 +89,13 @@ class TicTacToe(Cog): return all(game.channel != ctx.channel for game in ctx.cog.games) return check(predicate) + @staticmethod + def is_requester_free() -> t.Callable: + """Check is requester not already in any game.""" + async def predicate(ctx: Context) -> bool: + return all(ctx.author not in (player.user for player in game.players) for game in ctx.cog.games) + return check(predicate) + def setup(bot: SeasonalBot) -> None: """Load TicTacToe Cog.""" -- cgit v1.2.3 From 4a97fcd91d610da69d9dd4b75ca2bf80a094ae61 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 17:05:38 +0300 Subject: (TicTacToe): Created new class variable `over` to `Game`, added over check to cog checks. --- bot/exts/evergreen/tic_tac_toe.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 90f916ef..2795f94a 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -34,6 +34,7 @@ class Game: self.winner: t.Optional[Player] = None self.loser: t.Optional[Player] = None + self.over = False async def get_confirmation(self) -> t.Tuple[bool, t.Optional[str]]: """Ask does user want to play TicTacToe against requester. First player is always requester.""" @@ -60,6 +61,7 @@ class Game: check=confirm_check ) except asyncio.TimeoutError: + self.over = True await confirm_message.delete() return False, "Running out of time... Cancelled game." @@ -67,6 +69,7 @@ class Game: if reaction.emoji == Emojis.confirmation: return True, None else: + self.over = True return False, "User declined" async def add_reactions(self, msg: discord.Message) -> None: @@ -86,14 +89,16 @@ class TicTacToe(Cog): def is_channel_free() -> t.Callable: """Check is channel where command will be invoked free.""" async def predicate(ctx: Context) -> bool: - return all(game.channel != ctx.channel for game in ctx.cog.games) + return all(game.channel != ctx.channel for game in ctx.cog.games if not game.over) return check(predicate) @staticmethod def is_requester_free() -> t.Callable: """Check is requester not already in any game.""" async def predicate(ctx: Context) -> bool: - return all(ctx.author not in (player.user for player in game.players) for game in ctx.cog.games) + return all( + ctx.author not in (player.user for player in game.players) for game in ctx.cog.games if not game.over + ) return check(predicate) -- cgit v1.2.3 From 85750308db80a56b539004d935c3032e96c4947f Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 17:16:08 +0300 Subject: (TicTacToe): Moved checks to outside of class, created initial tic tac toe command. --- bot/exts/evergreen/tic_tac_toe.py | 46 +++++++++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 16 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 2795f94a..1a906535 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -2,7 +2,7 @@ import asyncio import typing as t import discord -from discord.ext.commands import Cog, Context, check +from discord.ext.commands import Cog, Context, check, command, guild_only from bot.bot import SeasonalBot from bot.constants import Emojis @@ -78,6 +78,22 @@ class Game: await msg.add_reaction(nr) +def is_channel_free() -> t.Callable: + """Check is channel where command will be invoked free.""" + async def predicate(ctx: Context) -> bool: + return all(game.channel != ctx.channel for game in ctx.cog.games if not game.over) + return check(predicate) + + +def is_requester_free() -> t.Callable: + """Check is requester not already in any game.""" + async def predicate(ctx: Context) -> bool: + return all( + ctx.author not in (player.user for player in game.players) for game in ctx.cog.games if not game.over + ) + return check(predicate) + + class TicTacToe(Cog): """TicTacToe cog contains tic-tac-toe game commands.""" @@ -85,21 +101,19 @@ class TicTacToe(Cog): self.bot = bot self.games: t.List[Game] = [] - @staticmethod - def is_channel_free() -> t.Callable: - """Check is channel where command will be invoked free.""" - async def predicate(ctx: Context) -> bool: - return all(game.channel != ctx.channel for game in ctx.cog.games if not game.over) - return check(predicate) - - @staticmethod - def is_requester_free() -> t.Callable: - """Check is requester not already in any game.""" - async def predicate(ctx: Context) -> bool: - return all( - ctx.author not in (player.user for player in game.players) for game in ctx.cog.games if not game.over - ) - return check(predicate) + @guild_only() + @is_channel_free() + @is_requester_free() + @command(name="tictactoe", aliases=("ttt",)) + async def tic_tac_toe(self, ctx: Context, opponent: discord.User) -> None: + """Tic Tac Toe game. Play agains friends. Use reactions to add your mark to field.""" + game = Game( + ctx.channel, + [Player(ctx.author, ctx), Player(opponent, ctx)], + ctx + ) + self.games.append(game) + await game.get_confirmation() def setup(bot: SeasonalBot) -> None: -- cgit v1.2.3 From 7a7385bf222c24f78356ea46331ae5d4abddb65a Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 19:33:05 +0300 Subject: (Constants, TicTacToe): Fixed number emojis contants, created helper function `send_board` to `Game` class. --- bot/constants.py | 18 +++++++++--------- bot/exts/evergreen/tic_tac_toe.py | 23 +++++++++++++++++++++-- 2 files changed, 30 insertions(+), 11 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/constants.py b/bot/constants.py index d43cf4d3..a8dd03e6 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -120,15 +120,15 @@ class Emojis: # TicTacToe Emojis number_emojis = { - 1: "\u0031", - 2: "\u0032", - 3: "\u0033", - 4: "\u0034", - 5: "\u0035", - 6: "\u0036", - 7: "\u0037", - 8: "\u0038", - 9: "\u0039" + 1: "\u0031\ufe0f\u20e3", + 2: "\u0032\ufe0f\u20e3", + 3: "\u0033\ufe0f\u20e3", + 4: "\u0034\ufe0f\u20e3", + 5: "\u0035\ufe0f\u20e3", + 6: "\u0036\ufe0f\u20e3", + 7: "\u0037\ufe0f\u20e3", + 8: "\u0038\ufe0f\u20e3", + 9: "\u0039\ufe0f\u20e3" } confirmation = "\u2705" decline = "\u274c" diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 1a906535..55bbb7be 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -28,6 +28,11 @@ class Game: self.channel = channel self.players = players self.ctx = ctx + self.board = [ + [Emojis.number_emojis[1], Emojis.number_emojis[2], Emojis.number_emojis[3]], + [Emojis.number_emojis[4], Emojis.number_emojis[5], Emojis.number_emojis[6]], + [Emojis.number_emojis[7], Emojis.number_emojis[8], Emojis.number_emojis[9]] + ] self.current = self.players[0] self.next = self.players[1] @@ -74,9 +79,18 @@ class Game: async def add_reactions(self, msg: discord.Message) -> None: """Add number emojis to message.""" - for nr in Emojis.number_emojis: + for nr in Emojis.number_emojis.values(): await msg.add_reaction(nr) + async def send_board(self) -> discord.Message: + """Send board and return it's message.""" + msg = "" + for line in self.board: + msg += " ".join(line) + msg += "\n" + message = await self.ctx.send(msg) + return message + def is_channel_free() -> t.Callable: """Check is channel where command will be invoked free.""" @@ -113,7 +127,12 @@ class TicTacToe(Cog): ctx ) self.games.append(game) - await game.get_confirmation() + confirmed, msg = await game.get_confirmation() + + if not confirmed: + if msg: + await ctx.send(msg) + return def setup(bot: SeasonalBot) -> None: -- cgit v1.2.3 From 0af8726612f53ce6e52fbd636e0709c06d78415d Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 19:36:32 +0300 Subject: (TicTacToe): Added check is opponent free when request playing. --- bot/exts/evergreen/tic_tac_toe.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 55bbb7be..01417f25 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -121,6 +121,11 @@ class TicTacToe(Cog): @command(name="tictactoe", aliases=("ttt",)) async def tic_tac_toe(self, ctx: Context, opponent: discord.User) -> None: """Tic Tac Toe game. Play agains friends. Use reactions to add your mark to field.""" + if not all( + opponent not in (player.user for player in g.players) for g in ctx.cog.games if not g.over + ): + await ctx.send("Opponent is already in game.") + return game = Game( ctx.channel, [Player(ctx.author, ctx), Player(opponent, ctx)], -- cgit v1.2.3 From 89e963b08a6348213d0585f17de4e18441426eb4 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 19:37:53 +0300 Subject: (TicTacToe): Added `symbol` to `Player` class. --- bot/exts/evergreen/tic_tac_toe.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 01417f25..bb8f8dac 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -16,9 +16,10 @@ CONFIRMATION_MESSAGE = ( class Player: """Class that contains information about player and functions that interact with player.""" - def __init__(self, user: discord.User, ctx: Context): + def __init__(self, user: discord.User, ctx: Context, symbol: str): self.user = user self.ctx = ctx + self.symbol = symbol class Game: -- cgit v1.2.3 From 65c496169259c79f537d8aa38d21eb32feb817c2 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 19:38:48 +0300 Subject: (TicTacToe): Added `symbol` to player class creation in `tictactoe` command. --- bot/exts/evergreen/tic_tac_toe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index bb8f8dac..1873216d 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -129,7 +129,7 @@ class TicTacToe(Cog): return game = Game( ctx.channel, - [Player(ctx.author, ctx), Player(opponent, ctx)], + [Player(ctx.author, ctx, Emojis.x), Player(opponent, ctx, Emojis.o)], ctx ) self.games.append(game) -- cgit v1.2.3 From 7c930b7bc003fecd42aef58384d33708aa1d4bfa Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 9 Apr 2020 13:28:11 +0300 Subject: (TicTacToe): Removed unnecessary variable creation in `Game.send_board`. --- bot/exts/evergreen/tic_tac_toe.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 1873216d..ea503e83 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -89,8 +89,7 @@ class Game: for line in self.board: msg += " ".join(line) msg += "\n" - message = await self.ctx.send(msg) - return message + return await self.ctx.send(msg) def is_channel_free() -> t.Callable: -- cgit v1.2.3 From b4fa0b421b7949f0aea37e1eede68c0ba2432918 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 9 Apr 2020 13:36:01 +0300 Subject: (TicTacToe): Created function `Game.play`. --- bot/exts/evergreen/tic_tac_toe.py | 6 ++++++ 1 file changed, 6 insertions(+) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index ea503e83..260c8c2f 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -91,6 +91,12 @@ class Game: msg += "\n" return await self.ctx.send(msg) + async def play(self) -> None: + """Start and handle game.""" + await self.ctx.send("It's time for game! Let's begin.") + board = await self.send_board() + await self.add_reactions(board) + def is_channel_free() -> t.Callable: """Check is channel where command will be invoked free.""" -- cgit v1.2.3 From ed7bbbcb635dcc6bf5e4028cc6fd1bc4abfe97a2 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 9 Apr 2020 17:14:23 +0300 Subject: (TicTacToe): Redesigned board system, applied it's changes + created new function to `Player` class: `get_move`. --- bot/exts/evergreen/tic_tac_toe.py | 47 ++++++++++++++++++++++++++++++++------- 1 file changed, 39 insertions(+), 8 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 260c8c2f..1df4571b 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -21,6 +21,27 @@ class Player: self.ctx = ctx self.symbol = symbol + async def get_move(self, board: t.Dict[int, str], msg: discord.Message) -> t.Tuple[bool, t.Optional[int]]: + """ + Get move from user. + + Return is timeout reached and position of field what user will fill when timeout don't reach. + """ + def check_for_move(r: discord.Reaction, u: discord.User) -> bool: + return ( + u.id == self.user.id + and msg.id == r.message.id + and r.emoji in board.values() + and r.emoji in Emojis.number_emojis.values() + ) + + try: + react, = await self.ctx.bot.wait_for('reaction_add', timeout=120.0, check=check_for_move) + except asyncio.TimeoutError: + return True, None + else: + return False, Emojis.number_emojis.keys()[Emojis.number_emojis.values().index(react.emoji)] + class Game: """Class that contains information and functions about Tic Tac Toe game.""" @@ -29,11 +50,17 @@ class Game: self.channel = channel self.players = players self.ctx = ctx - self.board = [ - [Emojis.number_emojis[1], Emojis.number_emojis[2], Emojis.number_emojis[3]], - [Emojis.number_emojis[4], Emojis.number_emojis[5], Emojis.number_emojis[6]], - [Emojis.number_emojis[7], Emojis.number_emojis[8], Emojis.number_emojis[9]] - ] + self.board = { + 1: Emojis.number_emojis[1], + 2: Emojis.number_emojis[2], + 3: Emojis.number_emojis[3], + 4: Emojis.number_emojis[4], + 5: Emojis.number_emojis[5], + 6: Emojis.number_emojis[6], + 7: Emojis.number_emojis[7], + 8: Emojis.number_emojis[8], + 9: Emojis.number_emojis[9] + } self.current = self.players[0] self.next = self.players[1] @@ -86,9 +113,13 @@ class Game: async def send_board(self) -> discord.Message: """Send board and return it's message.""" msg = "" - for line in self.board: - msg += " ".join(line) - msg += "\n" + c = 0 + for line in self.board.values(): + msg += f"{line} " + c += 1 + if c == 3: + msg += "\n" + c = 0 return await self.ctx.send(msg) async def play(self) -> None: -- cgit v1.2.3 From c62669579d566e0c396bccf9798344d74b77aefa Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 9 Apr 2020 18:55:14 +0300 Subject: (TicTacToe): Created `edit_board` function to `Game`, made fixes to `Player.get_move`, implemented `Game.play` functionality. --- bot/exts/evergreen/tic_tac_toe.py | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 1df4571b..b9e44220 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -36,11 +36,11 @@ class Player: ) try: - react, = await self.ctx.bot.wait_for('reaction_add', timeout=120.0, check=check_for_move) + react, _ = await self.ctx.bot.wait_for('reaction_add', timeout=120.0, check=check_for_move) except asyncio.TimeoutError: return True, None else: - return False, Emojis.number_emojis.keys()[Emojis.number_emojis.values().index(react.emoji)] + return False, list(Emojis.number_emojis.keys())[list(Emojis.number_emojis.values()).index(react.emoji)] class Game: @@ -122,12 +122,38 @@ class Game: c = 0 return await self.ctx.send(msg) + async def edit_board(self, message: discord.Message) -> None: + """Edit Tic Tac Toe game board in message.""" + msg = "" + c = 0 + for line in self.board.values(): + msg += f"{line} " + c += 1 + if c == 3: + msg += "\n" + c = 0 + await message.edit(content=msg) + async def play(self) -> None: """Start and handle game.""" await self.ctx.send("It's time for game! Let's begin.") board = await self.send_board() await self.add_reactions(board) + for _ in range(9): + announce = await self.ctx.send(f"{self.current.user.mention}, your turn! React to emoji to mark field.") + timeout, pos = await self.current.get_move(self.board, board) + await announce.delete() + if timeout: + await self.ctx.send(f"{self.current.user.mention} ran out of time. Canceling game.") + self.over = True + return + self.board[pos] = self.current.symbol + await self.edit_board(board) + await board.clear_reaction(Emojis.number_emojis[pos]) + self.current, self.next = self.next, self.current + self.over = True + def is_channel_free() -> t.Callable: """Check is channel where command will be invoked free.""" @@ -175,6 +201,7 @@ class TicTacToe(Cog): if msg: await ctx.send(msg) return + await game.play() def setup(bot: SeasonalBot) -> None: -- cgit v1.2.3 From 94a39f3f7f1b19b678572d2d494f07535e52a32f Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 9 Apr 2020 19:05:33 +0300 Subject: (TicTacToe): Created helper function `Game.check_for_win`. --- bot/exts/evergreen/tic_tac_toe.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index b9e44220..970f359d 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -134,6 +134,24 @@ class Game: c = 0 await message.edit(content=msg) + async def check_for_win(self) -> bool: + """Check from board, is any player won game.""" + if ( + # Horizontal + self.board[1] == self.board[2] == self.board[3] + or self.board[4] == self.board[5] == self.board[6] + or self.board[7] == self.board[8] == self.board[9] + # Vertical + or self.board[1] == self.board[4] == self.board[7] + or self.board[2] == self.board[5] == self.board[8] + or self.board[3] == self.board[6] == self.board[9] + # Diagonal + or self.board[1] == self.board[5] == self.board[9] + or self.board[3] == self.board[5] == self.board[7] + ): + return True + return False + async def play(self) -> None: """Start and handle game.""" await self.ctx.send("It's time for game! Let's begin.") -- cgit v1.2.3 From 310ac58be883f07a9a410ce19c6b0dd8ffcf09bd Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 9 Apr 2020 19:10:24 +0300 Subject: (TicTacToe): Added winner check to `Game.play`. --- bot/exts/evergreen/tic_tac_toe.py | 5 +++++ 1 file changed, 5 insertions(+) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 970f359d..ed612182 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -169,7 +169,12 @@ class Game: self.board[pos] = self.current.symbol await self.edit_board(board) await board.clear_reaction(Emojis.number_emojis[pos]) + if await self.check_for_win(): + await self.ctx.send(f":tada: {self.current.user.mention} is won this game! :tada:") + await board.clear_reactions() + break self.current, self.next = self.next, self.current + self.over = True -- cgit v1.2.3 From 235e8d9b9b3360e9ebc2a49c2ba0c4006fe5ae9f Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 9 Apr 2020 19:11:24 +0300 Subject: (TicTacToe): Removed unnecessary variable `channel` from `Game`. --- bot/exts/evergreen/tic_tac_toe.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index ed612182..e8aafa07 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -46,8 +46,7 @@ class Player: class Game: """Class that contains information and functions about Tic Tac Toe game.""" - def __init__(self, channel: discord.TextChannel, players: t.List[Player], ctx: Context): - self.channel = channel + def __init__(self, players: t.List[Player], ctx: Context): self.players = players self.ctx = ctx self.board = { @@ -213,7 +212,6 @@ class TicTacToe(Cog): await ctx.send("Opponent is already in game.") return game = Game( - ctx.channel, [Player(ctx.author, ctx, Emojis.x), Player(opponent, ctx, Emojis.o)], ctx ) -- cgit v1.2.3 From b15c637568f33b2600e822a5aff54a90ea02f91f Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 9 Apr 2020 19:14:25 +0300 Subject: (TicTacToe): Added check that don't allow you to play against yourself to `tictactoe` command. --- bot/exts/evergreen/tic_tac_toe.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index e8aafa07..f1e0834b 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -206,6 +206,9 @@ class TicTacToe(Cog): @command(name="tictactoe", aliases=("ttt",)) async def tic_tac_toe(self, ctx: Context, opponent: discord.User) -> None: """Tic Tac Toe game. Play agains friends. Use reactions to add your mark to field.""" + if opponent == ctx.author: + await ctx.send("You can't play against yourself.") + return if not all( opponent not in (player.user for player in g.players) for g in ctx.cog.games if not g.over ): -- cgit v1.2.3 From a2963c4b2cf3f81e845b5e223a1b4de6feac0fda Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 9 Apr 2020 19:15:55 +0300 Subject: (TicTacToe): Added loser and winner attaching to `Game.play` winning handling. --- bot/exts/evergreen/tic_tac_toe.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index f1e0834b..27bdbda1 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -169,6 +169,8 @@ class Game: await self.edit_board(board) await board.clear_reaction(Emojis.number_emojis[pos]) if await self.check_for_win(): + self.winner = self.current + self.loser = self.next await self.ctx.send(f":tada: {self.current.user.mention} is won this game! :tada:") await board.clear_reactions() break -- cgit v1.2.3 From b362d24b225e5d1d101e784d84227d563b226c64 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 9 Apr 2020 19:27:37 +0300 Subject: (TicTacToe): Added new variable to `Game` class: `canceled`, applied it's changes and created new `tictactoe history` command. --- bot/exts/evergreen/tic_tac_toe.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 27bdbda1..c1f780cf 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -2,10 +2,11 @@ import asyncio import typing as t import discord -from discord.ext.commands import Cog, Context, check, command, guild_only +from discord.ext.commands import Cog, Context, check, group, guild_only from bot.bot import SeasonalBot from bot.constants import Emojis +from bot.utils.pagination import LinePaginator CONFIRMATION_MESSAGE = ( "{opponent}, {requester} want to play Tic-Tac-Toe against you. React to this message with " @@ -67,6 +68,7 @@ class Game: self.winner: t.Optional[Player] = None self.loser: t.Optional[Player] = None self.over = False + self.canceled = False async def get_confirmation(self) -> t.Tuple[bool, t.Optional[str]]: """Ask does user want to play TicTacToe against requester. First player is always requester.""" @@ -94,6 +96,7 @@ class Game: ) except asyncio.TimeoutError: self.over = True + self.canceled = True await confirm_message.delete() return False, "Running out of time... Cancelled game." @@ -102,6 +105,7 @@ class Game: return True, None else: self.over = True + self.canceled = True return False, "User declined" async def add_reactions(self, msg: discord.Message) -> None: @@ -164,6 +168,7 @@ class Game: if timeout: await self.ctx.send(f"{self.current.user.mention} ran out of time. Canceling game.") self.over = True + self.canceled = True return self.board[pos] = self.current.symbol await self.edit_board(board) @@ -205,7 +210,7 @@ class TicTacToe(Cog): @guild_only() @is_channel_free() @is_requester_free() - @command(name="tictactoe", aliases=("ttt",)) + @group(name="tictactoe", aliases=("ttt",), invoke_without_command=True) async def tic_tac_toe(self, ctx: Context, opponent: discord.User) -> None: """Tic Tac Toe game. Play agains friends. Use reactions to add your mark to field.""" if opponent == ctx.author: @@ -229,6 +234,23 @@ class TicTacToe(Cog): return await game.play() + @tic_tac_toe.group(name="history", aliases=("log",), invoke_without_command=True) + async def tic_tac_toe_logs(self, ctx: Context) -> None: + """Show most recent tic-tac-toe games.""" + if len(self.games) < 1: + await ctx.send("No recent games.") + return + await LinePaginator.paginate( + ( + f"**#{i+1}**: {game.winner.user.mention} :trophy: vs {game.loser.user.mention}" + for i, game in enumerate(self.games) + if game.over + and not game.canceled + ), + ctx, + discord.Embed(title="Most recent Tic Tac Toe games") + ) + def setup(bot: SeasonalBot) -> None: """Load TicTacToe Cog.""" -- cgit v1.2.3 From 2d22d7b791416adea1052175da386a196d19d1a8 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 9 Apr 2020 19:28:44 +0300 Subject: (TicTacToe): Added way to send board to custom channel in `Game.send_board`. --- bot/exts/evergreen/tic_tac_toe.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index c1f780cf..87a845f1 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -113,7 +113,7 @@ class Game: for nr in Emojis.number_emojis.values(): await msg.add_reaction(nr) - async def send_board(self) -> discord.Message: + async def send_board(self, channel: t.Optional[discord.TextChannel] = None) -> discord.Message: """Send board and return it's message.""" msg = "" c = 0 @@ -123,6 +123,8 @@ class Game: if c == 3: msg += "\n" c = 0 + if channel: + return await channel.send(msg) return await self.ctx.send(msg) async def edit_board(self, message: discord.Message) -> None: -- cgit v1.2.3 From 14e5cadcbd8344cdba0b410567525f7518ac0a7a Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 9 Apr 2020 19:39:07 +0300 Subject: (TicTacToe): Created command to show game information `tictactoe history show `. --- bot/exts/evergreen/tic_tac_toe.py | 10 ++++++++++ 1 file changed, 10 insertions(+) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 87a845f1..a9971ad2 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -253,6 +253,16 @@ class TicTacToe(Cog): discord.Embed(title="Most recent Tic Tac Toe games") ) + @tic_tac_toe_logs.command(name="show", aliases=("s",)) + async def show_tic_tac_toe_board(self, ctx: Context, game_id: int) -> None: + """View game board by ID (ID is possible to get by `.tictactoe history`).""" + if len(self.games) < game_id: + await ctx.send("Game don't exist.") + return + game = self.games[game_id - 1] + await ctx.send(f"{game.winner.user} :trophy: vs {game.loser.user}") + await game.send_board(ctx.channel) + def setup(bot: SeasonalBot) -> None: """Load TicTacToe Cog.""" -- cgit v1.2.3 From 5e13a3e2a7b6ed4b93e306784fae8443bf2cab33 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 12 Apr 2020 18:41:37 +0300 Subject: (TicTacToe): Created `AI` class for Tic Tac Toe against computer playing. --- bot/exts/evergreen/tic_tac_toe.py | 47 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index a9971ad2..b306e803 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -1,4 +1,5 @@ import asyncio +import random import typing as t import discord @@ -44,6 +45,52 @@ class Player: return False, list(Emojis.number_emojis.keys())[list(Emojis.number_emojis.values()).index(react.emoji)] +class AI: + """Tic Tac Toe AI class for against computer gaming.""" + + def __init__(self, symbol: str): + self.symbol = symbol + + async def check_win(self, board: t.Dict[int, str]) -> bool: + """Check does this move will result game end.""" + if ( + # Horizontal + board[1] == board[2] == board[3] + or board[4] == board[5] == board[6] + or board[7] == board[8] == board[9] + # Vertical + or board[1] == board[4] == board[7] + or board[2] == board[5] == board[8] + or board[3] == board[6] == board[9] + # Diagonal + or board[1] == board[5] == board[9] + or board[3] == board[5] == board[7] + ): + return True + return False + + async def get_move(self, board: t.Dict[int, str], _: discord.Message) -> t.Tuple[bool, int]: + """Get move from AI. AI use Minimax strategy.""" + possible_moves = [i for i, emoji in board.items() if emoji in Emojis.number_emojis] + + for symbol in (Emojis.x, Emojis.o): + for move in possible_moves: + board_copy = board.copy() + board_copy[move] = symbol + if self.check_win(board_copy): + return False, move + + open_corners = [i for i in possible_moves if i in (1, 3, 7, 9)] + if len(open_corners) > 0: + return False, random.choice(open_corners) + + if 5 in possible_moves: + return False, 5 + + open_edges = [i for i in possible_moves if i in (2, 4, 6, 8)] + return False, random.choice(open_edges) + + class Game: """Class that contains information and functions about Tic Tac Toe game.""" -- cgit v1.2.3 From 85ed1469cefd408233fcdb84643089b4b6a36d88 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 12 Apr 2020 19:14:18 +0300 Subject: (TicTacToe): Implemented AI to game and cog. --- bot/exts/evergreen/tic_tac_toe.py | 82 ++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 28 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index b306e803..fc8edd70 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -71,13 +71,13 @@ class AI: async def get_move(self, board: t.Dict[int, str], _: discord.Message) -> t.Tuple[bool, int]: """Get move from AI. AI use Minimax strategy.""" - possible_moves = [i for i, emoji in board.items() if emoji in Emojis.number_emojis] + possible_moves = [i for i, emoji in board.items() if emoji in list(Emojis.number_emojis.values())] for symbol in (Emojis.x, Emojis.o): for move in possible_moves: board_copy = board.copy() board_copy[move] = symbol - if self.check_win(board_copy): + if await self.check_win(board_copy): return False, move open_corners = [i for i in possible_moves if i in (1, 3, 7, 9)] @@ -94,7 +94,7 @@ class AI: class Game: """Class that contains information and functions about Tic Tac Toe game.""" - def __init__(self, players: t.List[Player], ctx: Context): + def __init__(self, players: t.List[t.Union[Player, AI]], ctx: Context): self.players = players self.ctx = ctx self.board = { @@ -112,10 +112,11 @@ class Game: self.current = self.players[0] self.next = self.players[1] - self.winner: t.Optional[Player] = None - self.loser: t.Optional[Player] = None + self.winner: t.Optional[t.Union[Player, AI]] = None + self.loser: t.Optional[t.Union[Player, AI]] = None self.over = False self.canceled = False + self.draw = False async def get_confirmation(self) -> t.Tuple[bool, t.Optional[str]]: """Ask does user want to play TicTacToe against requester. First player is always requester.""" @@ -211,9 +212,11 @@ class Game: await self.add_reactions(board) for _ in range(9): - announce = await self.ctx.send(f"{self.current.user.mention}, your turn! React to emoji to mark field.") + if isinstance(self.current, Player): + announce = await self.ctx.send(f"{self.current.user.mention}, your turn! React to emoji to mark field.") timeout, pos = await self.current.get_move(self.board, board) - await announce.delete() + if isinstance(self.current, Player): + await announce.delete() if timeout: await self.ctx.send(f"{self.current.user.mention} ran out of time. Canceling game.") self.over = True @@ -225,11 +228,16 @@ class Game: if await self.check_for_win(): self.winner = self.current self.loser = self.next - await self.ctx.send(f":tada: {self.current.user.mention} is won this game! :tada:") + await self.ctx.send( + f":tada: {self.current.user.mention if isinstance(self.current, Player) else 'AI'} " + f"is won this game! :tada:" + ) await board.clear_reactions() break self.current, self.next = self.next, self.current - + if not self.winner: + self.draw = True + await self.ctx.send("It's DRAW!") self.over = True @@ -260,27 +268,34 @@ class TicTacToe(Cog): @is_channel_free() @is_requester_free() @group(name="tictactoe", aliases=("ttt",), invoke_without_command=True) - async def tic_tac_toe(self, ctx: Context, opponent: discord.User) -> None: - """Tic Tac Toe game. Play agains friends. Use reactions to add your mark to field.""" + async def tic_tac_toe(self, ctx: Context, opponent: t.Optional[discord.User]) -> None: + """Tic Tac Toe game. Play agains friends or AI. Use reactions to add your mark to field.""" if opponent == ctx.author: await ctx.send("You can't play against yourself.") return - if not all( + if opponent is not None and not all( opponent not in (player.user for player in g.players) for g in ctx.cog.games if not g.over ): await ctx.send("Opponent is already in game.") return - game = Game( - [Player(ctx.author, ctx, Emojis.x), Player(opponent, ctx, Emojis.o)], - ctx - ) + if opponent is None: + game = Game( + [Player(ctx.author, ctx, Emojis.x), AI(Emojis.o)], + ctx + ) + else: + game = Game( + [Player(ctx.author, ctx, Emojis.x), Player(opponent, ctx, Emojis.o)], + ctx + ) self.games.append(game) - confirmed, msg = await game.get_confirmation() + if opponent is not None: + confirmed, msg = await game.get_confirmation() - if not confirmed: - if msg: - await ctx.send(msg) - return + if not confirmed: + if msg: + await ctx.send(msg) + return await game.play() @tic_tac_toe.group(name="history", aliases=("log",), invoke_without_command=True) @@ -289,13 +304,21 @@ class TicTacToe(Cog): if len(self.games) < 1: await ctx.send("No recent games.") return + log_games = [] + for i, game in enumerate(self.games): + if game.over and not game.canceled: + if game.draw: + log_games.append( + f"**#{i+1}**: {game.players[0].user.mention} vs " + f"{game.players[1].user.mention if isinstance(game.players[1], Player) else 'AI'} (draw)" + ) + else: + log_games.append( + f"**#{i+1}**: {game.winner.user.mention if isinstance(game.winner, Player) else 'AI'} :trophy: " + f"vs {game.loser.user.mention if isinstance(game.loser, Player) else 'AI'}" + ) await LinePaginator.paginate( - ( - f"**#{i+1}**: {game.winner.user.mention} :trophy: vs {game.loser.user.mention}" - for i, game in enumerate(self.games) - if game.over - and not game.canceled - ), + log_games, ctx, discord.Embed(title="Most recent Tic Tac Toe games") ) @@ -307,7 +330,10 @@ class TicTacToe(Cog): await ctx.send("Game don't exist.") return game = self.games[game_id - 1] - await ctx.send(f"{game.winner.user} :trophy: vs {game.loser.user}") + await ctx.send( + f"{game.winner.user if isinstance(game.winner, Player) else 'AI'} " + f":trophy: vs {game.loser.user if isinstance(game.winner, Player) else 'AI'}" + ) await game.send_board(ctx.channel) -- cgit v1.2.3 From af6357c444cd55fb203cf9696f9b568b27ccd666 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 12 Apr 2020 19:17:15 +0300 Subject: (TicTacToe): Setting winning as priority in AI instead blocking opponent. --- bot/exts/evergreen/tic_tac_toe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index fc8edd70..72eb2090 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -73,7 +73,7 @@ class AI: """Get move from AI. AI use Minimax strategy.""" possible_moves = [i for i, emoji in board.items() if emoji in list(Emojis.number_emojis.values())] - for symbol in (Emojis.x, Emojis.o): + for symbol in (Emojis.o, Emojis.x): for move in possible_moves: board_copy = board.copy() board_copy[move] = symbol -- cgit v1.2.3 From ddec3a15ffa37b775bc483910baefb4e0aba2f88 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 13 Apr 2020 08:14:42 +0300 Subject: (TicTacToe): Created new helper functions `display` to `Player` and `AI` class to avoid `if` checks in strings. --- bot/exts/evergreen/tic_tac_toe.py | 8 ++++++++ 1 file changed, 8 insertions(+) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 72eb2090..8d0c384b 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -44,6 +44,10 @@ class Player: else: return False, list(Emojis.number_emojis.keys())[list(Emojis.number_emojis.values()).index(react.emoji)] + async def display(self) -> str: + """Return mention of user.""" + return self.user.mention + class AI: """Tic Tac Toe AI class for against computer gaming.""" @@ -90,6 +94,10 @@ class AI: open_edges = [i for i in possible_moves if i in (2, 4, 6, 8)] return False, random.choice(open_edges) + def display(self) -> str: + """Return `AI` as user name.""" + return "AI" + class Game: """Class that contains information and functions about Tic Tac Toe game.""" -- cgit v1.2.3 From 494cb28681f2de44ff4f95b1b9fe4b1662239cb6 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Mon, 13 Apr 2020 08:19:10 +0300 Subject: (TicTacToe): Applied `Player.display()` and `AI.display()` to cog and `Game` class. --- bot/exts/evergreen/tic_tac_toe.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 8d0c384b..435c7af3 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -237,8 +237,7 @@ class Game: self.winner = self.current self.loser = self.next await self.ctx.send( - f":tada: {self.current.user.mention if isinstance(self.current, Player) else 'AI'} " - f"is won this game! :tada:" + f":tada: {self.current.display()} is won this game! :tada:" ) await board.clear_reactions() break @@ -317,13 +316,11 @@ class TicTacToe(Cog): if game.over and not game.canceled: if game.draw: log_games.append( - f"**#{i+1}**: {game.players[0].user.mention} vs " - f"{game.players[1].user.mention if isinstance(game.players[1], Player) else 'AI'} (draw)" + f"**#{i+1}**: {game.players[0].display()} vs {game.players[1].display()} (draw)" ) else: log_games.append( - f"**#{i+1}**: {game.winner.user.mention if isinstance(game.winner, Player) else 'AI'} :trophy: " - f"vs {game.loser.user.mention if isinstance(game.loser, Player) else 'AI'}" + f"**#{i+1}**: {game.winner.display()} :trophy: vs {game.loser.display()}" ) await LinePaginator.paginate( log_games, @@ -339,8 +336,7 @@ class TicTacToe(Cog): return game = self.games[game_id - 1] await ctx.send( - f"{game.winner.user if isinstance(game.winner, Player) else 'AI'} " - f":trophy: vs {game.loser.user if isinstance(game.winner, Player) else 'AI'}" + f"{game.winner.display()} :trophy: vs {game.loser.display()}" ) await game.send_board(ctx.channel) -- cgit v1.2.3 From 8df212d6ca6bb44668da57fa99bae78871f2e864 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 24 Sep 2020 17:33:06 +0300 Subject: Tictactoe: Use __str__ instead custom display method for user/AI name display --- bot/exts/evergreen/tic_tac_toe.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 435c7af3..74b04db8 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -44,7 +44,7 @@ class Player: else: return False, list(Emojis.number_emojis.keys())[list(Emojis.number_emojis.values()).index(react.emoji)] - async def display(self) -> str: + def __str__(self) -> str: """Return mention of user.""" return self.user.mention @@ -94,7 +94,7 @@ class AI: open_edges = [i for i in possible_moves if i in (2, 4, 6, 8)] return False, random.choice(open_edges) - def display(self) -> str: + def __str__(self) -> str: """Return `AI` as user name.""" return "AI" @@ -237,7 +237,7 @@ class Game: self.winner = self.current self.loser = self.next await self.ctx.send( - f":tada: {self.current.display()} is won this game! :tada:" + f":tada: {self.current} is won this game! :tada:" ) await board.clear_reactions() break @@ -316,11 +316,11 @@ class TicTacToe(Cog): if game.over and not game.canceled: if game.draw: log_games.append( - f"**#{i+1}**: {game.players[0].display()} vs {game.players[1].display()} (draw)" + f"**#{i+1}**: {game.players[0]} vs {game.players[1]} (draw)" ) else: log_games.append( - f"**#{i+1}**: {game.winner.display()} :trophy: vs {game.loser.display()}" + f"**#{i+1}**: {game.winner} :trophy: vs {game.loser}" ) await LinePaginator.paginate( log_games, @@ -336,7 +336,7 @@ class TicTacToe(Cog): return game = self.games[game_id - 1] await ctx.send( - f"{game.winner.display()} :trophy: vs {game.loser.display()}" + f"{game.winner} :trophy: vs {game.loser}" ) await game.send_board(ctx.channel) -- cgit v1.2.3 From 0a275c90bfd824b320dd36c9b8c5fd73a143ad72 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 24 Sep 2020 20:00:44 +0300 Subject: Tictactoe: Refactor board message content generation --- bot/exts/evergreen/tic_tac_toe.py | 37 +++++++++---------------------------- 1 file changed, 9 insertions(+), 28 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 74b04db8..16871070 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -169,31 +169,12 @@ class Game: for nr in Emojis.number_emojis.values(): await msg.add_reaction(nr) - async def send_board(self, channel: t.Optional[discord.TextChannel] = None) -> discord.Message: - """Send board and return it's message.""" - msg = "" - c = 0 - for line in self.board.values(): - msg += f"{line} " - c += 1 - if c == 3: - msg += "\n" - c = 0 - if channel: - return await channel.send(msg) - return await self.ctx.send(msg) - - async def edit_board(self, message: discord.Message) -> None: - """Edit Tic Tac Toe game board in message.""" - msg = "" - c = 0 - for line in self.board.values(): - msg += f"{line} " - c += 1 - if c == 3: - msg += "\n" - c = 0 - await message.edit(content=msg) + def format_board(self) -> str: + """Get formatted tic-tac-toe board for message.""" + board = list(self.board.values()) + return "\n".join( + (f"{board[line]} {board[line + 1]} {board[line + 2]}" for line in range(0, len(board), 3)) + ) async def check_for_win(self) -> bool: """Check from board, is any player won game.""" @@ -216,7 +197,7 @@ class Game: async def play(self) -> None: """Start and handle game.""" await self.ctx.send("It's time for game! Let's begin.") - board = await self.send_board() + board = await self.ctx.send(self.format_board()) await self.add_reactions(board) for _ in range(9): @@ -231,7 +212,7 @@ class Game: self.canceled = True return self.board[pos] = self.current.symbol - await self.edit_board(board) + await board.edit(content=self.format_board()) await board.clear_reaction(Emojis.number_emojis[pos]) if await self.check_for_win(): self.winner = self.current @@ -338,7 +319,7 @@ class TicTacToe(Cog): await ctx.send( f"{game.winner} :trophy: vs {game.loser}" ) - await game.send_board(ctx.channel) + await ctx.send(game.format_board()) def setup(bot: SeasonalBot) -> None: -- cgit v1.2.3 From e6ca49b4745a3c568da6adf3e8c670661b15443d Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 24 Sep 2020 20:04:30 +0300 Subject: Tictactoe: Document `get_confirmation` return value --- bot/exts/evergreen/tic_tac_toe.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 16871070..0c13964a 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -127,7 +127,13 @@ class Game: self.draw = False async def get_confirmation(self) -> t.Tuple[bool, t.Optional[str]]: - """Ask does user want to play TicTacToe against requester. First player is always requester.""" + """ + Ask does user want to play TicTacToe against requester. First player is always requester. + + This return tuple that have: + - first element boolean (is game accepted?) + - (optional, only when first element is False, otherwise None) reason for declining. + """ confirm_message = await self.ctx.send( CONFIRMATION_MESSAGE.format( opponent=self.players[1].user.mention, -- cgit v1.2.3 From fdc4cbd258a66f37371e427c57271b8abf8378e5 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 24 Sep 2020 20:11:25 +0300 Subject: Tictactoe: Remove duplicate functions --- bot/exts/evergreen/tic_tac_toe.py | 62 +++++++++++++++------------------------ 1 file changed, 24 insertions(+), 38 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 0c13964a..26df4e10 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -15,6 +15,25 @@ CONFIRMATION_MESSAGE = ( ) +def check_win(board: t.Dict[int, str]) -> bool: + """Check from board, is any player won game.""" + if ( + # Horizontal + board[1] == board[2] == board[3] + or board[4] == board[5] == board[6] + or board[7] == board[8] == board[9] + # Vertical + or board[1] == board[4] == board[7] + or board[2] == board[5] == board[8] + or board[3] == board[6] == board[9] + # Diagonal + or board[1] == board[5] == board[9] + or board[3] == board[5] == board[7] + ): + return True + return False + + class Player: """Class that contains information about player and functions that interact with player.""" @@ -55,24 +74,6 @@ class AI: def __init__(self, symbol: str): self.symbol = symbol - async def check_win(self, board: t.Dict[int, str]) -> bool: - """Check does this move will result game end.""" - if ( - # Horizontal - board[1] == board[2] == board[3] - or board[4] == board[5] == board[6] - or board[7] == board[8] == board[9] - # Vertical - or board[1] == board[4] == board[7] - or board[2] == board[5] == board[8] - or board[3] == board[6] == board[9] - # Diagonal - or board[1] == board[5] == board[9] - or board[3] == board[5] == board[7] - ): - return True - return False - async def get_move(self, board: t.Dict[int, str], _: discord.Message) -> t.Tuple[bool, int]: """Get move from AI. AI use Minimax strategy.""" possible_moves = [i for i, emoji in board.items() if emoji in list(Emojis.number_emojis.values())] @@ -81,7 +82,7 @@ class AI: for move in possible_moves: board_copy = board.copy() board_copy[move] = symbol - if await self.check_win(board_copy): + if check_win(board_copy): return False, move open_corners = [i for i in possible_moves if i in (1, 3, 7, 9)] @@ -144,6 +145,9 @@ class Game: await confirm_message.add_reaction(Emojis.decline) def confirm_check(reaction: discord.Reaction, user: discord.User) -> bool: + """ + Check is user who reacted user from who this was requested, message is confirm message and emoji is valid. + """ return ( reaction.emoji in (Emojis.confirmation, Emojis.decline) and reaction.message.id == confirm_message.id @@ -182,24 +186,6 @@ class Game: (f"{board[line]} {board[line + 1]} {board[line + 2]}" for line in range(0, len(board), 3)) ) - async def check_for_win(self) -> bool: - """Check from board, is any player won game.""" - if ( - # Horizontal - self.board[1] == self.board[2] == self.board[3] - or self.board[4] == self.board[5] == self.board[6] - or self.board[7] == self.board[8] == self.board[9] - # Vertical - or self.board[1] == self.board[4] == self.board[7] - or self.board[2] == self.board[5] == self.board[8] - or self.board[3] == self.board[6] == self.board[9] - # Diagonal - or self.board[1] == self.board[5] == self.board[9] - or self.board[3] == self.board[5] == self.board[7] - ): - return True - return False - async def play(self) -> None: """Start and handle game.""" await self.ctx.send("It's time for game! Let's begin.") @@ -220,7 +206,7 @@ class Game: self.board[pos] = self.current.symbol await board.edit(content=self.format_board()) await board.clear_reaction(Emojis.number_emojis[pos]) - if await self.check_for_win(): + if check_win(self.board): self.winner = self.current self.loser = self.next await self.ctx.send( -- cgit v1.2.3 From 88c5270cebb56c1cf84ea0a2bfb78cca6bf2252a Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 24 Sep 2020 20:14:09 +0300 Subject: Tictactoe: Document another check --- bot/exts/evergreen/tic_tac_toe.py | 3 +++ 1 file changed, 3 insertions(+) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 26df4e10..1bd20b70 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -49,6 +49,9 @@ class Player: Return is timeout reached and position of field what user will fill when timeout don't reach. """ def check_for_move(r: discord.Reaction, u: discord.User) -> bool: + """ + Check does user who reacted is user who we want, message is board message and emoji is in board values. + """ return ( u.id == self.user.id and msg.id == r.message.id -- cgit v1.2.3 From d7c215e723da334f3ef3927c173680307d7764ba Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Thu, 24 Sep 2020 20:17:47 +0300 Subject: Tictactoe: Fix docstrings formatting --- bot/exts/evergreen/tic_tac_toe.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 1bd20b70..20fbb9be 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -49,9 +49,7 @@ class Player: Return is timeout reached and position of field what user will fill when timeout don't reach. """ def check_for_move(r: discord.Reaction, u: discord.User) -> bool: - """ - Check does user who reacted is user who we want, message is board message and emoji is in board values. - """ + """Check does user who reacted is user who we want, message is board and emoji is in board values.""" return ( u.id == self.user.id and msg.id == r.message.id @@ -148,9 +146,7 @@ class Game: await confirm_message.add_reaction(Emojis.decline) def confirm_check(reaction: discord.Reaction, user: discord.User) -> bool: - """ - Check is user who reacted user from who this was requested, message is confirm message and emoji is valid. - """ + """Check is user who reacted from who this was requested, message is confirmation and emoji is valid.""" return ( reaction.emoji in (Emojis.confirmation, Emojis.decline) and reaction.message.id == confirm_message.id -- cgit v1.2.3 From fa7c5c68959b664c0b4d5d983cfaafc84d2759da Mon Sep 17 00:00:00 2001 From: xithrius Date: Fri, 8 Jan 2021 17:06:23 -0800 Subject: Corrected small spelling mistake. --- bot/exts/evergreen/tic_tac_toe.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 20fbb9be..b27f1942 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -5,7 +5,7 @@ import typing as t import discord from discord.ext.commands import Cog, Context, check, group, guild_only -from bot.bot import SeasonalBot +from bot.bot import Bot from bot.constants import Emojis from bot.utils.pagination import LinePaginator @@ -239,7 +239,7 @@ def is_requester_free() -> t.Callable: class TicTacToe(Cog): """TicTacToe cog contains tic-tac-toe game commands.""" - def __init__(self, bot: SeasonalBot): + def __init__(self, bot: Bot): self.bot = bot self.games: t.List[Game] = [] @@ -248,7 +248,7 @@ class TicTacToe(Cog): @is_requester_free() @group(name="tictactoe", aliases=("ttt",), invoke_without_command=True) async def tic_tac_toe(self, ctx: Context, opponent: t.Optional[discord.User]) -> None: - """Tic Tac Toe game. Play agains friends or AI. Use reactions to add your mark to field.""" + """Tic Tac Toe game. Play against friends or AI. Use reactions to add your mark to field.""" if opponent == ctx.author: await ctx.send("You can't play against yourself.") return @@ -313,6 +313,6 @@ class TicTacToe(Cog): await ctx.send(game.format_board()) -def setup(bot: SeasonalBot) -> None: +def setup(bot: Bot) -> None: """Load TicTacToe Cog.""" bot.add_cog(TicTacToe(bot)) -- cgit v1.2.3 From 48103a601f48ac620adf8f97de7ab9f7ab942998 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 9 Jan 2021 08:21:44 +0200 Subject: Simplify check_win function returning --- bot/exts/evergreen/tic_tac_toe.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index b27f1942..d5c2a558 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -17,21 +17,21 @@ CONFIRMATION_MESSAGE = ( def check_win(board: t.Dict[int, str]) -> bool: """Check from board, is any player won game.""" - if ( + return any( + ( # Horizontal - board[1] == board[2] == board[3] - or board[4] == board[5] == board[6] - or board[7] == board[8] == board[9] + board[1] == board[2] == board[3], + board[4] == board[5] == board[6], + board[7] == board[8] == board[9], # Vertical - or board[1] == board[4] == board[7] - or board[2] == board[5] == board[8] - or board[3] == board[6] == board[9] + board[1] == board[4] == board[7], + board[2] == board[5] == board[8], + board[3] == board[6] == board[9], # Diagonal - or board[1] == board[5] == board[9] - or board[3] == board[5] == board[7] - ): - return True - return False + board[1] == board[5] == board[9], + board[3] == board[5] == board[7], + ) + ) class Player: -- cgit v1.2.3 From 97ef76a6206a69d4ab58ca9de9c980afa4ca20c6 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 16 Jan 2021 20:12:51 +0200 Subject: Move Tic Tac Toe board to embed description --- bot/exts/evergreen/tic_tac_toe.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index d5c2a558..a206aee7 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -188,7 +188,9 @@ class Game: async def play(self) -> None: """Start and handle game.""" await self.ctx.send("It's time for game! Let's begin.") - board = await self.ctx.send(self.format_board()) + board = await self.ctx.send( + embed=discord.Embed(description=self.format_board()) + ) await self.add_reactions(board) for _ in range(9): @@ -203,7 +205,9 @@ class Game: self.canceled = True return self.board[pos] = self.current.symbol - await board.edit(content=self.format_board()) + await board.edit( + embed=discord.Embed(description=self.format_board()) + ) await board.clear_reaction(Emojis.number_emojis[pos]) if check_win(self.board): self.winner = self.current -- cgit v1.2.3 From 3fcddb2e7d4e855eef815df9e3552fce929ce00f Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 16 Jan 2021 20:27:58 +0200 Subject: Fix grammar Co-authored-by: ChrisJL --- bot/exts/evergreen/tic_tac_toe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index a206aee7..db84427c 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -10,7 +10,7 @@ from bot.constants import Emojis from bot.utils.pagination import LinePaginator CONFIRMATION_MESSAGE = ( - "{opponent}, {requester} want to play Tic-Tac-Toe against you. React to this message with " + "{opponent}, {requester} wants to play Tic-Tac-Toe against you. React to this message with " f"{Emojis.confirmation} to accept or with {Emojis.decline} to decline." ) -- cgit v1.2.3 From fb09472ddc9a52b4bcc78ce2f67f2ac768c8a8ec Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 16 Jan 2021 20:33:47 +0200 Subject: Add missing a article Co-authored-by: ChrisJL --- bot/exts/evergreen/tic_tac_toe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index db84427c..4b76f94d 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -220,7 +220,7 @@ class Game: self.current, self.next = self.next, self.current if not self.winner: self.draw = True - await self.ctx.send("It's DRAW!") + await self.ctx.send("It's a DRAW!") self.over = True -- cgit v1.2.3 From 6a3366a03de682f6d00aef9bfe33d7c8a10e6ba7 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 16 Jan 2021 20:35:28 +0200 Subject: Improve "your turn" message --- bot/exts/evergreen/tic_tac_toe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 4b76f94d..49571a34 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -195,7 +195,7 @@ class Game: for _ in range(9): if isinstance(self.current, Player): - announce = await self.ctx.send(f"{self.current.user.mention}, your turn! React to emoji to mark field.") + announce = await self.ctx.send(f"{self.current.user.mention}, it's your turn! React with an emoji to take your go.") timeout, pos = await self.current.get_move(self.board, board) if isinstance(self.current, Player): await announce.delete() -- cgit v1.2.3 From e2878ab625b7add00fe6aea3175107c9bd9e1cdb Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 16 Jan 2021 20:37:11 +0200 Subject: More grammar fixes --- bot/exts/evergreen/tic_tac_toe.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 49571a34..ed4a6d52 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -187,7 +187,7 @@ class Game: async def play(self) -> None: """Start and handle game.""" - await self.ctx.send("It's time for game! Let's begin.") + await self.ctx.send("It's time for the game! Let's begin.") board = await self.ctx.send( embed=discord.Embed(description=self.format_board()) ) @@ -213,7 +213,7 @@ class Game: self.winner = self.current self.loser = self.next await self.ctx.send( - f":tada: {self.current} is won this game! :tada:" + f":tada: {self.current} won this game! :tada:" ) await board.clear_reactions() break -- cgit v1.2.3 From b5490070a481d09a82e682be0b822baab4fc373e Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sat, 16 Jan 2021 20:48:27 +0200 Subject: Fix too long line --- bot/exts/evergreen/tic_tac_toe.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index ed4a6d52..bcc4c97e 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -195,7 +195,10 @@ class Game: for _ in range(9): if isinstance(self.current, Player): - announce = await self.ctx.send(f"{self.current.user.mention}, it's your turn! React with an emoji to take your go.") + announce = await self.ctx.send( + f"{self.current.user.mention}, it's your turn! " + "React with an emoji to take your go." + ) timeout, pos = await self.current.get_move(self.board, board) if isinstance(self.current, Player): await announce.delete() -- cgit v1.2.3 From 674147c2858dfb6e10470d7b4c044f46615950e3 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 17 Jan 2021 08:32:00 +0200 Subject: Remove unnecessary line split Co-authored-by: Xithrius <15021300+Xithrius@users.noreply.github.com> --- bot/exts/evergreen/tic_tac_toe.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index bcc4c97e..daa646a8 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -314,9 +314,7 @@ class TicTacToe(Cog): await ctx.send("Game don't exist.") return game = self.games[game_id - 1] - await ctx.send( - f"{game.winner} :trophy: vs {game.loser}" - ) + await ctx.send(f"{game.winner} :trophy: vs {game.loser}") await ctx.send(game.format_board()) -- cgit v1.2.3 From 1d6912caf4bf3cb0604933ec66fa13294fa5d68a Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 17 Jan 2021 08:38:20 +0200 Subject: Fix indention --- bot/exts/evergreen/tic_tac_toe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index daa646a8..22fff102 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -314,7 +314,7 @@ class TicTacToe(Cog): await ctx.send("Game don't exist.") return game = self.games[game_id - 1] - await ctx.send(f"{game.winner} :trophy: vs {game.loser}") + await ctx.send(f"{game.winner} :trophy: vs {game.loser}") await ctx.send(game.format_board()) -- cgit v1.2.3 From 7cdf800368e4e287a8fd2bc3b539395ff6c73e41 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Sun, 24 Jan 2021 17:10:08 +0200 Subject: Decrease timeout from 120 sec to 30 sec --- bot/exts/evergreen/tic_tac_toe.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot/exts/evergreen/tic_tac_toe.py') diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py index 22fff102..e1190502 100644 --- a/bot/exts/evergreen/tic_tac_toe.py +++ b/bot/exts/evergreen/tic_tac_toe.py @@ -58,7 +58,7 @@ class Player: ) try: - react, _ = await self.ctx.bot.wait_for('reaction_add', timeout=120.0, check=check_for_move) + react, _ = await self.ctx.bot.wait_for('reaction_add', timeout=30.0, check=check_for_move) except asyncio.TimeoutError: return True, None else: -- cgit v1.2.3