aboutsummaryrefslogtreecommitdiffstats
path: root/bot/exts/evergreen/tic_tac_toe.py
diff options
context:
space:
mode:
Diffstat (limited to 'bot/exts/evergreen/tic_tac_toe.py')
-rw-r--r--bot/exts/evergreen/tic_tac_toe.py335
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())