diff options
| author | 2021-09-05 13:40:16 -0700 | |
|---|---|---|
| committer | 2021-09-05 13:40:16 -0700 | |
| commit | da43ac4255fb255cc1feb9b146e1d8faa68858f6 (patch) | |
| tree | 9c5a1e8d4543ade9946fe86da17fc69b83ba1a81 /bot/exts/evergreen/tic_tac_toe.py | |
| parent | Fix issues occured while deploying [no ci] (diff) | |
| parent | Merge pull request #853 from python-discord/post-restructure-fix (diff) | |
Merge branch 'main' into color-677
Diffstat (limited to 'bot/exts/evergreen/tic_tac_toe.py')
| -rw-r--r-- | bot/exts/evergreen/tic_tac_toe.py | 335 | 
1 files changed, 0 insertions, 335 deletions
| diff --git a/bot/exts/evergreen/tic_tac_toe.py b/bot/exts/evergreen/tic_tac_toe.py deleted file mode 100644 index 5c4f8051..00000000 --- a/bot/exts/evergreen/tic_tac_toe.py +++ /dev/null @@ -1,335 +0,0 @@ -import asyncio -import random -from typing import Callable, Optional, Union - -import discord -from discord.ext.commands import Cog, Context, check, group, guild_only - -from bot.bot import Bot -from bot.constants import Emojis -from bot.utils.pagination import LinePaginator - -CONFIRMATION_MESSAGE = ( -    "{opponent}, {requester} wants to play Tic-Tac-Toe against you." -    f"\nReact to this message with {Emojis.confirmation} to accept or with {Emojis.decline} to decline." -) - - -def check_win(board: dict[int, str]) -> bool: -    """Check from board, is any player won game.""" -    return any( -        ( -            # Horizontal -            board[1] == board[2] == board[3], -            board[4] == board[5] == board[6], -            board[7] == board[8] == board[9], -            # Vertical -            board[1] == board[4] == board[7], -            board[2] == board[5] == board[8], -            board[3] == board[6] == board[9], -            # Diagonal -            board[1] == board[5] == board[9], -            board[3] == board[5] == board[7], -        ) -    ) - - -class Player: -    """Class that contains information about player and functions that interact with player.""" - -    def __init__(self, user: discord.User, ctx: Context, symbol: str): -        self.user = user -        self.ctx = ctx -        self.symbol = symbol - -    async def get_move(self, board: dict[int, str], msg: discord.Message) -> tuple[bool, 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: -            """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 -                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=30.0, check=check_for_move) -        except asyncio.TimeoutError: -            return True, None -        else: -            return False, list(Emojis.number_emojis.keys())[list(Emojis.number_emojis.values()).index(react.emoji)] - -    def __str__(self) -> str: -        """Return mention of user.""" -        return self.user.mention - - -class AI: -    """Tic Tac Toe AI class for against computer gaming.""" - -    def __init__(self, symbol: str): -        self.symbol = symbol - -    async def get_move(self, board: dict[int, str], _: discord.Message) -> 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())] - -        for symbol in (Emojis.o_square, Emojis.x_square): -            for move in possible_moves: -                board_copy = board.copy() -                board_copy[move] = symbol -                if 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) - -    def __str__(self) -> str: -        """Return `AI` as user name.""" -        return "AI" - - -class Game: -    """Class that contains information and functions about Tic Tac Toe game.""" - -    def __init__(self, players: list[Union[Player, AI]], ctx: Context): -        self.players = players -        self.ctx = ctx -        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] - -        self.winner: Optional[Union[Player, AI]] = None -        self.loser: Optional[Union[Player, AI]] = None -        self.over = False -        self.canceled = False -        self.draw = False - -    async def get_confirmation(self) -> tuple[bool, Optional[str]]: -        """ -        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, -                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: -            """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 -                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: -            self.over = True -            self.canceled = True -            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: -            self.over = True -            self.canceled = True -            return False, "User declined" - -    async def add_reactions(self, msg: discord.Message) -> None: -        """Add number emojis to message.""" -        for nr in Emojis.number_emojis.values(): -            await msg.add_reaction(nr) - -    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 play(self) -> None: -        """Start and handle game.""" -        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()) -        ) -        await self.add_reactions(board) - -        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." -                ) -            timeout, pos = await self.current.get_move(self.board, board) -            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 -                self.canceled = True -                return -            self.board[pos] = self.current.symbol -            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 -                self.loser = self.next -                await self.ctx.send( -                    f":tada: {self.current} 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 a DRAW!") -        self.over = True - - -def is_channel_free() -> 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() -> 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.""" - -    def __init__(self): -        self.games: list[Game] = [] - -    @guild_only() -    @is_channel_free() -    @is_requester_free() -    @group(name="tictactoe", aliases=("ttt", "tic"), invoke_without_command=True) -    async def tic_tac_toe(self, ctx: Context, opponent: Optional[discord.User]) -> None: -        """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 -        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 -        if opponent is None: -            game = Game( -                [Player(ctx.author, ctx, Emojis.x_square), AI(Emojis.o_square)], -                ctx -            ) -        else: -            game = Game( -                [Player(ctx.author, ctx, Emojis.x_square), Player(opponent, ctx, Emojis.o_square)], -                ctx -            ) -        self.games.append(game) -        if opponent is not None: -            if opponent.bot:  # check whether the opponent is a bot or not -                await ctx.send("You can't play Tic-Tac-Toe with bots!") -                return - -            confirmed, msg = await game.get_confirmation() - -            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) -    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 -        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]} vs {game.players[1]} (draw)" -                    ) -                else: -                    log_games.append( -                        f"**#{i+1}**: {game.winner} :trophy: vs {game.loser}" -                    ) -        await LinePaginator.paginate( -            log_games, -            ctx, -            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] - -        if game.draw: -            description = f"{game.players[0]} vs {game.players[1]} (draw)\n\n{game.format_board()}" -        else: -            description = f"{game.winner} :trophy: vs {game.loser}\n\n{game.format_board()}" - -        embed = discord.Embed( -            title=f"Match #{game_id} Game Board", -            description=description, -        ) -        await ctx.send(embed=embed) - - -def setup(bot: Bot) -> None: -    """Load the TicTacToe cog.""" -    bot.add_cog(TicTacToe()) | 
