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') 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') 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') 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') 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') 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') 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 83d9173cdcd8671dedf5400dde81caac21e37634 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 11:57:13 +0300 Subject: (Constants, TicTacToe): Added number emojis that will be shown in board and in reactions. --- bot/constants.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index ca9bb94a..0135124b 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -118,6 +118,19 @@ class Emojis: pull_request_closed = "<:PRClosed:629695470519713818>" merge = "<:PRMerged:629695470570176522>" + # TicTacToe Emojis + number_emojis = { + 1: "\u0031", + 2: "\u0032", + 3: "\u0033", + 4: "\u0034", + 5: "\u0035", + 6: "\u0036", + 7: "\u0037", + 8: "\u0038", + 9: "\u0039" + } + class Hacktoberfest(NamedTuple): voice_id = 514420006474219521 -- cgit v1.2.3 From 1abec1772f140f886b84fb43c394527553cb271a Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 11:59:12 +0300 Subject: (Constants, TicTacToe): Added confirmation and declining emojis. --- bot/constants.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index 0135124b..a23c6bcc 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -130,6 +130,8 @@ class Emojis: 8: "\u0038", 9: "\u0039" } + confirmation = "\u2705" + decline = "\u274c" class Hacktoberfest(NamedTuple): -- cgit v1.2.3 From fb9523b9c5e93ff30cdc58ac451e807da323c8b4 Mon Sep 17 00:00:00 2001 From: ks129 <45097959+ks129@users.noreply.github.com> Date: Tue, 7 Apr 2020 12:01:26 +0300 Subject: (Constants, TicTacToe): Added X and O emojis. --- bot/constants.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'bot') diff --git a/bot/constants.py b/bot/constants.py index a23c6bcc..d43cf4d3 100644 --- a/bot/constants.py +++ b/bot/constants.py @@ -132,6 +132,8 @@ class Emojis: } confirmation = "\u2705" decline = "\u274c" + x = "\U0001f1fd" + o = "\U0001f1f4" class Hacktoberfest(NamedTuple): -- 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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 d3690c4056826d963c4a25b8fc7f5704aa8e93e1 Mon Sep 17 00:00:00 2001 From: Hambira Date: Sat, 30 May 2020 22:36:45 +0530 Subject: "adding xkcd feature" --- bot/exts/evergreen/xkcd.py | 72 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 bot/exts/evergreen/xkcd.py (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py new file mode 100644 index 00000000..11477216 --- /dev/null +++ b/bot/exts/evergreen/xkcd.py @@ -0,0 +1,72 @@ +import logging +import random + +import discord +from discord.ext import commands + +log = logging.getLogger(__name__) + + +class XKCD(commands.Cog): + """A cog for posting the XKCD .""" + + def __init__(self, bot: commands.Bot): + self.bot = bot + + @commands.command(name="xkcd") + async def fetch_xkcd_comics(self, ctx: commands.Context, comic: str = "latest") -> None: + """Read your Fav XKCD comics.""" + if comic not in ["random", "latest"]: + url = f"https://xkcd.com/{comic}/info.0.json" + else: + url = "https://xkcd.com/info.0.json" + + # ---- random choice ----- + if comic == "random": + async with self.bot.http_session.get(url) as r: + json_data = await r.json() + random_pick = random.randint(1, int(json_data["num"])) + url = f"https://xkcd.com/{random_pick}/info.0.json" + + log.trace(f"Querying xkcd API: {url}") + async with self.bot.http_session.get(url) as r: + if r.status == "200": + json_data = await r.json() + else: + # ----- Exception handling | Guides to use ------ + log.warning(f"Received response {r.status} from: {url}") + # -- get the latest comic number --- + url = f"https://xkcd.com/info.0.json" + async with self.bot.http_session.get(url) as r: + latest_data = await r.json() + + # --- beautify response --- + latest_num = latest_data["num"] + resp = discord.Embed( + title="Guides | Usage", + description=f''' + .xkcd latest (Retrieves the latest comic) + .xkcd random (Retrieves random comic) + .xkcd number (Enter a comic number between 1 & {latest_num}) + ''' + ) + return await ctx.send(embed=resp) + + # --- response variables ---- + day, month, year = json_data["day"], json_data["month"], json_data["year"] + comic_number = json_data["num"] + + # ---- beautify response ---- + embed = discord.Embed( + title=json_data['title'], + description=json_data["alt"] + ) + embed.set_image(url=json_data['img']) + embed.set_footer(text=f"Post date : {day}-{month}-{year} | xkcd comics - {comic_number}") + + await ctx.send(embed=embed) + + +def setup(bot: commands.Bot) -> None: + """XKCD Cog load.""" + bot.add_cog(XKCD(bot)) -- 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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') 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 dd8664c3b4ff8037b9e28d9935a2f99fe9463a7b Mon Sep 17 00:00:00 2001 From: soul crusher 2005 Date: Wed, 20 Jan 2021 16:18:43 +0530 Subject: Update trivia_quiz.json new questions --- bot/resources/evergreen/trivia_quiz.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) (limited to 'bot') diff --git a/bot/resources/evergreen/trivia_quiz.json b/bot/resources/evergreen/trivia_quiz.json index 8f0a4114..a5e3a3e4 100644 --- a/bot/resources/evergreen/trivia_quiz.json +++ b/bot/resources/evergreen/trivia_quiz.json @@ -247,6 +247,18 @@ "question": "What is the capital of Iraq?", "answer": "Baghdad", "info": "Baghdad is the capital of Iraq. It has a population of 7 million people." + }, + { + "id": 136, + "question":"The United Nations headquarters is located at which city?", + "answer":"New York", + "info":"The United Nations is headquartered in New York City in a complex designed by a board of architects led by Wallace Harrison and built by the architectural firm Harrison & Abramovitz. The complex has served as the official headquarters of the United Nations since its completion in 1951." + }, + { + "id": 137, + "question":"At what year did Christopher Columbus discover America?", + "answer":"1492", + "info":"The explorer Christopher Columbus made four trips across the Atlantic Ocean from Spain: in 1492, 1493, 1498 and 1502. He was determined to find a direct water route west from Europe to Asia, but he never did. Instead, he stumbled upon the Americas" } ] } -- cgit v1.2.3 From d259741371af9d4a09ed97b3f0ddb3df25ce2ded Mon Sep 17 00:00:00 2001 From: Vivaan Parashar Date: Mon, 19 Oct 2020 13:48:48 +0530 Subject: Implement Spooky Name Rate game removed commented code added the delete command Changed the name from ratethespook to spooknamerate renamed file too rename ratethespook.json to spooknamerate.json Added more names from mockaroo added one user one reaction removed print statements fixed flake8 comments and prevented the user from deleting the word while polling fixed typo, added random messages, made each entry unique, fixed one user one reaction, made it for one day. I think I am done commented some code autopep8ed edited few lines of code added comments added some comments edited code edited code edited fixed syntax error fixed flake8 complaints fixed flake8 complaint fixed small error added a `word` command and informed user if they don't have a registered word fixed a small error where the first and last names weren't separated removed unecessary code changed word to name remove slash in multiline strings and remove unecessary comments. Also, lock the background loop to `OCTOBER` and make emojis into discord emoij form (:emoij_name:) fixed an accidental tab removed another unecessary comment remove more unecessary comments remove `.keys()` for dictionaries removed `len() > 0` for lists and dicts and changed emojis to '\N{}' form Fixed code so that return value is that specified and added fail-safes instead of `if` and `else`s f-stringify fixed borderline api abuse and missing space in defining word lint code multiple imports on one line and fix typo remove unecessary return, shorten var typehint remove 'Just' in suggestion and add extra line in json file. - unecessary comment and replace on with for in help embed description shorten emoij_message remove check in delete command use defaultdict sort imports group imports, add typehinting, remove unnecessary comments and docstring, remove redundant elses and returns sort imports refactor var wrds, use generator instead of list use typing.Dict[str, str] instead of dict and use .items() instead of get() add a comment remove reduntant comment Renamed variable to avoid conflicts add asyncio locking to prevent Runtime error add some comments lock all commands to OCTOBER enhance looping in checking messages add `cog_check` instead of limiting each command remove unused import remove test code and comments use fail fast rename function Make storage persistent and make sure announce_word does not go off everytime the bot restarts fixed typo make data persist, rename everything that has word in it to 'name' and make sure announce_name doesn't start off everytime the bot restarts. remove testing code which would cause a real mess if commited. Which I did commit. use a separate file for the name and first_time becuase re-dumping such a long file might take time make var for repetitive paths and change .day to .hour change scheduling logic lint code add cog_unload fix error in spooknamerate_names.json and fix the before_loop in spooknamerate.py revert accidental changes and remove commented code remove unused code refactor vars to caps and make emojis_val global edit docstring and make seasonalbot_commands to community_bot_commands make annotation correct and add check when channel is None for get_channel Add fullstop Loop directly over data Add a proper dash and fix linting Fix linting reverted to making EMOJIS_VAL global and fixed capitalization fix small error verify it is working and simplify import remove data files Use redis caching instead of JSON and rename remove empty title and description in embed and use discord's red color remove var typehint add Client.month_override for dev move ping function rename seasonalbot to sir-lancebot remove unnecessary newline fix line formatting move added_messages to global Add more info on the caches remove + alias improve formatting use str.format instead of func fix error directly used Channels.community_bot_commands get user from cache instead of actively fetching the user move help messages to constant add more info in err msg Apply suggestions from review Co-authored-by: ks129 <45097959+ks129@users.noreply.github.com> remove unnecessary comments remove another redundant comment improve formatting and use better var names hard-code a var Use get or fetch format Remove redundant commit Fix in_allowed_month for debugging remove extra space make channel name link to channel simplify uteration use msg.reactions directly rename r to reaction and directly use variable reformat code use from_dict instead of manually creating the Embed Remove commented code fix channel linking add some debugging support add some more info to the debug mode Directly use getLogger sort imports Remove (name) in function doctype Use SpookNameRate.debug everywhere Shrink function call to one line and remove extra info in comment Use fail fast in on_reaction_add use environment var for debug mode Set debug val to False by default Fix some line breaks that formatting with black had made and use fail fast Use custom environment variable instead of the global bot env var make bot reply and store info from the bot's reply instead of the user's message remove an accidental swp file fix the reaction not getting removed remove extra brackets use generators instead of lists fix logging statement simplify loop rename spooknamerate to spookynamerate Correct docstring Improve the name announcing code Ignore reaction of all bots rearrange or send "Name deleted" instead of "Message deleted" Add client prefix --- bot/exts/halloween/spookynamerate.py | 401 ++++ bot/resources/halloween/spookynamerate_names.json | 2206 +++++++++++++++++++++ 2 files changed, 2607 insertions(+) create mode 100644 bot/exts/halloween/spookynamerate.py create mode 100644 bot/resources/halloween/spookynamerate_names.json (limited to 'bot') diff --git a/bot/exts/halloween/spookynamerate.py b/bot/exts/halloween/spookynamerate.py new file mode 100644 index 00000000..e2950343 --- /dev/null +++ b/bot/exts/halloween/spookynamerate.py @@ -0,0 +1,401 @@ +import asyncio +import json +import random +from collections import defaultdict +from datetime import datetime, timedelta +from logging import getLogger +from os import getenv +from pathlib import Path +from typing import Dict, Union + +from async_rediscache import RedisCache +from discord import Embed, Reaction, TextChannel, User +from discord.colour import Colour +from discord.ext import tasks +from discord.ext.commands import Bot, Cog, Context, group + +from bot.constants import Channels, Client, Colours, Month +from bot.utils.decorators import InMonthCheckFailure + +logger = getLogger(__name__) + +EMOJIS_VAL = { + "\N{Jack-O-Lantern}": 1, + "\N{Ghost}": 2, + "\N{Skull and Crossbones}": 3, + "\N{Zombie}": 4, + "\N{Face Screaming In Fear}": 5, +} +ADDED_MESSAGES = [ + "Let's see if you win?", + ":jack_o_lantern: SPOOKY :jack_o_lantern:", + "If you got it, haunt it.", + "TIME TO GET YOUR SPOOKY ON! :skull:", +] +PING = "<@{id}>" + +EMOJI_MESSAGE = "\n".join([f"- {emoji} {val}" for emoji, val in EMOJIS_VAL.items()]) +HELP_MESSAGE_DICT = { + "title": "Spooky Name Rate", + "description": f"Help for the `{Client.prefix}spookynamerate` command", + "color": Colours.soft_orange, + "fields": [ + { + "name": "How to play", + "value": ( + "Everyday, the bot will post a random name, which you will need to spookify using your creativity.\n" + "You can rate each message according to how scary it is.\n" + "At the end of the day, the author of the message with most reactions will be the winner of the day.\n" + f"On a scale of 1 to {len(EMOJIS_VAL)}, the reactions order:\n" + f"{EMOJI_MESSAGE}" + ), + "inline": False, + }, + { + "name": "How do I add my spookified name?", + "value": f"Simply type `{Client.prefix}spookynamerate add my name`", + "inline": False, + }, + { + "name": "How do I *delete* my spookified name?", + "value": f"Simply type `{Client.prefix}spookynamerate delete`", + "inline": False, + }, + ], +} + + +class SpookyNameRate(Cog): + """ + A game that asks the user to spookify or halloweenify a name that is given everyday. + + It sends a random name everyday. The user needs to try and spookify it to his best ability and + send that name back using the `spookynamerate add entry` command + """ + + # This cache stores the message id of each added word along with a dictionary which contains the name the author + # added, the author's id, and the author's score (which is 0 by default) + messages = RedisCache() + + # The data cache stores small information such as the current name that is going on and whether it is the first time + # the bot is running + data = RedisCache() + debug = getenv('SPOOKYNAMERATE_DEBUG', False) # Enable if you do not want to limit the commands to October or if + # you do not want to wait till 12 UTC. Note: if debug is enabled and you run `.cogs reload spookynamerate`, it + # will automatically start the scoring and announcing the result (without waiting for 12, so do not expect it to.). + # Also, it won't wait for the two hours (when the poll closes). + + def __init__(self, bot: Bot) -> None: + self.bot = bot + + names_data = self.load_json( + Path("bot", "resources", "halloween", "spookynamerate_names.json") + ) + self.first_names = names_data["first_names"] + self.last_names = names_data["last_names"] + # the names are from https://www.mockaroo.com/ + + self.name = None + + self.bot.loop.create_task(self.load_vars()) + + self.first_time = None + self.poll = False + self.announce_name.start() + self.checking_messages = asyncio.Lock() + # Define an asyncio.Lock() to make sure the dictionary isn't changed + # when checking the messages for duplicate emojis' + + async def load_vars(self) -> None: + """Loads the variables that couldn't be loaded in __init__.""" + self.first_time = await self.data.get("first_time", True) + self.name = await self.data.get("name") + + @group(name="spookynamerate", invoke_without_command=True) + async def spooky_name_rate(self, ctx: Context) -> None: + """Get help on the Spooky Name Rate game.""" + await ctx.send(embed=Embed.from_dict(HELP_MESSAGE_DICT)) + + @spooky_name_rate.command(name="list", aliases=["all", "entries"]) + async def list_entries(self, ctx: Context) -> None: + """Send all the entries up till now in a single embed.""" + await ctx.send(embed=await self.get_responses_list(final=False)) + + @spooky_name_rate.command(name="name") + async def tell_name(self, ctx: Context) -> None: + """Tell the current random name.""" + if not self.poll: + await ctx.send(f"The name is **{self.name}**") + return + + await ctx.send( + f"The name ~~is~~ was **{self.name}**. The poll has already started, so you cannot " + "add an entry." + ) + + @spooky_name_rate.command(name="add", aliases=["register"]) + async def add_name(self, ctx: Context, *, name: str) -> None: + """Use this command to add/register your spookified name.""" + if self.poll: + logger.info(f"{ctx.message.author} tried to add a name, but the poll had already started.") + await ctx.send("Sorry, the poll has started! You can try and participate in the next round though!") + return + + message = ctx.message + + for data in (json.loads(user_data) for _, user_data in await self.messages.items()): + if data["author"] == message.author.id: + await ctx.send( + "But you have already added an entry! Type " + f"`{self.bot.command_prefix}spookynamerate " + "delete` to delete it, and then you can add it again" + ) + return + + elif data["name"] == name: + await ctx.send("TOO LATE. Someone has already added this name.") + return + + msg = await (await self.get_channel()).send(f"{message.author.mention} added the name {name!r}!") + + await self.messages.set( + msg.id, + json.dumps( + { + "name": name, + "author": message.author.id, + "score": 0, + } + ), + ) + + for emoji in EMOJIS_VAL: + await msg.add_reaction(emoji) + + logger.info(f"{message.author} added the name {name!r}") + + @spooky_name_rate.command(name="delete") + async def delete_name(self, ctx: Context) -> None: + """Delete the user's name.""" + if self.poll: + await ctx.send("You can't delete your name since the poll has already started!") + return + for message_id, data in await self.messages.items(): + data = json.loads(data) + + if ctx.author.id == data["author"]: + await self.messages.delete(message_id) + await ctx.send(f'Name deleted successfully ({data["name"]!r})!') + return + + await ctx.send( + f"But you don't have an entry... :eyes: Type `{self.bot.command_prefix}spookynamerate add your entry`" + ) + + @Cog.listener() + async def on_reaction_add(self, reaction: Reaction, user: User) -> None: + """Ensures that each user adds maximum one reaction.""" + if user.bot or not await self.messages.contains(reaction.message.id): + return + + async with self.checking_messages: # Acquire the lock so that the dictionary isn't reset while iterating. + if reaction.emoji in EMOJIS_VAL: + # create a custom counter + reaction_counter = defaultdict(int) + for msg_reaction in reaction.message.reactions: + async for reaction_user in msg_reaction.users(): + if reaction_user == self.bot.user: + continue + reaction_counter[reaction_user] += 1 + + if reaction_counter[user] > 1: + await user.send( + "Sorry, you have already added a reaction, " + "please remove your reaction and try again." + ) + await reaction.remove(user) + return + + @tasks.loop(hours=24.0) + async def announce_name(self) -> None: + """Announces the name needed to spookify every 24 hours and the winner of the previous game.""" + if not self.in_allowed_month(): + return + + channel = await self.get_channel() + + if self.first_time: + await channel.send( + "Okkey... Welcome to the **Spooky Name Rate Game**! It's a relatively simple game.\n" + f"Everyday, a random name will be sent in <#{Channels.community_bot_commands}> " + "and you need to try and spookify it!\nRegister your name using " + f"`{self.bot.command_prefix}spookynamerate add spookified name`" + ) + + await self.data.set("first_time", False) + self.first_time = False + + else: + if await self.messages.items(): + await channel.send(embed=await self.get_responses_list(final=True)) + self.poll = True + if not SpookyNameRate.debug: + await asyncio.sleep(2 * 60 * 60) # sleep for two hours + + logger.info("Calculating score") + for message_id, data in await self.messages.items(): + data = json.loads(data) + + msg = await channel.fetch_message(message_id) + score = 0 + for reaction in msg.reactions: + reaction_value = EMOJIS_VAL.get(reaction.emoji, 0) # get the value of the emoji else 0 + score += reaction_value * (reaction.count - 1) # multiply by the num of reactions + # subtract one, since one reaction was done by the bot + + logger.debug(f"{self.bot.get_user(data['author'])} got a score of {score}") + data["score"] = score + await self.messages.set(message_id, json.dumps(data)) + + # Sort the winner messages + winner_messages = sorted( + ((msg_id, json.loads(usr_data)) for msg_id, usr_data in await self.messages.items()), + key=lambda x: x[1]["score"], + reverse=True, + ) + + winners = [] + for i, winner in enumerate(winner_messages): + winners.append(winner) + if len(winner_messages) > i + 1: + if winner_messages[i + 1][1]["score"] != winner[1]["score"]: + break + elif len(winner_messages) == (i + 1) + 1: # The next element is the last element + if winner_messages[i + 1][1]["score"] != winner[1]["score"]: + break + + # one iteration is complete + await channel.send("Today's Spooky Name Rate Game ends now, and the winner(s) is(are)...") + + async with channel.typing(): + await asyncio.sleep(1) # give the drum roll feel + + if not winners: # There are no winners (no participants) + await channel.send("Hmm... Looks like no one participated! :cry:") + return + + score = winners[0][1]["score"] + congratulations = "to all" if len(winners) > 1 else PING.format(id=winners[0][1]["author"]) + names = ", ".join(f'{win[1]["name"]} ({PING.format(id=win[1]["author"])})' for win in winners) + + # display winners, their names and scores + await channel.send( + f"Congratulations {congratulations}!\n" + f"You have a score of {score}!\n" + f"Your name{ 's were' if len(winners) > 1 else 'was'}:\n{names}" + ) + + # Send random party emojis + party = (random.choice([":partying_face:", ":tada:"]) for _ in range(random.randint(1, 10))) + await channel.send(" ".join(party)) + + async with self.checking_messages: # Acquire the lock to delete the messages + await self.messages.clear() # reset the messages + + # send the next name + self.name = f"{random.choice(self.first_names)} {random.choice(self.last_names)}" + await self.data.set("name", self.name) + + await channel.send( + "Let's move on to the next name!\nAnd the next name is...\n" + f"**{self.name}**!\nTry to spookify that... :smirk:" + ) + + self.poll = False # accepting responses + + @announce_name.before_loop + async def wait_till_scheduled_time(self) -> None: + """Waits till the next day's 12PM if crossed it, otherwise waits till the same day's 12PM.""" + if SpookyNameRate.debug: + return + + now = datetime.utcnow() + if now.hour < 12: + twelve_pm = now.replace(hour=12, minute=0, second=0, microsecond=0) + time_left = twelve_pm - now + await asyncio.sleep(time_left.seconds) + return + + tomorrow_12pm = now + timedelta(days=1) + tomorrow_12pm = tomorrow_12pm.replace(hour=12, minute=0, second=0, microsecond=0) + await asyncio.sleep((tomorrow_12pm - now).seconds) + + async def get_responses_list(self, final: bool = False) -> Embed: + """Returns an embed containing the responses of the people.""" + channel = await self.get_channel() + + embed = Embed(color=Colour.red()) + + if await self.messages.items(): + if final: + embed.title = "Spooky Name Rate is about to end!" + embed.description = ( + "This Spooky Name Rate round is about to end in 2 hours! You can review " + "the entries below! Have you rated other's names?" + ) + else: + embed.title = "All the spookified names!" + embed.description = "See a list of all the entries entered by everyone!" + else: + embed.title = "No one has added an entry yet..." + + for message_id, data in await self.messages.items(): + data = json.loads(data) + + embed.add_field( + name=(self.bot.get_user(data["author"]) or await self.bot.fetch_user(data["author"])).name, + value=f"[{(data)['name']}](https://discord.com/channels/{Client.guild}/{channel.id}/{message_id})", + ) + + return embed + + async def get_channel(self) -> Union[TextChannel, None]: + """Gets the sir-lancebot-channel after waiting until ready.""" + await self.bot.wait_until_ready() + channel = self.bot.get_channel( + Channels.community_bot_commands + ) or await self.bot.fetch_channel(Channels.community_bot_commands) + if not channel: + logger.warning("Bot is unable to get the #seasonalbot-commands channel. Please check the channel ID.") + return channel + + @staticmethod + def load_json(file: Path) -> Dict[str, str]: + """Loads a JSON file and returns its contents.""" + with file.open("r", encoding="utf-8") as f: + return json.load(f) + + @staticmethod + def in_allowed_month() -> bool: + """Returns whether running in the limited month.""" + if SpookyNameRate.debug: + return True + + if not Client.month_override: + return datetime.utcnow().month == Month.OCTOBER + return Client.month_override == Month.OCTOBER + + def cog_check(self, ctx: Context) -> bool: + """A command to check whether the command is being called in October.""" + if not self.in_allowed_month(): + raise InMonthCheckFailure("You can only use these commands in October!") + return True + + def cog_unload(self) -> None: + """Stops the announce_name task.""" + self.announce_name.cancel() + + +def setup(bot: Bot) -> None: + """Loads the SpookyNameRate Cog.""" + bot.add_cog(SpookyNameRate(bot)) diff --git a/bot/resources/halloween/spookynamerate_names.json b/bot/resources/halloween/spookynamerate_names.json new file mode 100644 index 00000000..7657880b --- /dev/null +++ b/bot/resources/halloween/spookynamerate_names.json @@ -0,0 +1,2206 @@ +{ + "first_names": [ + "Eberhard", + "Gladys", + "Joshua", + "Misty", + "Bondy", + "Constantine", + "Juliette", + "Dalis", + "Nap", + "Sandy", + "Inglebert", + "Sasha", + "Julietta", + "Christoforo", + "Del", + "Zelma", + "Vladimir", + "Wayland", + "Enos", + "Siobhan", + "Farrand", + "Ailee", + "Horatia", + "Gloriana", + "Britney", + "Shel", + "Lindsey", + "Francis", + "Elsa", + "Fred", + "Upton", + "Lothaire", + "Cara", + "Margarete", + "Wolfgang", + "Charin", + "Loydie", + "Aurelea", + "Sibel", + "Glenden", + "Julian", + "Roby", + "Gerri", + "Sandie", + "Twila", + "Shaylyn", + "Clyde", + "Dina", + "Chase", + "Caron", + "Carlin", + "Aida", + "Rhonda", + "Rebekkah", + "Charmian", + "Lindy", + "Obadiah", + "Willy", + "Matti", + "Melodie", + "Ira", + "Wilfrid", + "Berton", + "Denver", + "Clarette", + "Nicolas", + "Tawnya", + "Cynthy", + "Arman", + "Sherwood", + "Flemming", + "Berri", + "Beret", + "Aili", + "Hannie", + "Eadie", + "Tannie", + "Gilda", + "Walton", + "Nolly", + "Tonya", + "Meaghan", + "Timmi", + "Faina", + "Sarge", + "Britteny", + "Farlay", + "Carola", + "Skippy", + "Corrina", + "Hans", + "Courtnay", + "Taffy", + "Averill", + "Martie", + "Tobye", + "Broderic", + "Gardner", + "Lucky", + "Beverie", + "Ignaz", + "Siana", + "Marybelle", + "Leif", + "Baily", + "Pyotr", + "Myrtle", + "Darb", + "Gar", + "Vinni", + "Samson", + "Kinny", + "Briant", + "Verney", + "Del", + "Marion", + "Beniamino", + "Nona", + "Fay", + "Noreen", + "Maurizio", + "Nial", + "Mirabella", + "Melisa", + "Anatol", + "Halette", + "Johnathon", + "Antonietta", + "Germana", + "Towny", + "Shayne", + "Court", + "Merrile", + "Staffard", + "Odele", + "Gustav", + "Moyna", + "Warden", + "Craggie", + "Hurleigh", + "Hartley", + "Rustie", + "Raven", + "Farra", + "Leonidas", + "Jorrie", + "Maximilian", + "Augustin", + "Cordelia", + "Christoffer", + "Lana", + "Vittorio", + "Janith", + "Margaret", + "Bethanne", + "Brooke", + "Payton", + "Poul", + "Diahann", + "Andy", + "Garek", + "Isa", + "Dunn", + "Anny", + "Hillary", + "Andres", + "Winn", + "Gare", + "Ameline", + "Audre", + "Rodrigo", + "Anabal", + "Reuben", + "Cecil", + "Alexandro", + "Corny", + "Erek", + "William", + "Rudyard", + "Muffin", + "Allin", + "Emmit", + "Heindrick", + "Myrna", + "Kriste", + "Perry", + "Annmarie", + "Jasun", + "Esdras", + "Jobyna", + "Marian", + "Theodore", + "Dionisio", + "Efren", + "Clarita", + "Leilah", + "Modestia", + "Clem", + "Jemmy", + "Karol", + "Minni", + "Damien", + "Tessy", + "Roanne", + "Daniele", + "Camel", + "Charlot", + "Daron", + "Cherey", + "Ashil", + "Joel", + "Michell", + "Sukey", + "Micheil", + "Chev", + "Winny", + "Natale", + "Kendra", + "Bell", + "Darice", + "Beilul", + "Leonore", + "Abba", + "Warden", + "Bryna", + "Sammy", + "Brantley", + "Goldi", + "Meridith", + "Eleanor", + "Brear", + "Kristina", + "Muriel", + "Serge", + "Iver", + "Jonis", + "Ada", + "Marleen", + "Pavlov", + "Kellia", + "Abdel", + "Waylin", + "Ignazio", + "Tana", + "Kiley", + "Lynna", + "Peyton", + "Linoel", + "Patrice", + "Loria", + "Linda", + "Edna", + "Viki", + "Kelcy", + "Chelsae", + "Olga", + "Trace", + "Ethel", + "Giorgio", + "Geralda", + "Rosaline", + "Caralie", + "Duke", + "Sig", + "Seana", + "Boris", + "Jeanie", + "Stacee", + "Giffie", + "Myrta", + "Prescott", + "Roger", + "Ame", + "Lelia", + "Marthena", + "Mord", + "Tommi", + "Artemus", + "Wynn", + "Rodi", + "Denna", + "Joleen", + "Iris", + "Pascale", + "Cody", + "Kienan", + "Darline", + "Lanna", + "Chandra", + "Michel", + "Nanete", + "Rosana", + "Ondrea", + "Linette", + "Syd", + "Rhianon", + "Christiano", + "Moyna", + "Darbee", + "Chadd", + "Roselia", + "Niki", + "Flint", + "Natala", + "Merrie", + "Noelyn", + "Arvin", + "Vin", + "Khalil", + "Nance", + "Seward", + "Dagmar", + "Shanta", + "Noland", + "Vance", + "Kyla", + "Locke", + "Abagail", + "Guthrey", + "Thalia", + "Devlen", + "Parrnell", + "Leonard", + "Amber", + "Dell", + "Lolita", + "Revkah", + "Ronna", + "Ninnetta", + "Jobey", + "Larisa", + "Wendel", + "Sonnnie", + "Saul", + "Lem", + "Wang", + "Borg", + "Korie", + "Rosanna", + "Barnaby", + "Channa", + "Gordan", + "Wang", + "Dasi", + "Laurianne", + "Jo ann", + "Bond", + "Kean", + "Harwell", + "Abbey", + "Carlo", + "Hamil", + "Ameline", + "Tristam", + "Donn", + "Earle", + "Lanie", + "Maximilianus", + "Frieda", + "Noella", + "Orsa", + "Timmi", + "Linea", + "Claudina", + "Langsdon", + "Murdock", + "Cello", + "Lek", + "Viviyan", + "Candra", + "Erena", + "Shirline", + "Mariann", + "Keelby", + "Jacquelin", + "Clerissa", + "Davis", + "Ara", + "My", + "Andris", + "Drugi", + "Lynn", + "Andonis", + "Jamie", + "Cherise", + "Lonni", + "Reamonn", + "Cathee", + "Clarence", + "Joletta", + "Tanny", + "Gasparo", + "Heddie", + "Cullin", + "Sander", + "Emmalee", + "Gwendolin", + "Hayley", + "Mandie", + "Cassondra", + "Celestyna", + "Fanny", + "Alica", + "Vivyan", + "Kippy", + "Leandra", + "Jerry", + "Elspeth", + "Lexine", + "Tobie", + "Allin", + "Ambros", + "Ash", + "Conroy", + "Melonie", + "Aylmer", + "Maximo", + "Connie", + "Torre", + "Tammie", + "Corabella", + "Beau", + "Nancee", + "Ailbert", + "Florrie", + "Trevar", + "Tiffani", + "Dre", + "Eward", + "Hallie", + "Stesha", + "Ralina", + "Vinni", + "Bastien", + "Galvan", + "Romain", + "Yasmin", + "Theodoric", + "Maxy", + "Lesly", + "Gerald", + "Erskine", + "Joice", + "Theadora", + "Sheeree", + "Danit", + "Burr", + "Morten", + "Godfree", + "Lacey", + "Sandye", + "Louisa", + "Annora", + "Rochester", + "Saundra", + "Deeann", + "Aloisia", + "Oralle", + "Ree", + "Kaile", + "Rogerio", + "Graeme", + "Garald", + "Hulda", + "Deny", + "Bessy", + "Zarah", + "Melisande", + "Taffy", + "Jed", + "Bar", + "Jacki", + "Avictor", + "Damiano", + "Yasmeen", + "Geralda", + "Kermie", + "Verge", + "Cyril", + "Klara", + "Anna", + "Abey", + "Mariellen", + "Mirabel", + "Charmain", + "Carleton", + "Biddie", + "Junina", + "Cass", + "Jdavie", + "Laird", + "Olenka", + "Dion", + "Hedy", + "Haley", + "Stacy", + "Alis", + "Morena", + "Damita", + "Wynn", + "Kellia", + "Midge", + "Gerri", + "Symon", + "Markus", + "Brenn", + "Rancell", + "Marlon", + "Dulciana", + "Lemmy", + "Neale", + "Vladamir", + "Alasteir", + "Gilberta", + "Seumas", + "Ronda", + "Myrvyn", + "Gabey", + "Goldia", + "Lothaire", + "Averil", + "Marlo", + "Nanice", + "Bernadette", + "Nehemiah", + "Ivar", + "Natala", + "Dorthy", + "Melva", + "Alisha", + "Ruthann", + "Ray", + "Ariel", + "Gib", + "Pippo", + "Miner", + "Ardith", + "Letisha", + "Granger", + "Sue", + "Toby", + "Tallou", + "Stephi", + "Hunter", + "Terrell", + "Pail", + "Moise", + "Rosetta", + "Ira", + "Denyse", + "Jackie", + "Fons", + "Goldy", + "Rani", + "Bendick", + "Valentijn", + "Annabell", + "Ardith", + "Lesly", + "Almire", + "Emmalyn", + "Mechelle", + "Anna", + "Duff", + "Louise", + "Vivian", + "Farand", + "Sophi", + "Thedric", + "Vivien", + "Jere", + "Kassie", + "Andy", + "Helli", + "Ros", + "Babara", + "Othella", + "Shelton", + "Hector", + "Charmian", + "Rosamond", + "Maison", + "Magda", + "Gustave", + "Latisha", + "Erik", + "Gavin", + "Bobette", + "Masha", + "Collie", + "Kippie", + "Jillayne", + "Fairfax", + "Ulrika", + "Juliann", + "Joly", + "Aldus", + "Clarie", + "Aluin", + "Claudetta", + "Noella", + "Nichols", + "Rutger", + "Niall", + "Hunter", + "Hyacinthia", + "Eva", + "Humphrey", + "Randi", + "Leontyne", + "Bordy", + "Orin", + "Tobey", + "Aldis", + "Vernon", + "Griz", + "Dynah", + "Ann-marie", + "Inglebert", + "Gifford", + "Emeline", + "Shem", + "Sigvard", + "Mayne", + "Rhodia", + "Seward", + "Valencia", + "Babara", + "Cirstoforo", + "Nye", + "Merissa", + "Lucinda", + "Wynn", + "Vassili", + "Cletus", + "Felisha", + "Laural", + "William", + "Emmalynne", + "Angy", + "Charles", + "Jemmy", + "Edward", + "Millicent", + "Homer", + "Allie", + "Brandyn", + "Dannye", + "Hector", + "Fawne", + "Frayda", + "Issiah", + "Deana", + "Bearnard", + "Ken", + "Sinclare", + "Mallorie", + "Noby", + "Deonne", + "Brig", + "Ruy", + "Vivia", + "Nyssa", + "Ame", + "Carmen", + "Solly", + "Carolee", + "Felice", + "Claiborne", + "Layney", + "Raina", + "Tami", + "Dosi", + "Barth", + "Julita", + "Gardiner", + "Stesha", + "Geneva", + "Saudra", + "Ella", + "Welbie", + "Marya", + "Happy", + "Brandise", + "Jewell", + "Joana", + "Eddy", + "Buck", + "Leslie", + "Yolanda", + "Murdoch", + "Muffin", + "Myrna", + "Susi", + "Berthe", + "Debra", + "Kyla", + "Bron", + "Thurston", + "Case", + "Shelli", + "Danika", + "Charissa", + "Wylie", + "Corine", + "Caitrin", + "Atalanta", + "Vevay", + "Thekla", + "Inez", + "Pris", + "Zsazsa", + "Ardenia", + "Ole", + "Kelcy", + "Earl", + "Pierson", + "Opalina", + "Leta", + "Keefer", + "Conrado", + "Chen", + "Alys", + "Floyd", + "Kai", + "Warden", + "Peyton", + "Debora", + "Walton", + "Fionna", + "Kendra", + "Michail", + "Christa", + "Theodor", + "Avivah", + "Patric", + "Quinton", + "Fey", + "Lewiss", + "Loren", + "Nedi", + "Fergus", + "Jeanie", + "Liuka", + "Ashley", + "Ellsworth", + "Winslow", + "Land", + "Rooney", + "Kati", + "Joelie", + "Garner", + "Clarice", + "Clair", + "Heddi", + "Ivan", + "Enrichetta", + "Umberto", + "Alys", + "Marcellina", + "Elnore", + "Wilburt", + "Ami", + "Meridith", + "Devlin", + "Cicely", + "Nathanael", + "Rafi", + "Arluene", + "Erasmus", + "Tasia", + "Seumas", + "George", + "Fredrika", + "Jayne", + "Linus", + "Mathilde", + "Klarrisa", + "Willy", + "Rad", + "Rae", + "Wilfred", + "Amberly", + "Paulo", + "Robbi", + "Gladys", + "Mirilla", + "Danica", + "Montgomery", + "Bellina", + "Neill", + "Roddie", + "Sebastiano", + "Adrianne", + "Gilli", + "Rhodia", + "Orbadiah", + "Levy", + "Griswold", + "Millicent", + "Carry", + "Alexander", + "Carole", + "Othilie", + "Enrica", + "Corissa", + "Meaghan", + "Margret", + "Sheff", + "Walton", + "Tremain", + "Bear", + "Maximilian", + "Theodora", + "Fredric", + "Baudoin", + "Rees", + "Roldan", + "Mayor", + "Angelica", + "Clemente", + "Florencia", + "Lancelot", + "Valencia", + "Caddric", + "Frieda", + "Jarvis", + "Shamus", + "Kalindi", + "Allen", + "Maureen", + "Ax", + "Barbra", + "Craggy", + "Howie", + "Orson", + "Cammy", + "Sullivan", + "Marleen", + "Jarrad", + "Lucy", + "Catha", + "Guillemette", + "Birdie", + "Forrest", + "Luce", + "Myriam", + "Serge", + "Kali", + "Ruperto", + "Trisha", + "Shaylynn", + "Janella", + "Franciskus", + "Melinde", + "Effie", + "Letti", + "Roderic", + "Jandy", + "Michaelina", + "Mohammed", + "Dolorita", + "Elbertine", + "Esma", + "Emmett", + "Lucila", + "Joyann", + "Mufi", + "Karlotta", + "Vannie", + "Daphna", + "Blondie", + "Madelene", + "Tomkin", + "Kassie", + "Flynn", + "Zebadiah", + "Lauritz", + "Brian", + "Leah", + "Amalita", + "Corissa", + "Onfre", + "Shantee", + "Deena", + "Marena", + "Alejoa", + "Fania", + "Catha", + "Cherlyn", + "Gerrilee", + "Brook", + "Yardley", + "Karry", + "Dennis", + "Ingra", + "Damian", + "Alexandros", + "Romola", + "Grantley", + "Antons", + "Randal", + "Lorilee", + "Brier", + "Tyrone", + "Jennica", + "Deidre", + "Arlin", + "Marline", + "Lyell", + "Lorelei", + "Marius", + "Willy", + "Teddy", + "Grantham", + "Yelena", + "Jaimie", + "Brewer", + "Tess", + "Othelia", + "Bondy", + "Rebecka", + "Laurice", + "Jasen", + "Betty", + "Alverta", + "Pepita", + "Kandace", + "Loni", + "Doreen", + "Ketty", + "Ree", + "Danni", + "Zorah", + "Shayla", + "Ivy", + "Darin", + "Karie", + "Brittaney", + "Viole", + "Harlene", + "Jasun", + "Aime", + "Rickie", + "Heath", + "Andris", + "Vaughn", + "Giorgi", + "Maddalena", + "Shirley", + "Cherie", + "Zacharia", + "Darcey", + "Barbee", + "Ernest", + "Sher", + "Faustina", + "Nari", + "Gusella", + "Reginald", + "Zack", + "Michele", + "Gene", + "Lindy", + "Mirilla", + "Tudor", + "Tyler", + "Bernadina", + "Magdalen", + "Nollie", + "Coreen", + "Hoebart", + "Virginie", + "Waylin", + "Hank", + "Valenka", + "Sabine", + "Jesus", + "Annabell", + "Jesselyn", + "Marysa", + "Corbett", + "Carena", + "Bert", + "Tanhya", + "Alphonse", + "Johnette", + "Vince", + "Cordell", + "Ramonda", + "Trev", + "Glenna", + "Loy", + "Arni", + "Tedd", + "Tristam", + "Zelma", + "Emmeline", + "Ellswerth", + "Janeta", + "Hughie", + "Tarun", + "Enid", + "Rafe", + "Hal", + "Melissa", + "Layan", + "Sia", + "Horace", + "Derry", + "Kelsi", + "Zacharia", + "Tillie", + "Dillon", + "Maxwell", + "Shanai", + "Charlize", + "Usama", + "Nabeela", + "Emily-Jane", + "Martyn", + "Tre", + "Ioan", + "Elysia", + "Mikaeel", + "Danny", + "Ciaron", + "Ace", + "Amy-Louise", + "Gabrielle", + "Robbie", + "Thea", + "Gloria", + "Jana", + "Cole", + "Eamon", + "Samiyah", + "Ellie-Mai", + "Lawson", + "Gia", + "Merryn", + "Andre", + "Ansh", + "Kavita", + "Alasdair", + "Aamina", + "Donna", + "Dario", + "Sahra", + "Brittany", + "Shakeel", + "Taylor", + "Ellenor", + "Kacy", + "Gene", + "Hetty", + "Fletcher", + "Donte", + "Krisha", + "Everett", + "Leila", + "Aairah", + "Zander", + "Sakina", + "Sanaya", + "Nelly", + "Manon", + "Antonio", + "Aimie", + "Kyran", + "Daria", + "Tilly-Mae", + "Lisa", + "Ammaarah", + "Adina", + "Kaan", + "Torin", + "Sadie", + "Mia-Rose", + "Aadam", + "Phyllis", + "Jace", + "Fraser", + "Tamanna", + "Dahlia", + "Cristian", + "Maira", + "Lana", + "Lily-Mai", + "Barney", + "Beatrice", + "Tabitha", + "Anis", + "Heidi", + "Ahyan", + "Usaamah", + "Jolene", + "Melisa", + "Magdalena", + "Hina" + ], + "last_names": [ + "Silveston", + "Manson", + "Hoodlass", + "Auden", + "Speakman", + "Seavers", + "Sodeau", + "Gouth", + "Pickersail", + "Ferschke", + "Buzzing", + "Kinnar", + "Pemberton", + "Firebrace", + "Kornilyev", + "Linsley", + "Petyanin", + "McCobb", + "Disdel", + "Eskrick", + "Pringuer", + "Clavering", + "Sims", + "Lippitt", + "Springall", + "Spiteri", + "Dwyr", + "Tomas", + "Cleminson", + "Crowder", + "Juster", + "Leven", + "Doucette", + "Schimoni", + "Readwing", + "Karet", + "Reef", + "Welden", + "Bemand", + "Schulze", + "Bartul", + "Collihole", + "Thain", + "Bernhardt", + "Tolputt", + "Hedges", + "Lowne", + "Kobu", + "Cabrera", + "Gavozzi", + "Ghilardini", + "Leamon", + "Gadsden", + "Gregg", + "Tew", + "Bangle", + "Youster", + "Vince", + "Cristea", + "Ablott", + "Lightowlers", + "Kittredge", + "Armour", + "Bukowski", + "Knowlton", + "Juett", + "Santorini", + "Ends", + "Hawkings", + "Janowicz", + "Harry", + "Bougourd", + "Gillow", + "Whalebelly", + "Conneau", + "Mellows", + "Stolting", + "Stickells", + "Maryet", + "Echallie", + "Edgecombe", + "Orchart", + "Mowles", + "McGibbon", + "Titchen", + "Madgewick", + "Fairburne", + "Colgan", + "Chaudhry", + "Taks", + "Lorinez", + "Eixenberger", + "Burel", + "Chapleo", + "Margram", + "Purse", + "MacKay", + "Oxlade", + "Prahm", + "Wellbank", + "Blackborow", + "Woodbridge", + "Sodory", + "Vedmore", + "Beeckx", + "Newcomb", + "Ridel", + "Desporte", + "Jobling", + "Winear", + "Korneichuk", + "Aucott", + "Wawer", + "Aicheson", + "Hawkslee", + "Wynes", + "St. Quentin", + "McQuorkel", + "Hendrick", + "Rudsdale", + "Winsor", + "Thunders", + "Stonbridge", + "Perrie", + "D'Alessandro", + "Banasevich", + "Mc Elory", + "Cobbledick", + "Wreakes", + "Carnie", + "Pallister", + "Yeates", + "Hoovart", + "Doogood", + "Churn", + "Gillon", + "Nibley", + "Dusting", + "Melledy", + "O'Noland", + "Crosfeld", + "Pairpoint", + "Longson", + "Rodden", + "Foyston", + "Le Teve", + "Brumen", + "Pudsey", + "Klimentov", + "Agent", + "Seabert", + "Cramp", + "Bitcheno", + "Embery", + "Etheredge", + "Sheardown", + "McKune", + "Vearncomb", + "Lavington", + "Rylands", + "Derges", + "Olivetti", + "Matasov", + "Thrower", + "Jobin", + "Ramsell", + "Rude", + "Tregale", + "Bradforth", + "McQuarter", + "Walburn", + "Poad", + "Filtness", + "Carneck", + "Pavis", + "Pinchen", + "Polye", + "Abry", + "Radloff", + "McDugal", + "Loughton", + "Revitt", + "Baniard", + "Kovalski", + "Mapother", + "Hendrikse", + "Rickardsson", + "Featherbie", + "Harlow", + "Kruschov", + "McCrillis", + "Barabich", + "Peaker", + "Skamell", + "Gorges", + "Chance", + "Bresner", + "Profit", + "Swinfon", + "Goldson", + "Nunson", + "Tarling", + "Ruperti", + "Grimsell", + "Davey", + "Deetlof", + "Gave", + "Fawltey", + "Tyre", + "Whaymand", + "Trudgian", + "McAndrew", + "Aleksankov", + "Dimbleby", + "Beseke", + "Cleverley", + "Aberhart", + "Courtin", + "MacKellen", + "Johannesson", + "Churm", + "Laverock", + "Astbury", + "Canto", + "Nelles", + "Dormand", + "Blucher", + "Youngs", + "Dalrymple", + "M'Chirrie", + "Jansens", + "Golthorpp", + "Ibberson", + "Andriveau", + "Paulton", + "Parrington", + "Shergill", + "Bickerton", + "Hugonneau", + "Cornelissen", + "Spincks", + "Malkinson", + "Kettow", + "Wasiel", + "Skeat", + "Maynard", + "Goutcher", + "Cratchley", + "Loving", + "Averies", + "Cahillane", + "Alvarado", + "Truggian", + "Bravington", + "McGonigle", + "Crocombe", + "Slorance", + "Dukes", + "Nairns", + "Condict", + "Got", + "Flowerdew", + "Deboy", + "Death", + "Patroni", + "Colgrave", + "Polley", + "Spraging", + "Orteaux", + "Daskiewicz", + "Dunsmore", + "Forrington", + "De Gogay", + "Swires", + "Grimmert", + "Castells", + "Scraggs", + "Chase", + "Dixsee", + "Brennans", + "Gookes", + "MacQueen", + "Galbreth", + "Buttwell", + "Annear", + "Sutherley", + "Portis", + "Pashen", + "Blackbourn", + "Sedgemond", + "Huegett", + "Emms", + "Leifer", + "Paschek", + "Bynold", + "Mahony", + "Izacenko", + "Hadland", + "Sallows", + "Hamper", + "Godlee", + "Rablin", + "Emms", + "Zealy", + "Russi", + "Crassweller", + "Shotbolt", + "Van Der Weedenburg", + "MacGille", + "Carillo", + "Guerin", + "Cuolahan", + "Metzel", + "Martinovsky", + "Stoggles", + "Brameld", + "Coupland", + "Kaaskooper", + "Sallows", + "Rizzotto", + "Dike", + "O'Lochan", + "Spragg", + "Lavarack", + "MacNess", + "Swetenham", + "Dillet", + "Coffey", + "Meikle", + "Loynes", + "Josum", + "Adkin", + "Tompsett", + "Maclaine", + "Fippe", + "Bispo", + "Whittek", + "Rylett", + "Iveagh", + "Elgar", + "Casswell", + "Tilt", + "Macklin", + "Lillee", + "Hamshere", + "Coite", + "Dollard", + "Tiesman", + "Coltart", + "Stothert", + "Crosswaite", + "Padgett", + "Gleadle", + "Meedendorpe", + "Alexsandrovich", + "Williamson", + "Futty", + "Antwis", + "Romanski", + "Dionisetti", + "Dimitriev", + "Swalowe", + "Dewing", + "O'Driscoll", + "Jeandel", + "Summerly", + "Shoute", + "Trelevan", + "Matkin", + "Headey", + "Rosson", + "Dunn", + "Gunner", + "Stapells", + "Fratczak", + "McGillivray", + "Edis", + "Treuge", + "Haskayne", + "Perell", + "O'Fairy", + "Slisby", + "Axcell", + "Mattingley", + "Tumilty", + "Kibble", + "Lambert", + "Hassall", + "Simpkin", + "Nitti", + "Stiegar", + "Pavitt", + "Kerby", + "Ruzic", + "Westwick", + "Tonbye", + "Bocken", + "Kinforth", + "Wren", + "Attow", + "McComish", + "McNickle", + "Wildman", + "O'Corhane", + "Jewar", + "Caveau", + "Woodrooffe", + "Batson", + "Stayt", + "A'field", + "Domesday", + "Taberer", + "Gigg", + "Stanmore", + "Hanton", + "Roskell", + "Brasener", + "Stanbro", + "Cordy", + "O'Bradane", + "Hansberry", + "Erdes", + "Wagon", + "Jimmes", + "Ruffles", + "Wigginton", + "Haste", + "Rymill", + "Tomsett", + "Ambrosoli", + "Reidshaw", + "Nurcombe", + "Costigan", + "Berwick", + "Hinchon", + "Blissitt", + "Golston", + "Goullee", + "Hudspeth", + "Traher", + "Salandino", + "Fatscher", + "Davidov", + "Baukham", + "Mallan", + "Kilmurray", + "Dmych", + "Mair", + "Felmingham", + "Kedward", + "Leechman", + "Frank", + "Tremoulet", + "Manley", + "Newcom", + "Brandone", + "Cliffe", + "Shorte", + "Baalham", + "Fairhead", + "Sheal", + "Effnert", + "MacCaughey", + "Rizzolo", + "Linthead", + "Greenhouse", + "Clayson", + "Franca", + "Lambell", + "Egdal", + "Pringell", + "Penni", + "Train", + "Langfitt", + "Dady", + "Rannigan", + "Ledwidge", + "Summerton", + "D'Hooghe", + "Ary", + "Gooderick", + "Scarsbrooke", + "Janouch", + "Pond", + "Menichini", + "Crinidge", + "Sneesbie", + "Harflete", + "Ubsdell", + "Littleover", + "Vanne", + "Fassbender", + "Zellner", + "Gorce", + "McKeighan", + "Claffey", + "MacGarvey", + "Norwich", + "Antosch", + "Loughton", + "McCuthais", + "Arnaudi", + "Broz", + "Stert", + "McMechan", + "Texton", + "Bees", + "Couser", + "Easseby", + "McCorry", + "Fetterplace", + "Crankshaw", + "Spancock", + "Neasam", + "Bruckental", + "Badgers", + "Rodda", + "Bossingham", + "Crump", + "Jurgensen", + "Noyes", + "Scarman", + "Bakey", + "Swindin", + "Tolworthie", + "Vynehall", + "Shallcrass", + "Bazoge", + "Jonczyk", + "Eatherton", + "Finlason", + "Hembery", + "Lassetter", + "Soule", + "Baldocci", + "Thurman", + "Poppy", + "Eveque", + "Summerlad", + "Eberle", + "Pettecrew", + "Hitzmann", + "Allonby", + "Bodimeade", + "Catteroll", + "Wooldridge", + "Baines", + "Halloway", + "Doghartie", + "Bracher", + "Kynnd", + "Metherell", + "Routham", + "Fielder", + "Ashleigh", + "Aked", + "Kolakowski", + "Picardo", + "Murdy", + "Feacham", + "Lewin", + "Braben", + "Salaman", + "Letterick", + "Bovaird", + "Moriarty", + "Bertot", + "Cowan", + "Dionisi", + "Maybey", + "Joskowicz", + "Shoutt", + "Bernli", + "Dikles", + "Corringham", + "Shaw", + "Donovin", + "Merigeau", + "Pinckney", + "Queripel", + "Sampson", + "Benfell", + "Cansdell", + "Tasseler", + "Amthor", + "Nancekivell", + "Stock", + "Boltwood", + "Goreisr", + "Le Grand", + "Terrans", + "Knapp", + "Roseman", + "Gunstone", + "Hissie", + "Orto", + "Bell", + "Colam", + "Drust", + "Roseblade", + "Sulman", + "Jennaway", + "Joust", + "Curthoys", + "Cajkler", + "MacIllrick", + "Print", + "Coulthard", + "Lemmon", + "Bush", + "McMurrugh", + "Toping", + "Brute", + "Fryman", + "Bosomworth", + "Lawson", + "Lauder", + "Heinssen", + "Bittlestone", + "Brinson", + "Hambling", + "Vassman", + "Brookbank", + "Bolstridge", + "Leslie", + "Berndsen", + "Aindrais", + "Mogra", + "Wilson", + "Josefs", + "Norgan", + "Wong", + "le Keux", + "Hastwall", + "Bunson", + "Van", + "Waghorne", + "Ojeda", + "Boole", + "Winters", + "Gurge", + "Gallemore", + "Perulli", + "Dight", + "Di Filippo", + "Winsley", + "Chalcraft", + "Human", + "Laetham", + "Lennie", + "McSorley", + "Toolan", + "Brammar", + "Cadogan", + "Molloy", + "Shoveller", + "Vignaux", + "Hannaway", + "Sykora", + "Brealey", + "Harness", + "Profit", + "Goldsbury", + "Brands", + "Godmar", + "Binden", + "Kondratenya", + "Warsap", + "Rumble", + "Maudson", + "Demer", + "Laxtonne", + "Kmietsch", + "Colten", + "Raysdale", + "Gadd", + "Blanche", + "Viant", + "Daskiewicz", + "Macura", + "Crouch", + "Janicijevic", + "Oade", + "Fancourt", + "Dimitriev", + "Earnshaw", + "Wing", + "Fountain", + "Fearey", + "Nottram", + "Bescoby", + "Jeandeau", + "Mapowder", + "Iacobo", + "Rabjohns", + "Dean", + "Whiterod", + "Mathiasen", + "Josephson", + "Boc", + "Olivet", + "Yeardley", + "Labuschagne", + "Curmi", + "Rogger", + "Tesoe", + "Mellhuish", + "Malan", + "McArt", + "Ing", + "Renowden", + "Mellsop", + "Critchlow", + "Seedhouse", + "Tiffin", + "Chirm", + "Oldknow", + "Wolffers", + "Dainter", + "Bundy", + "Copplestone", + "Moses", + "Weedon", + "Borzone", + "Craigg", + "Pyrah", + "Shoorbrooke", + "Jeandeau", + "Halgarth", + "Bamlett", + "Greally", + "Abrahamovitz", + "Oger", + "Mandrake", + "Craigg", + "Stenning", + "Tommei", + "Mapother", + "Cree", + "Clandillon", + "Thorlby", + "Careswell", + "Woolnough", + "McMeekin", + "Woodman", + "Mougin", + "Burchill", + "Pegg", + "Morin", + "Eskriett", + "Gelderd", + "Latham", + "Siney", + "Freen", + "Walrond", + "Bell", + "Twigley", + "D'Souza", + "Anton", + "Doyle", + "Pieters", + "Rosenvasser", + "Mackneis", + "Brisse", + "Boffin", + "Rushe", + "Cozens", + "Bensusan", + "Plampin", + "Gauford", + "Lecky", + "Belton", + "Fleming", + "Gent", + "Bunclark", + "Climar", + "Milner", + "Karolovsky", + "Claesens", + "Oleksiak", + "Barkway", + "Glenister", + "Steynor", + "Hecks", + "Rollo", + "Elcoux", + "Altham", + "Veschambes", + "Livingstone", + "Miroy", + "Edy", + "Bendle", + "Widdall", + "Onions", + "Devita", + "McOwan", + "Ahearne", + "Wisniowski", + "Pask", + "Ciccottini", + "Parlatt", + "Gindghill", + "Marquess", + "Claworth", + "Veel", + "Fairbairn", + "Galletley", + "Glew", + "Gillice", + "Liddyard", + "Babin", + "Ryson", + "Kyteley", + "Toms", + "Downton", + "Mougel", + "Inglefield", + "Gaskins", + "Bradie", + "Stanbury", + "McMenamy", + "Cranstone", + "Thody", + "Iacovozzo", + "Theobalds", + "Perrins", + "Dyott", + "Hupe", + "Gelling", + "Eadington", + "Crumbie", + "Stainsby", + "Kolakowski", + "Norwich", + "Ehrat", + "Basnett", + "Marden", + "Godby", + "Kubacki", + "Wiles", + "Littrick", + "Chuck", + "Negus", + "Aisthorpe", + "Danelut", + "Helversen", + "McCombe", + "Dallender", + "Offner", + "Leser", + "Savin", + "Belcham", + "Pockett", + "Selway", + "Santostefano.", + "Telford", + "Presser", + "Haken", + "Wybourne", + "Reolfo", + "Mineghelli", + "Beverage", + "Grimsdike", + "Drogan", + "Bynert", + "Boothman", + "Postle", + "Baskwell", + "Branno", + "Hechlin", + "Geake", + "Morstatt", + "Towne", + "Phillott", + "Doumerc", + "Ladewig", + "Sexty", + "Sleigh", + "Simonaitis", + "Han", + "Crommett", + "Blowes", + "Floyde", + "Delgardo", + "Brounsell", + "Klimowski", + "Jaffray", + "Kingzeth", + "Pithie", + "Eriksson", + "Gudgin", + "Hamal", + "Hooks", + "Rosle", + "Braysher", + "O'Curneen", + "Millett", + "Woofinden", + "Lillistone", + "Broxis", + "Mochar", + "Drewell", + "Hedgeman", + "Wharf", + "Lambden", + "Lambol", + "Slowcock", + "Cicchillo", + "Trineman", + "Sinyard", + "Brandone", + "Masding", + "Britnell", + "Quinlan", + "Arnopp", + "Jeratt", + "Bantick", + "Craigs", + "Pantling", + "Klais", + "Pickvance", + "Goodwill", + "McGavin", + "Esslemont", + "Bakewell", + "Downer", + "Scallan", + "Ronchka", + "Scholcroft", + "Van Der Walt", + "Armfield", + "Chalker", + "Chinge", + "Yakubov", + "Folkerd", + "Manon", + "Gookey", + "Connold", + "Dusey", + "Muselli", + "Skala", + "Dibbin", + "Kreber", + "De Blasi", + "Drei", + "Argo", + "Maudson", + "Stanlick", + "Steinham", + "Dallewater", + "Litchmore", + "Mathie", + "Gook", + "Forrestor", + "Ferreira", + "Budd", + "Joskowitz", + "Whetnall", + "Beany", + "Keymar", + "Merrin", + "Waldera", + "O'Gleasane", + "Duiged", + "Cumo", + "Giddings", + "Craker", + "Olenov", + "Whayman", + "Raoux", + "Delete", + "McDell", + "Gauntlett", + "Gomby", + "Rottgers", + "Spraggon", + "Orth", + "Shortan", + "Lineen", + "Monkhouse", + "Di Domenico", + "Brinsden", + "MacCallister", + "Sieghard", + "Pheasant", + "Cloney", + "Igglesden", + "Checklin", + "Grosier", + "Garnett", + "Vasnetsov", + "Chsteney", + "Manifield", + "Coutts", + "Bagshawe", + "Pryn", + "Dunstall", + "Rowlings", + "Whines", + "Bish", + "Solomon", + "Mackay", + "Daugherty", + "Gutierrez", + "Goff", + "Villanueva", + "Heath", + "Serrano", + "Munro", + "Levine", + "Barrett", + "Bateman", + "Colon", + "Alford", + "Whitehouse", + "Mendoza", + "Keith", + "Orr", + "Shepherd", + "North", + "Steele", + "Morales", + "Shea", + "Olsen", + "Wormald", + "Torres", + "Haines", + "Kerr", + "Reeves", + "Bates", + "Potts", + "Foreman", + "Herrera", + "Mccoy", + "Fulton", + "Charles", + "Clay", + "Estes", + "Mata", + "Childs", + "Kendall", + "Wallace", + "Thorpe", + "Oconnell", + "Waters", + "Roth", + "Barker", + "Fritz", + "Singleton", + "Sharpe", + "Little", + "Oliver", + "Ayala", + "Khan", + "Braun", + "Dean", + "Stout", + "Adamson", + "Tate", + "Juarez", + "Pickett", + "Burke", + "Gordon", + "Mackenzie", + "Bloggs", + "Read", + "Britton", + "Jefferson", + "Lutz", + "Chen", + "Wagstaff", + "Coates", + "Gilliam", + "Mullins", + "Ryan", + "Moon", + "Thompson", + "Abbott", + "Cotton", + "Barajas", + "Chan", + "Bostock", + "Spencer", + "Sparrow", + "Robinson", + "Morrison", + "Aguirre", + "Clayton", + "Hope", + "Swanson", + "Ochoa", + "Ruiz", + "Truong", + "Gibbons", + "Daniel", + "Zimmerman", + "Flynn", + "Keeling", + "Greenaway", + "Edwards" + ] +} -- cgit v1.2.3 From c654f693eda0db6b12e8bafa6ef0354ca4f43245 Mon Sep 17 00:00:00 2001 From: aruna2019 Date: Thu, 21 Jan 2021 01:13:52 +0530 Subject: Fixed battleship reading uppercases incorrectly. --- bot/exts/evergreen/battleship.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/battleship.py b/bot/exts/evergreen/battleship.py index 9bc374e6..fa3fb35c 100644 --- a/bot/exts/evergreen/battleship.py +++ b/bot/exts/evergreen/battleship.py @@ -140,7 +140,7 @@ class Game: @staticmethod def get_square(grid: Grid, square: str) -> Square: """Grabs a square from a grid with an inputted key.""" - index = ord(square[0]) - ord("A") + index = ord(square[0].upper()) - ord("A") number = int(square[1:]) return grid[number-1][index] # -1 since lists are indexed from 0 -- cgit v1.2.3 From ee829251a1f83b8620a76e6c81fe907ada1e1cc1 Mon Sep 17 00:00:00 2001 From: Xithrius <15021300+Xithrius@users.noreply.github.com> Date: Sun, 24 Jan 2021 01:21:24 -0800 Subject: Put a space after the colons for #562 trivia. --- bot/resources/evergreen/trivia_quiz.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) (limited to 'bot') diff --git a/bot/resources/evergreen/trivia_quiz.json b/bot/resources/evergreen/trivia_quiz.json index a5e3a3e4..faa3bc3b 100644 --- a/bot/resources/evergreen/trivia_quiz.json +++ b/bot/resources/evergreen/trivia_quiz.json @@ -250,15 +250,15 @@ }, { "id": 136, - "question":"The United Nations headquarters is located at which city?", - "answer":"New York", - "info":"The United Nations is headquartered in New York City in a complex designed by a board of architects led by Wallace Harrison and built by the architectural firm Harrison & Abramovitz. The complex has served as the official headquarters of the United Nations since its completion in 1951." + "question": "The United Nations headquarters is located at which city?", + "answer": "New York", + "info": "The United Nations is headquartered in New York City in a complex designed by a board of architects led by Wallace Harrison and built by the architectural firm Harrison & Abramovitz. The complex has served as the official headquarters of the United Nations since its completion in 1951." }, { "id": 137, - "question":"At what year did Christopher Columbus discover America?", - "answer":"1492", - "info":"The explorer Christopher Columbus made four trips across the Atlantic Ocean from Spain: in 1492, 1493, 1498 and 1502. He was determined to find a direct water route west from Europe to Asia, but he never did. Instead, he stumbled upon the Americas" + "question": "At what year did Christopher Columbus discover America?", + "answer": "1492", + "info": "The explorer Christopher Columbus made four trips across the Atlantic Ocean from Spain: in 1492, 1493, 1498 and 1502. He was determined to find a direct water route west from Europe to Asia, but he never did. Instead, he stumbled upon the Americas" } ] } -- cgit v1.2.3 From 948a64f3de06b946f7e4c3e1e730dd6849c58ca1 Mon Sep 17 00:00:00 2001 From: xithrius Date: Sun, 24 Jan 2021 03:00:47 -0800 Subject: Refactored the xkcd command, added a refresher. --- bot/exts/evergreen/xkcd.py | 113 ++++++++++++++++++++++----------------------- 1 file changed, 56 insertions(+), 57 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index 11477216..5c100bf0 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -1,72 +1,71 @@ import logging -import random +from random import randint +from typing import Dict, Optional, Union -import discord -from discord.ext import commands +from discord import Embed +from discord.ext import tasks +from discord.ext.commands import Cog, Context, command + +from bot.bot import Bot log = logging.getLogger(__name__) +URL = "https://xkcd.com/{0}/info.0.json" +LATEST = "https://xkcd.com/info.0.json" + -class XKCD(commands.Cog): - """A cog for posting the XKCD .""" +class XKCD(Cog): + """Retrieving XKCD comics.""" - def __init__(self, bot: commands.Bot): + def __init__(self, bot: Bot) -> None: self.bot = bot + self.latest_comic_info: Dict[str, Union[str, int]] = {} + self.get_latest_comic_info.start() - @commands.command(name="xkcd") - async def fetch_xkcd_comics(self, ctx: commands.Context, comic: str = "latest") -> None: - """Read your Fav XKCD comics.""" - if comic not in ["random", "latest"]: - url = f"https://xkcd.com/{comic}/info.0.json" - else: - url = "https://xkcd.com/info.0.json" - - # ---- random choice ----- - if comic == "random": - async with self.bot.http_session.get(url) as r: - json_data = await r.json() - random_pick = random.randint(1, int(json_data["num"])) - url = f"https://xkcd.com/{random_pick}/info.0.json" - - log.trace(f"Querying xkcd API: {url}") - async with self.bot.http_session.get(url) as r: - if r.status == "200": - json_data = await r.json() + def cog_unload(self) -> None: + """Cancels refreshing of the task for refreshing the most recent comic info.""" + self.get_latest_comic_info.cancel() + + @tasks.loop(minutes=30) + async def get_latest_comic_info(self) -> None: + """Refreshes latest comic's information ever 30 minutes. Also used for finding a random comic.""" + async with self.bot.http_session.get(LATEST) as resp: + if resp.status == 200: + self.latest_comic_info = await resp.json() else: - # ----- Exception handling | Guides to use ------ - log.warning(f"Received response {r.status} from: {url}") - # -- get the latest comic number --- - url = f"https://xkcd.com/info.0.json" - async with self.bot.http_session.get(url) as r: - latest_data = await r.json() - - # --- beautify response --- - latest_num = latest_data["num"] - resp = discord.Embed( - title="Guides | Usage", - description=f''' - .xkcd latest (Retrieves the latest comic) - .xkcd random (Retrieves random comic) - .xkcd number (Enter a comic number between 1 & {latest_num}) - ''' - ) - return await ctx.send(embed=resp) - - # --- response variables ---- - day, month, year = json_data["day"], json_data["month"], json_data["year"] - comic_number = json_data["num"] - - # ---- beautify response ---- - embed = discord.Embed( - title=json_data['title'], - description=json_data["alt"] - ) - embed.set_image(url=json_data['img']) - embed.set_footer(text=f"Post date : {day}-{month}-{year} | xkcd comics - {comic_number}") + log.debug(f"Failed to get latest XKCD comic information. Status code {resp.status}") + + @command(name="xkcd") + async def fetch_xkcd_comics(self, ctx: Context, comic: Optional[str]) -> None: + """ + Getting an xkcd comic's information along with the image. + + To get a random comic, don't type any number as an argument. To get the latest, enter 0. + """ + embed = Embed() + + comic = comic or randint(1, self.latest_comic_info['num']) + + if comic == "latest": + info = self.latest_comic_info + + else: + async with self.bot.http_session.get(URL.format(comic)) as resp: + if resp.status == 200: + info = await resp.json() + else: + embed.description = f"{resp.status}: Could not retrieve xkcd comic #{comic}." + log.debug(f"Retrieving xkcd comic #{comic} failed with status code {resp.status}.") + await ctx.send(embed=embed) + return + + embed.set_image(url=info["img"]) + date = f"{info['year']}/{info['month']}/{info['day']}" + embed.set_footer(text=f"{date} - #{comic}, \'{info['safe_title']}\'") await ctx.send(embed=embed) -def setup(bot: commands.Bot) -> None: - """XKCD Cog load.""" +def setup(bot: Bot) -> None: + """Loading the XKCD cog.""" bot.add_cog(XKCD(bot)) -- cgit v1.2.3 From 075559cddd1ca3d99c5d85e4bc4f01687d845fda Mon Sep 17 00:00:00 2001 From: xithrius Date: Sun, 24 Jan 2021 03:17:40 -0800 Subject: Added footer comic number for random and latest. --- bot/exts/evergreen/xkcd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index 5c100bf0..06c3b4a2 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -61,7 +61,7 @@ class XKCD(Cog): embed.set_image(url=info["img"]) date = f"{info['year']}/{info['month']}/{info['day']}" - embed.set_footer(text=f"{date} - #{comic}, \'{info['safe_title']}\'") + embed.set_footer(text=f"{date} - #{info['num']}, \'{info['safe_title']}\'") await ctx.send(embed=embed) -- cgit v1.2.3 From 2d52b09977b73b5f7583e914ffc5465321548f6a Mon Sep 17 00:00:00 2001 From: xithrius Date: Sun, 24 Jan 2021 03:23:34 -0800 Subject: Added soft red color if the command fails. --- bot/exts/evergreen/xkcd.py | 2 ++ 1 file changed, 2 insertions(+) (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index 06c3b4a2..b2f8879a 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -7,6 +7,7 @@ from discord.ext import tasks from discord.ext.commands import Cog, Context, command from bot.bot import Bot +from bot.constants import Colours log = logging.getLogger(__name__) @@ -55,6 +56,7 @@ class XKCD(Cog): info = await resp.json() else: embed.description = f"{resp.status}: Could not retrieve xkcd comic #{comic}." + embed.colour = Colours.soft_red log.debug(f"Retrieving xkcd comic #{comic} failed with status code {resp.status}.") await ctx.send(embed=embed) return -- cgit v1.2.3 From 295f0d33a4257d7d930a4da5ddf2f845f86ac730 Mon Sep 17 00:00:00 2001 From: xithrius Date: Sun, 24 Jan 2021 03:59:09 -0800 Subject: Added handling for comic arguments and interactive comics. --- bot/exts/evergreen/xkcd.py | 36 +++++++++++++++++++++++++----------- 1 file changed, 25 insertions(+), 11 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index b2f8879a..cb61e5b8 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -1,4 +1,5 @@ import logging +import re from random import randint from typing import Dict, Optional, Union @@ -11,8 +12,8 @@ from bot.constants import Colours log = logging.getLogger(__name__) -URL = "https://xkcd.com/{0}/info.0.json" -LATEST = "https://xkcd.com/info.0.json" +COMIC_FORMAT = re.compile(r"latest|[0-9]+") +BASE_URL = "https://xkcd.com" class XKCD(Cog): @@ -30,7 +31,7 @@ class XKCD(Cog): @tasks.loop(minutes=30) async def get_latest_comic_info(self) -> None: """Refreshes latest comic's information ever 30 minutes. Also used for finding a random comic.""" - async with self.bot.http_session.get(LATEST) as resp: + async with self.bot.http_session.get(f"{BASE_URL}/info.0.json") as resp: if resp.status == 200: self.latest_comic_info = await resp.json() else: @@ -41,29 +42,42 @@ class XKCD(Cog): """ Getting an xkcd comic's information along with the image. - To get a random comic, don't type any number as an argument. To get the latest, enter 0. + To get a random comic, don't type any number as an argument. To get the latest, type 'latest'. """ - embed = Embed() + embed = Embed(title=f"XKCD comic #{self.latest_comic_info['num'] if comic == 'latest' else comic}") - comic = comic or randint(1, self.latest_comic_info['num']) + embed.colour = Colours.soft_red + + if (comic := re.match(COMIC_FORMAT, comic)) is None: + embed.description = "Inputted comic parameter should either be an integer or 'latest'." + await ctx.send(embed=embed) + return + + comic = comic.group(0) or randint(1, self.latest_comic_info['num']) if comic == "latest": info = self.latest_comic_info else: - async with self.bot.http_session.get(URL.format(comic)) as resp: + async with self.bot.http_session.get(f"{BASE_URL}/{comic}/info.0.json") as resp: if resp.status == 200: info = await resp.json() else: embed.description = f"{resp.status}: Could not retrieve xkcd comic #{comic}." - embed.colour = Colours.soft_red log.debug(f"Retrieving xkcd comic #{comic} failed with status code {resp.status}.") await ctx.send(embed=embed) return - embed.set_image(url=info["img"]) - date = f"{info['year']}/{info['month']}/{info['day']}" - embed.set_footer(text=f"{date} - #{info['num']}, \'{info['safe_title']}\'") + if info["img"][:-3] in ("jpg", "png", "gif"): + embed.set_image(url=info["img"]) + date = f"{info['year']}/{info['month']}/{info['day']}" + embed.set_footer(text=f"{date} - #{info['num']}, \'{info['safe_title']}\'") + embed.colour = Colours.soft_green + else: + embed.description = ( + "Selected comic is interactive, and cannot be displayed within an embed.\n" + f"Comic can be viewed [here](https://xkcd.com/{info['num']})" + ) await ctx.send(embed=embed) -- cgit v1.2.3 From f5ea1ff1a9013df7426abc3076f66d12e64cb6b8 Mon Sep 17 00:00:00 2001 From: xithrius Date: Sun, 24 Jan 2021 04:00:38 -0800 Subject: Grammer formatting. --- bot/exts/evergreen/xkcd.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index cb61e5b8..91006715 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -75,8 +75,8 @@ class XKCD(Cog): embed.colour = Colours.soft_green else: embed.description = ( - "Selected comic is interactive, and cannot be displayed within an embed.\n" - f"Comic can be viewed [here](https://xkcd.com/{info['num']})" + "The selected comic is interactive, and cannot be displayed within an embed.\n" + f"Comic can be viewed [here](https://xkcd.com/{info['num']})." ) await ctx.send(embed=embed) -- cgit v1.2.3 From 2c14deb0cd25ca8f1c80fc6e02d321dca1af75d1 Mon Sep 17 00:00:00 2001 From: xithrius Date: Sun, 24 Jan 2021 04:24:09 -0800 Subject: Finished up optimization of statements. --- bot/exts/evergreen/xkcd.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index 91006715..e387d3c8 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -44,16 +44,16 @@ class XKCD(Cog): To get a random comic, don't type any number as an argument. To get the latest, type 'latest'. """ - embed = Embed(title=f"XKCD comic #{self.latest_comic_info['num'] if comic == 'latest' else comic}") + embed = Embed(title=f"XKCD comic '{comic}'") embed.colour = Colours.soft_red - if (comic := re.match(COMIC_FORMAT, comic)) is None: + if comic and (comic := re.match(COMIC_FORMAT, comic)) is None: embed.description = "Inputted comic parameter should either be an integer or 'latest'." await ctx.send(embed=embed) return - comic = comic.group(0) or randint(1, self.latest_comic_info['num']) + comic = randint(1, self.latest_comic_info['num']) if comic is None else comic.group(0) if comic == "latest": info = self.latest_comic_info @@ -63,12 +63,15 @@ class XKCD(Cog): if resp.status == 200: info = await resp.json() else: + embed.title = f"XKCD comic #{comic}" embed.description = f"{resp.status}: Could not retrieve xkcd comic #{comic}." log.debug(f"Retrieving xkcd comic #{comic} failed with status code {resp.status}.") await ctx.send(embed=embed) return - if info["img"][:-3] in ("jpg", "png", "gif"): + embed.title = f"XKCD comic #{info['num']}" + + if info["img"][-3:] in ("jpg", "png", "gif"): embed.set_image(url=info["img"]) date = f"{info['year']}/{info['month']}/{info['day']}" embed.set_footer(text=f"{date} - #{info['num']}, \'{info['safe_title']}\'") -- cgit v1.2.3 From 62b1b277146c19ba441b5dcceec073489dd7178a Mon Sep 17 00:00:00 2001 From: Xithrius <15021300+Xithrius@users.noreply.github.com> Date: Sun, 24 Jan 2021 04:38:59 -0800 Subject: Changed comic argument error for fluency of reading. Co-authored-by: ChrisJL --- bot/exts/evergreen/xkcd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index e387d3c8..674ad4b0 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -49,7 +49,7 @@ class XKCD(Cog): embed.colour = Colours.soft_red if comic and (comic := re.match(COMIC_FORMAT, comic)) is None: - embed.description = "Inputted comic parameter should either be an integer or 'latest'." + embed.description = "Comic parameter should either be an integer or 'latest'." await ctx.send(embed=embed) return -- cgit v1.2.3 From a81d30f34dc4d4fa6e8550437e6d329a4da4e746 Mon Sep 17 00:00:00 2001 From: xithrius Date: Sun, 24 Jan 2021 05:41:58 -0800 Subject: Removed newline between if/else statement. --- bot/exts/evergreen/xkcd.py | 1 - 1 file changed, 1 deletion(-) (limited to 'bot') diff --git a/bot/exts/evergreen/xkcd.py b/bot/exts/evergreen/xkcd.py index 674ad4b0..d3224bfe 100644 --- a/bot/exts/evergreen/xkcd.py +++ b/bot/exts/evergreen/xkcd.py @@ -57,7 +57,6 @@ class XKCD(Cog): if comic == "latest": info = self.latest_comic_info - else: async with self.bot.http_session.get(f"{BASE_URL}/{comic}/info.0.json") as resp: if resp.status == 200: -- 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') 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