first commit

This commit is contained in:
RayExo
2026-07-12 15:16:07 +05:30
commit 2289301f26
915 changed files with 69400 additions and 0 deletions

View File

@@ -0,0 +1,36 @@
"""This folder contains games that require discord.py v2.0.0 + to be used
they utilize UI components such as buttons.
"""
from __future__ import annotations
#from .aki_buttons import BetaAkinator
from .twenty_48_buttons import BetaTwenty48
from .wordle_buttons import BetaWordle
from .tictactoe_buttons import BetaTictactoe
from .memory_game import MemoryGame
from .rps_buttons import BetaRockPaperScissors
#from .hangman_buttons import BetaHangman
from .reaction_test_buttons import BetaReactionGame
from .country_guess_buttons import BetaCountryGuesser
from .chess_buttons import BetaChess
from .battleship_buttons import BetaBattleShip
from .number_slider import NumberSlider
from .lights_out import LightsOut
#from .boggle import Boggle
from .connect_four_buttons import BetaConnectFour
__all__: tuple[str, ...] = (
"BetaConnectFour",
"BetaTwenty48",
"BetaWordle",
"BetaTictactoe",
"MemoryGame",
"BetaRockPaperScissors",
"BetaReactionGame",
"BetaCountryGuesser",
"BetaChess",
"BetaBattleShip",
"NumberSlider",
"LightsOut",
)

View File

@@ -0,0 +1,503 @@
from __future__ import annotations
from typing import Optional, Any, Coroutine, Union
import asyncio
import string
import discord
from discord.ext import commands
from utils.emoji import TARGET
from utils.emoji import TARGET
from ..battleship import (
BattleShip,
SHIPS,
Ship,
Board,
)
from .wordle_buttons import WordInputButton
from ..utils import DiscordColor, DEFAULT_COLOR, BaseView
class Player:
def __init__(self, player: discord.User, *, game: BetaBattleShip) -> None:
self.game = game
self.player = player
self.embed = discord.Embed(title="Log", description="```\n\u200b\n```")
self._logs: list[str] = []
self.log: str = ""
self.approves_cancel: bool = False
def update_log(self, log: str) -> None:
self._logs.append(log)
log_str = "\n\n".join(self._logs[-self.game.max_log_size :])
if len(self._logs) > self.game.max_log_size:
log_str = "...\n\n" + log_str
self.embed.description = f"```diff\n{log_str}\n```"
def __getattribute__(self, name: str) -> Any:
try:
return super().__getattribute__(name)
except AttributeError:
return self.player.__getattribute__(name)
class BattleshipInput(discord.ui.Modal, title="Input a coordinate"):
def __init__(self, view: BattleshipView) -> None:
super().__init__()
self.view = view
self.coord = discord.ui.TextInput(
label="Enter your target coordinate",
placeholder="ex: a8",
style=discord.TextStyle.short,
required=True,
min_length=2,
max_length=3,
)
self.add_item(self.coord)
async def on_submit(self, interaction: discord.Interaction) -> None:
game = self.view.game
content = self.coord.value
content = content.strip().lower()
if not game.inputpat.fullmatch(content):
return await interaction.response.send_message(
f"`{content}` is not a valid coordinate!", ephemeral=True
)
else:
raw, coords = game.get_coords(content)
self.view.update_views()
if coords in self.view.player_board.moves:
return await interaction.response.send_message(
"You've attacked this coordinate before!", ephemeral=True
)
else:
await interaction.response.defer()
return await game.process_move(raw, coords)
class BattleshipButton(WordInputButton):
view: BattleshipView
async def callback(self, interaction: discord.Interaction) -> None:
game = self.view.game
if self.label == "Cancel":
player = self.view.player
other_player = (
game.player2
if interaction.user == game.player1.player
else game.player1
)
if not player.approves_cancel:
player.approves_cancel = True
await interaction.response.defer()
if not other_player.approves_cancel:
await player.send("- Waiting for opponent to approve cancellation -")
await other_player.send(
"Opponent wants to cancel, press the `Cancel` button if you approve."
)
else:
game.view1.disable_all()
game.view2.disable_all()
await game.player1.send("**GAME OVER**, Cancelled")
await game.player2.send("**GAME OVER**, Cancelled")
await game.message1.edit(view=game.view1)
await game.message2.edit(view=game.view2)
game.view1.stop()
return game.view2.stop()
else:
if interaction.user != game.turn.player:
return await interaction.response.send_message(
"It is not your turn yet!", ephemeral=True
)
else:
return await interaction.response.send_modal(BattleshipInput(self.view))
class CoordButton(discord.ui.Button["BattleshipView"]):
def __init__(self, letter_or_num: Union[str, int]) -> None:
super().__init__(
label=str(letter_or_num),
style=discord.ButtonStyle.green,
)
async def callback(self, interaction: discord.Interaction) -> None:
game = self.view.game
if self.label.isdigit():
self.view.digit = int(self.label)
raw = self.view.alpha + str(self.view.digit)
coords = (game.to_num(self.view.alpha), self.view.digit)
await interaction.response.defer()
self.view.alpha = None
self.view.digit = None
self.view.update_views()
return await game.process_move(raw, coords)
else:
self.view.alpha = self.label.lower()
self.view.initialize_view(clear=True)
return await interaction.response.edit_message(view=self.view)
class BattleshipView(BaseView):
def __init__(self, game: BetaBattleShip, user: Player, *, timeout: float) -> None:
super().__init__(timeout=timeout)
self.game = game
self.player = user
self.player_board = self.game.get_board(self.player)
self.initialize_view(start=True)
self.alpha: Optional[str] = None
self.digit: Optional[int] = None
def disable(self) -> None:
self.disable_all()
self.children[-1].disabled = False
def update_views(self) -> None:
game = self.game
self.disable()
other_view = game.view1 if game.turn == game.player2 else game.view2
other_view.clear_items()
other_view.initialize_view()
def initialize_view(self, *, clear: bool = False, start: bool = False) -> None:
moves = self.player_board.moves
if clear:
self.clear_items()
for num in range(1, 11):
button = CoordButton(num)
coord = (self.game.to_num(self.alpha), num)
if coord in moves:
button.disabled = True
self.add_item(button)
else:
for letter in string.ascii_uppercase[:10]:
button = CoordButton(letter)
if all(
(self.game.to_num(letter.lower()), i) in moves for i in range(1, 11)
):
button.disabled = True
self.add_item(button)
inpbutton = BattleshipButton()
inpbutton.label = "\u200b"
inpbutton.emoji = TARGET
self.add_item(inpbutton)
self.add_item(BattleshipButton(cancel_button=True))
if start and self.player == self.game.player2:
self.disable()
class SetupInput(discord.ui.Modal):
def __init__(self, button: SetupButton) -> None:
self.button = button
self.ship = self.button.label
super().__init__(title=f"{self.ship} Setup")
self.start_coord = discord.ui.TextInput(
label=f"Enter the starting coordinate",
placeholder="ex: a8",
style=discord.TextStyle.short,
required=True,
min_length=2,
max_length=3,
)
self.is_vertical = discord.ui.TextInput(
label=f"Do you want it to be vertical? (y/n)",
placeholder='"y" or "n"',
style=discord.TextStyle.short,
required=True,
min_length=1,
max_length=1,
)
self.add_item(self.start_coord)
self.add_item(self.is_vertical)
async def on_submit(self, interaction: discord.Interaction) -> None:
game = self.button.view.game
start = self.start_coord.value.strip().lower()
vertical = self.is_vertical.value.strip().lower()
board = game.get_board(interaction.user)
if not game.inputpat.match(start):
return await interaction.response.send_message(
f"{start} is not a valid coordinate!", ephemeral=True
)
if vertical not in ("y", "n"):
return await interaction.response.send_message(
f"Response for `vertical` must be either `y` or `n`", ephemeral=True
)
vertical = vertical != "y"
_, start = game.get_coords(start)
new_ship = Ship(
name=self.ship,
size=self.button.ship_size,
start=start,
vertical=vertical,
color=self.button.ship_color,
)
if board._is_valid(new_ship):
self.button.disabled = True
board.ships.append(new_ship)
embed, file, _, _ = await game.get_file(interaction.user, hide=False)
await interaction.response.edit_message(
attachments=[file], embed=embed, view=self.button.view
)
if all(
button.disabled
for button in self.button.view.children
if isinstance(button, discord.ui.Button)
):
await interaction.user.send(
"**All setup!** (Game will soon start after the opponent finishes)"
)
return self.button.view.stop()
else:
return await interaction.response.send_message(
"Ship placement was detected to be invalid, please try again.",
ephemeral=True,
)
class SetupButton(discord.ui.Button["SetupView"]):
def __init__(
self, label: str, ship_size: int, ship_color: tuple[int, int, int]
) -> None:
super().__init__(
label=label,
style=discord.ButtonStyle.green,
)
self.ship_size = ship_size
self.ship_color = ship_color
async def callback(self, interaction: discord.Interaction) -> None:
await interaction.response.send_modal(SetupInput(self))
class SetupView(BaseView):
def __init__(self, game: BetaBattleShip, timeout: float) -> None:
super().__init__(timeout=timeout)
self.game = game
for ship, (size, color) in SHIPS.items():
self.add_item(SetupButton(ship, size, color))
class BetaBattleShip(BattleShip):
"""
BattleShip(buttons) Game
"""
embed: discord.Embed
def __init__(
self,
player1: discord.User,
player2: discord.User,
*,
random: bool = True,
) -> None:
super().__init__(player1, player2, random=random)
self.player1: Player = Player(player1, game=self)
self.player2: Player = Player(player2, game=self)
self.turn: Player = self.player1
def get_board(self, player: discord.User, other: bool = False) -> Board:
player = getattr(player, "player", player)
if other:
return (
self.player2_board
if player == self.player1.player
else self.player1_board
)
else:
return (
self.player1_board
if player == self.player1.player
else self.player2_board
)
async def get_ship_inputs(self, user: Player) -> Coroutine[Any, Any, bool]:
embed, file, _, _ = await self.get_file(user)
embed1 = discord.Embed(
description="**Press the buttons to place your ships!**",
color=self.embed_color,
)
view = SetupView(self, timeout=self.timeout)
await user.send(file=file, embeds=[embed, embed1], view=view)
return view.wait()
async def process_move(self, raw: str, coords: tuple[int, int]):
sunk, hit = self.place_move(self.turn, coords)
next_turn = self.player2 if self.turn == self.player1 else self.player1
if hit and sunk:
self.turn.update_log(
f"+ ({raw}) was a hit!, you also sank one of their ships! :)"
)
next_turn.update_log(
f"- They went for ({raw}), and it was a hit!\n- One of your ships also got sunk! :("
)
elif hit:
self.turn.update_log(f"+ ({raw}) was a hit :)")
next_turn.update_log(f"- They went for ({raw}), and it was a hit! :(")
else:
self.turn.update_log(f"- ({raw}) was a miss :(")
next_turn.update_log(f"+ They went for ({raw}), and it was a miss! :)")
e1, f1, e2, f2 = await self.get_file(self.player1)
e3, f3, e4, f4 = await self.get_file(self.player2)
self.turn = next_turn
self.player1.embed.set_field_at(
0, name="\u200b", value=f"```yml\nturn: {self.turn.player}\n```"
)
self.player2.embed.set_field_at(
0, name="\u200b", value=f"```yml\nturn: {self.turn.player}\n```"
)
await self.message1.edit(
view=self.view1,
content="**Battleship**",
embeds=[e2, e1, self.player1.embed],
attachments=[f2, f1],
)
await self.message2.edit(
view=self.view2,
content="**Battleship**",
embeds=[e4, e3, self.player2.embed],
attachments=[f4, f3],
)
if winner := self.who_won():
await winner.send("Congrats, you won! :)")
other = self.player2 if winner == self.player1 else self.player1
await other.send("You lost, better luck next time :(")
self.view1.stop()
return self.view2.stop()
async def start(
self,
ctx: commands.Context[commands.Bot],
*,
max_log_size: int = 10,
embed_color: DiscordColor = DEFAULT_COLOR,
timeout: Optional[float] = None,
) -> tuple[discord.Message, discord.Message]:
"""
starts the battleship(buttons) game
Parameters
----------
ctx : commands.Context[commands.Bot]
the context of the invokation command
max_log_size : int, optional
indicates the length of the move log to show, by default 10
embed_color : DiscordColor, optional
the color of the game embed, by default DEFAULT_COLOR
timeout : Optional[float], optional
the timeout for the view, by default None
Returns
-------
tuple[discord.Message, discord.Message]
returns the game messages respectively
"""
self.max_log_size = max_log_size
self.timeout = timeout
self.embed_color = embed_color
await ctx.send("**Game Started!**\nI've setup the boards in your dms!")
if not self.random:
await asyncio.gather(
await self.get_ship_inputs(self.player1),
await self.get_ship_inputs(self.player2),
)
self.player1.embed.color = self.embed_color
self.player2.embed.color = self.embed_color
e1, f1, e2, f2 = await self.get_file(self.player1)
e3, f3, e4, f4 = await self.get_file(self.player2)
self.view1 = BattleshipView(self, user=self.player1, timeout=timeout)
self.view2 = BattleshipView(self, user=self.player2, timeout=timeout)
self.player1.embed.add_field(
name="\u200b", value=f"```yml\nturn: {self.turn.player}\n```"
)
self.player2.embed.add_field(
name="\u200b", value=f"```yml\nturn: {self.turn.player}\n```"
)
self.message1 = await self.player1.send(
content="**Game starting!**",
view=self.view1,
embeds=[e2, e1, self.player1.embed],
files=[f2, f1],
)
self.message2 = await self.player2.send(
content="**Game starting!**",
view=self.view2,
embeds=[e4, e3, self.player2.embed],
files=[f4, f3],
)
await asyncio.gather(
self.view1.wait(),
self.view2.wait(),
)
return self.message1, self.message2

View File

@@ -0,0 +1,140 @@
from __future__ import annotations
from typing import Optional
import discord
from discord.ext import commands
from ..chess_game import Chess
from .wordle_buttons import WordInputButton
from ..utils import DiscordColor, DEFAULT_COLOR, BaseView
class ChessInput(discord.ui.Modal, title="Make your move"):
def __init__(self, view: ChessView) -> None:
super().__init__()
self.view = view
self.move_from = discord.ui.TextInput(
label="from coordinate",
style=discord.TextStyle.short,
required=True,
min_length=2,
max_length=2,
)
self.move_to = discord.ui.TextInput(
label="to coordinate",
style=discord.TextStyle.short,
required=True,
min_length=2,
max_length=2,
)
self.add_item(self.move_from)
self.add_item(self.move_to)
async def on_submit(self, interaction: discord.Interaction) -> discord.Message:
game = self.view.game
from_coord = self.move_from.value.strip().lower()
to_coord = self.move_to.value.strip().lower()
uci = from_coord + to_coord
try:
is_valid_uci = game.board.parse_uci(uci)
except ValueError:
is_valid_uci = False
if not is_valid_uci:
return await interaction.response.send_message(
f"Invalid coordinates for move: `{from_coord} -> {to_coord}`",
ephemeral=True,
)
else:
await game.place_move(uci)
if game.board.is_game_over():
self.view.disable_all()
embed = await game.fetch_results()
self.view.stop()
else:
embed = await game.make_embed()
return await interaction.response.edit_message(embed=embed, view=self.view)
class ChessButton(WordInputButton):
view: ChessView
async def callback(self, interaction: discord.Interaction) -> None:
game = self.view.game
if interaction.user not in (game.black, game.white):
return await interaction.response.send_message(
"You are not part of this game!", ephemeral=True
)
else:
if self.label == "Cancel":
self.view.disable_all()
await interaction.message.edit(view=self.view)
await interaction.response.send_message(f"**Game Over!** Cancelled")
return self.view.stop()
else:
if interaction.user != game.turn:
return await interaction.response.send_message(
"It is not your turn yet!", ephemeral=True
)
else:
return await interaction.response.send_modal(ChessInput(self.view))
class ChessView(BaseView):
def __init__(self, game: BetaChess, *, timeout: float) -> None:
super().__init__(timeout=timeout)
self.game = game
inpbutton = ChessButton()
inpbutton.label = "Make your move!"
self.add_item(inpbutton)
self.add_item(ChessButton(cancel_button=True))
class BetaChess(Chess):
"""
Chess(buttons) Game
"""
async def start(
self,
ctx: commands.Context[commands.Bot],
*,
embed_color: DiscordColor = DEFAULT_COLOR,
timeout: Optional[float] = None,
) -> discord.Message:
"""
starts the Chess(buttons) Game
Parameters
----------
ctx : commands.Context
the context of the invokation command
embed_color : DiscordColor, optional
the color of the game embed, by default DEFAULT_COLOR
timeout : Optional[float], optional
the timeout for the view, by default None
Returns
-------
discord.Message
returns the game message
"""
self.embed_color = discord.Color.random()
embed = await self.make_embed()
self.view = ChessView(self, timeout=timeout)
self.message = await ctx.send(embed=embed, view=self.view)
await self.view.wait()
return self.message

View File

@@ -0,0 +1,113 @@
from __future__ import annotations
from typing import Optional
import discord
from discord.ext import commands
from ..connect_four import ConnectFour, BLANK
from ..utils import *
class ConnectFourButton(discord.ui.Button["ConnectFourView"]):
def __init__(self, number: int, style: discord.ButtonStyle) -> None:
self.number = number
super().__init__(
label=str(self.number),
style=style,
)
async def callback(self, interaction: discord.Interaction) -> None:
game = self.view.game
if interaction.user not in (game.red_player, game.blue_player):
return await interaction.response.send_message(
"You are not part of this game!", ephemeral=True
)
if interaction.user != game.turn:
return await interaction.response.send_message(
"It is not your turn yet!", ephemeral=True
)
if game.board[0][self.number - 1] != BLANK:
return await interaction.response.send_message(
"Selected column is full!", ephemeral=True
)
game.place_move(self.number - 1, interaction.user)
status = game.is_game_over()
embed = game.make_embed(status=status)
if status:
self.view.disable_all()
self.view.stop()
return await interaction.response.edit_message(
view=self.view,
embed=embed,
content=game.board_string(),
)
class ConnectFourView(BaseView):
game: ConnectFour
def __init__(self, game: BetaConnectFour, timeout: float) -> None:
super().__init__(timeout=timeout)
self.game = game
for i in range(1, 8):
self.add_item(ConnectFourButton(i, self.game.button_style))
class BetaConnectFour(ConnectFour):
"""
Connect-4(buttons) Game
"""
async def start(
self,
ctx: commands.Context[commands.Bot],
*,
timeout: Optional[float] = None,
button_style: discord.ButtonStyle = discord.ButtonStyle.blurple,
embed_color: DiscordColor = DEFAULT_COLOR,
) -> discord.Message:
"""
starts the Connect-4(buttons) game
Parameters
----------
ctx : commands.Context
the context of the invokation command
timeout : Optional[float], optional
the timeout for when waiting, by default None
button_style : discord.ButtonStyle, optional
the primary button style to use, by default discord.ButtonStyle.red
embed_color : DiscordColor, optional
the color of the game embed, by default DEFAULT_COLOR
Returns
-------
discord.Message
returns the game message
"""
self.embed_color = discord.Color.random()
self.button_style = button_style
self.view = ConnectFourView(self, timeout=timeout)
embed = self.make_embed(status=False)
self.message = await ctx.send(
content=self.board_string(),
view=self.view,
embed=embed,
)
await self.view.wait()
return self.message

View File

@@ -0,0 +1,166 @@
from __future__ import annotations
from typing import Optional
import discord
from discord.ext import commands
from ..country_guess import CountryGuesser
from ..utils import DiscordColor, DEFAULT_COLOR, BaseView
class CountryInput(discord.ui.Modal, title="Input your guess!"):
def __init__(self, view: CountryView) -> None:
super().__init__()
self.view = view
self.guess = discord.ui.TextInput(
label="Input your guess",
style=discord.TextStyle.short,
required=True,
max_length=self.view.game.accepted_length,
)
self.add_item(self.guess)
async def on_submit(self, interaction: discord.Interaction) -> None:
guess = self.guess.value.strip().lower()
game = self.view.game
if guess == game.country:
game.update_guesslog("+ GAME OVER, you won! +")
await interaction.response.send_message(
f"That is correct! The country was `{game.country.title()}`"
)
self.view.disable_all()
game.embed.description = f"```fix\n{game.country.title()}\n```"
await interaction.message.edit(view=self.view, embed=game.embed)
return self.view.stop()
else:
game.guesses -= 1
if not game.guesses:
self.view.disable_all()
game.update_guesslog("- GAME OVER, you lost -")
await interaction.message.edit(embed=game.embed, view=self.view)
await interaction.response.send_message(
f"Game Over! you lost, The country was `{game.country.title()}`"
)
return self.view.stop()
else:
acc = game.get_accuracy(guess)
game.update_guesslog(
f"- [{guess}] was incorrect! but you are ({acc}%) of the way there!\n"
f"+ You have {game.guesses} guesses left.\n"
)
await interaction.response.edit_message(embed=game.embed)
class CountryView(BaseView):
def __init__(
self, game: BetaCountryGuesser, *, user: discord.User, timeout: float
) -> None:
super().__init__(timeout=timeout)
self.game = game
self.user = user
async def interaction_check(self, interaction: discord.Interaction) -> bool:
if interaction.user != self.user:
await interaction.response.send_message(
f"This is not your game!", ephemeral=True
)
return False
else:
return True
@discord.ui.button(label="Make a guess!", style=discord.ButtonStyle.blurple)
async def guess_button(self, interaction: discord.Interaction, _) -> None:
return await interaction.response.send_modal(CountryInput(self))
@discord.ui.button(label="hint", style=discord.ButtonStyle.green)
async def hint_button(
self, interaction: discord.Interaction, button: discord.ui.Button
) -> None:
hint = self.game.get_hint()
self.game.hints -= 1
await interaction.response.send_message(
f"Here is your hint: `{hint}`", ephemeral=True
)
if not self.game.hints:
button.disabled = True
await interaction.message.edit(view=self)
@discord.ui.button(label="Cancel", style=discord.ButtonStyle.red)
async def cancel_button(self, interaction: discord.Interaction, _) -> None:
self.disable_all()
self.game.embed.description = f"```fix\n{self.game.country.title()}\n```"
self.game.update_guesslog("- GAME OVER, CANCELLED -")
await interaction.response.send_message(
f"Game Over! The country was `{self.game.country.title()}`"
)
await interaction.message.edit(view=self, embed=self.game.embed)
return self.stop()
class BetaCountryGuesser(CountryGuesser):
"""
Country Guesser(buttons) Game
"""
guesslog: str = ""
def update_guesslog(self, entry: str) -> None:
self.guesslog += entry + "\n"
self.embed.set_field_at(
1, name="Guess Log", value=f"```diff\n{self.guesslog}\n```"
)
async def start(
self,
ctx: commands.Context[commands.Bot],
*,
embed_color: DiscordColor = DEFAULT_COLOR,
ignore_diff_len: bool = False,
timeout: Optional[float] = None,
) -> discord.Message:
"""
starts the Country Guesser(buttons) Game
Parameters
----------
ctx : commands.Context
the context of the invokation command
embed_color : DiscordColor, optional
the color of the game embed, by default DEFAULT_COLOR
ignore_diff_len : bool, optional
specifies whether or not to ignore guesses that are not of the same length as the correct answer, by default False
timeout : Optional[float], optional
the timeout for the view, by default None
Returns
-------
discord.Message
returns the game message
"""
self.accepted_length = len(self.country) if ignore_diff_len else None
file = await self.get_country()
self.embed_color = discord.Color.random()
self.embed = self.get_embed()
self.embed.add_field(
name="Guess Log", value="```diff\n\u200b\n```", inline=False
)
self.view = CountryView(self, user=ctx.author, timeout=timeout)
self.message = await ctx.send(embed=self.embed, file=file, view=self.view)
await self.view.wait()
return self.message

View File

@@ -0,0 +1,168 @@
from __future__ import annotations
import random
from typing import Any, TYPE_CHECKING, Optional, Literal, Final
import discord
from discord.ext import commands
from .number_slider import SlideView
from ..utils import *
if TYPE_CHECKING:
from typing_extensions import TypeAlias
Board: TypeAlias = list[list[Optional[Literal["💡"]]]]
BULB: Final[Literal["💡"]] = "💡"
class LightsOutButton(discord.ui.Button["LightsOutView"]):
def __init__(
self, emoji: str, *, style: discord.ButtonStyle, row: int, col: int
) -> None:
super().__init__(
emoji=emoji,
label="\u200b",
style=style,
row=row,
)
self.col = col
async def callback(self, interaction: discord.Interaction) -> None:
game = self.view.game
if interaction.user != game.player:
return await interaction.response.send_message(
"This is not your game!", ephemeral=True
)
else:
row, col = self.row, self.col
beside_item = game.beside_item(row, col)
game.toggle(row, col)
for i, j in beside_item:
game.toggle(i, j)
self.view.update_board(clear=True)
game.moves += 1
game.embed.set_field_at(0, name="\u200b", value=f"Moves: `{game.moves}`")
if game.tiles == game.completed:
self.view.disable_all()
self.view.stop()
game.embed.description = "**Congrats! You won!**"
return await interaction.response.edit_message(
embed=game.embed, view=self.view
)
class LightsOutView(SlideView):
game: LightsOut
def __init__(self, game: LightsOut, *, timeout: float) -> None:
super().__init__(game, timeout=timeout)
def update_board(self, *, clear: bool = False) -> None:
if clear:
self.clear_items()
for i, row in enumerate(self.game.tiles):
for j, tile in enumerate(row):
button = LightsOutButton(
emoji=tile,
style=self.game.button_style,
row=i,
col=j,
)
self.add_item(button)
class LightsOut:
"""
Lights Out Game
"""
def __init__(self, count: Literal[1, 2, 3, 4, 5] = 4) -> None:
if count not in range(1, 6):
raise ValueError("Count must be an integer between 1 and 5")
self.moves: int = 0
self.count = count
self.completed: Final[Board] = [[None] * self.count for _ in range(self.count)]
self.tiles: Board = []
self.player: Optional[discord.User] = None
self.button_style: discord.ButtonStyle = discord.ButtonStyle.green
def toggle(self, row: int, col: int) -> None:
self.tiles[row][col] = BULB if self.tiles[row][col] is None else None
def beside_item(self, row: int, col: int) -> list[tuple[int, int]]:
beside = [
(row - 1, col),
(row, col - 1),
(row + 1, col),
(row, col + 1),
]
data = [
(i, j)
for i, j in beside
if i in range(self.count) and j in range(self.count)
]
return data
async def start(
self,
ctx: commands.Context[commands.Bot],
*,
button_style: discord.ButtonStyle = discord.ButtonStyle.green,
embed_color: DiscordColor = DEFAULT_COLOR,
timeout: Optional[float] = None,
) -> discord.Message:
"""
starts the Lights Out Game
Parameters
----------
ctx : commands.Context
the context of the invokation command
button_style : discord.ButtonStyle, optional
the primary button style to use, by default discord.ButtonStyle.green
embed_color : DiscordColor, optional
the color of the game embed, by default DEFAULT_COLOR
timeout : Optional[float], optional
the timeout for the view, by default None
Returns
-------
discord.Message
returns the game message
"""
self.button_style = button_style
self.player = ctx.author
self.tiles = random.choices((None, BULB), k=self.count**2)
self.tiles = chunk(self.tiles, count=self.count)
self.view = LightsOutView(self, timeout=timeout)
self.embed = discord.Embed(
description="Turn off all the tiles!", color=embed_color
)
self.embed.add_field(name="\u200b", value="Moves: `0`")
self.message = await ctx.send(embed=self.embed, view=self.view)
await double_wait(
wait_for_delete(ctx, self.message),
self.view.wait(),
)
return self.message

View File

@@ -0,0 +1,181 @@
from __future__ import annotations
from typing import Optional, ClassVar
import random
import asyncio
import discord
from discord.ext import commands
from ..utils import *
class MemoryButton(discord.ui.Button["MemoryView"]):
def __init__(self, emoji: str, *, style: discord.ButtonStyle, row: int = 0) -> None:
self.value = emoji
super().__init__(
label="\u200b",
style=style,
row=row,
)
async def callback(self, interaction: discord.Interaction) -> None:
game = self.view.game
if opened := self.view.opened:
game.moves += 1
game.embed.set_field_at(0, name="\u200b", value=f"Moves: `{game.moves}`")
self.emoji = self.value
self.disabled = True
await interaction.response.edit_message(view=self.view)
if opened.value != self.value:
await asyncio.sleep(self.view.pause_time)
opened.emoji = None
opened.disabled = False
self.emoji = None
self.disabled = False
self.view.opened = None
else:
self.view.opened = None
if all(
button.disabled
for button in self.view.children
if isinstance(button, discord.ui.Button)
):
await interaction.message.edit(
content="Game Over, Congrats!", view=self.view
)
return self.view.stop()
return await interaction.message.edit(view=self.view, embed=game.embed)
else:
self.emoji = self.value
self.view.opened = self
self.disabled = True
return await interaction.response.edit_message(view=self.view)
class MemoryView(BaseView):
board: list[list[str]]
DEFAULT_ITEMS: ClassVar[list[str]] = [
"🥝",
"🍓",
"🍹",
"🍋",
"🥭",
"🍎",
"🍊",
"🍍",
"🍑",
"🍇",
"🍉",
"🥬",
]
def __init__(
self,
game: MemoryGame,
items: list[str],
*,
button_style: discord.ButtonStyle,
pause_time: float,
timeout: Optional[float] = None,
) -> None:
super().__init__(timeout=timeout)
self.game = game
self.button_style = button_style
self.pause_time = pause_time
self.opened: Optional[MemoryButton] = None
if not items:
items = self.DEFAULT_ITEMS[:]
assert len(items) == 12
items *= 2
random.shuffle(items)
random.shuffle(items)
items.insert(12, None)
self.board = chunk(items, count=5)
for i, row in enumerate(self.board):
for item in row:
button = MemoryButton(item, style=self.button_style, row=i)
if not item:
button.disabled = True
self.add_item(button)
class MemoryGame:
"""
Memory Game
"""
def __init__(self) -> None:
self.embed_color: Optional[DiscordColor] = None
self.embed: Optional[discord.Embed] = None
self.moves: int = 0
async def start(
self,
ctx: commands.Context[commands.Bot],
*,
embed_color: DiscordColor = DEFAULT_COLOR,
items: list[str] = [],
pause_time: float = 0.7,
button_style: discord.ButtonStyle = discord.ButtonStyle.red,
timeout: Optional[float] = None,
) -> discord.Message:
"""
starts the memory game
Parameters
----------
ctx : commands.Context
the context of the invokation command
embed_color : DiscordColor, optional
the color of the game embed, by default DEFAULT_COLOR
items : list[str], optional
items to use for the game tiles, by default []
pause_time : float, optional
specifies the duration to pause for before hiding the tiles again, by default 0.7
button_style : discord.ButtonStyle, optional
the primary button style to use, by default discord.ButtonStyle.red
timeout : Optional[float], optional
the timeout for the view, by default None
Returns
-------
discord.Message
returns the game message
"""
self.embed_color = discord.Color.random()
self.embed = discord.Embed(
description="**Memory Game**", color=discord.Color.random()
)
self.embed.add_field(name="\u200b", value="Moves: `0`")
self.view = MemoryView(
game=self,
items=items,
button_style=button_style,
pause_time=pause_time,
timeout=timeout,
)
self.message = await ctx.send(embed=self.embed, view=self.view)
await double_wait(
wait_for_delete(ctx, self.message),
self.view.wait(),
)
return self.message

View File

@@ -0,0 +1,196 @@
from __future__ import annotations
from typing import TYPE_CHECKING, Optional, Literal
import random
import discord
from discord.ext import commands
from ..utils import *
if TYPE_CHECKING:
from typing_extensions import TypeAlias
Board: TypeAlias = list[list[Optional[int]]]
class SlideButton(discord.ui.Button["SlideView"]):
def __init__(self, label: str, *, style: discord.ButtonStyle, row: int) -> None:
super().__init__(
label=label,
style=style,
row=row,
)
if label == "\u200b":
self.disabled = True
async def callback(self, interaction: discord.Interaction) -> None:
game = self.view.game
if interaction.user != game.player:
return await interaction.response.send_message(
"This is not your game!", ephemeral=True
)
else:
num = int(self.label)
if num not in game.beside_blank():
return await interaction.response.defer()
else:
ix, iy = game.get_item(num)
nx, ny = game.get_item()
game.numbers[nx][ny], game.numbers[ix][iy] = (
game.numbers[ix][iy],
game.numbers[nx][ny],
)
self.view.update_board(clear=True)
game.moves += 1
game.embed.set_field_at(
0, name="\u200b", value=f"Moves: `{game.moves}`"
)
if game.numbers == game.completed:
self.view.disable_all()
self.view.stop()
game.embed.description = "**Congrats! You won!**"
return await interaction.response.edit_message(
embed=game.embed, view=self.view
)
class SlideView(BaseView):
def __init__(self, game: NumberSlider, *, timeout: float) -> None:
super().__init__(timeout=timeout)
self.game = game
self.update_board()
def update_board(self, *, clear: bool = False) -> None:
if clear:
self.clear_items()
for i, row in enumerate(self.game.numbers):
for j, number in enumerate(row):
if number == self.game.completed[i][j]:
style = self.game.correct_style
else:
style = self.game.wrong_style
button = SlideButton(
label=number or "\u200b",
style=style,
row=i,
)
self.add_item(button)
class NumberSlider:
"""
Number Slider Game
"""
def __init__(self, count: Literal[1, 2, 3, 4, 5] = 4) -> None:
if count not in range(1, 6):
raise ValueError("Count must be an integer between 1 and 5")
self.all_numbers = list(range(1, count**2))
self.player: Optional[discord.User] = None
self.moves: int = 0
self.count = count
self.numbers: Board = []
self.completed: Board = []
self.wrong_style: discord.ButtonStyle = discord.ButtonStyle.gray
self.correct_style: discord.ButtonStyle = discord.ButtonStyle.green
def get_item(self, obj: Optional[int] = None) -> tuple[int, int]:
return next(
(x, y)
for x, row in enumerate(self.numbers)
for y, item in enumerate(row)
if item == obj
)
def beside_blank(self) -> list[int]:
nx, ny = self.get_item()
beside_item = [
(nx - 1, ny),
(nx, ny - 1),
(nx + 1, ny),
(nx, ny + 1),
]
data = [
self.numbers[i][j]
for i, j in beside_item
if i in range(self.count) and j in range(self.count)
]
return data
async def start(
self,
ctx: commands.Context[commands.Bot],
*,
wrong_style: discord.ButtonStyle = discord.ButtonStyle.gray,
correct_style: discord.ButtonStyle = discord.ButtonStyle.green,
embed_color: DiscordColor = DEFAULT_COLOR,
timeout: Optional[float] = None,
) -> discord.Message:
"""
starts the number slider game
Parameters
----------
ctx : commands.Context
the context of the invokation command
wrong_style : discord.ButtonStyle, optional
the button style to use for tiles that are in the wrong spot, by default discord.ButtonStyle.gray
correct_style : discord.ButtonStyle, optional
the button style to use for tiles that are in the right spot, by default discord.ButtonStyle.green
embed_color : DiscordColor, optional
the game embedd color, by default DEFAULT_COLOR
timeout : Optional[float], optional
the timeout for the view, by default None
Returns
-------
discord.Message
returns the game message
"""
self.player = ctx.author
self.wrong_style = wrong_style
self.correct_style = correct_style
numbers = self.all_numbers[:]
random.shuffle(numbers)
random.shuffle(numbers)
numbers.append(None)
self.numbers = chunk(numbers, count=self.count)
self.completed = chunk(self.all_numbers + [None], count=self.count)
self.view = SlideView(self, timeout=timeout)
self.embed = discord.Embed(
description="Slide the tiles back in ascending order!", color=discord.Color.random()
)
self.embed.add_field(name="\u200b", value="Moves: `0`")
self.message = await ctx.send(embed=self.embed, view=self.view)
await double_wait(
wait_for_delete(ctx, self.message),
self.view.wait(),
)
return self.message

View File

@@ -0,0 +1,132 @@
from __future__ import annotations
from typing import Optional, Union
import time
import random
import asyncio
import discord
from discord.ext import commands
from ..utils import DiscordColor, DEFAULT_COLOR, BaseView
class ReactionButton(discord.ui.Button["ReactionView"]):
def __init__(self, style: discord.ButtonStyle) -> None:
super().__init__(label="\u200b", style=style)
self.edited: bool = False
self.clicked: bool = False
async def callback(self, interaction: discord.Interaction) -> None:
game = self.view.game
if game.author_only and interaction.user != game.author:
return await interaction.response.send_message(
"This game is only for the author!", ephemeral=True
)
if not self.edited or self.clicked:
return await interaction.response.defer()
else:
end_time = time.perf_counter()
elapsed = end_time - self.view.game.start_time
game.embed.description = (
f"{interaction.user.mention} reacted first in `{elapsed:.2f}s` !"
)
await interaction.response.edit_message(embed=game.embed)
self.clicked = True
return game.finished_event.set()
class ReactionView(BaseView):
game: BetaReactionGame
def __init__(
self,
game: BetaReactionGame,
*,
button_style: discord.ButtonStyle,
timeout: float,
) -> None:
super().__init__(timeout=timeout)
self.game = game
self.button_style = button_style
self.button = ReactionButton(self.button_style)
self.add_item(self.button)
class BetaReactionGame:
"""
Reaction(buttons) game
"""
async def start(
self,
ctx: commands.Context[commands.Bot],
*,
author_only: bool = False,
pause_range: tuple[float, float] = (1.0, 5.0),
start_button_style: discord.ButtonStyle = discord.ButtonStyle.blurple,
end_button_style: Union[
discord.ButtonStyle, tuple[discord.ButtonStyle, ...]
] = (discord.ButtonStyle.green, discord.ButtonStyle.red),
embed_color: DiscordColor = DEFAULT_COLOR,
timeout: Optional[float] = None,
) -> discord.Message:
"""
starts the Reaction(buttons) Game
Parameters
----------
ctx : commands.Context
the context of the invokation command
author_only : bool, optional
specifies whether or not tjhe view is only limited to the author, by default False
pause_range : tuple[float, float], optional
the time range to randomly pause for, by default (1.0, 5.0)
start_button_style : discord.ButtonStyle, optional
specifies the button style to start with, by default discord.ButtonStyle.blurple
end_button_style : Union[discord.ButtonStyle, tuple[discord.ButtonStyle, ...]], optional
specifies the button styles(s) to change to, by default (discord.ButtonStyle.green, discord.ButtonStyle.red)
embed_color : DiscordColor, optional
the color of the game embed, by default DEFAULT_COLOR
timeout : Optional[float], optional
the timeout for the view, by default None
Returns
-------
discord.Message
returns the game message
"""
self.finished_event = asyncio.Event()
self.author_only = author_only
self.author = ctx.author
self.embed = discord.Embed(
title="Reaction Game",
description=f"Click the button below, when the button changes color!",
color=discord.Color.random(),
)
self.view = ReactionView(self, button_style=start_button_style, timeout=timeout)
self.message = await ctx.send(embed=self.embed, view=self.view)
pause = random.uniform(*pause_range)
await asyncio.sleep(pause)
if isinstance(end_button_style, tuple):
self.view.button.style = random.choice(end_button_style)
else:
self.view.button.style = end_button_style
await self.message.edit(view=self.view)
self.start_time = time.perf_counter()
self.view.button.edited = True
await self.finished_event.wait()
return self.message

View File

@@ -0,0 +1,174 @@
from __future__ import annotations
from typing import Optional
import random
import discord
from utils.emoji import WARNING_ALT
from discord.ext import commands
from ..rps import RockPaperScissors
from ..utils import DiscordColor, DEFAULT_COLOR, BaseView
class RPSButton(discord.ui.Button["RPSView"]):
def __init__(self, emoji: str, *, style: discord.ButtonStyle) -> None:
super().__init__(
label="\u200b",
emoji=emoji,
style=style,
)
def get_choice(self, user: discord.User, other: bool = False) -> Optional[str]:
game = self.view.game
if other:
return game.player2_choice if user == game.player2 else game.player1_choice
else:
return game.player1_choice if user == game.player1 else game.player2_choice
async def callback(self, interaction: discord.Interaction) -> None:
game = self.view.game
players = (game.player1, game.player2) if game.player2 else (game.player1,)
if interaction.user not in players:
return await interaction.response.send_message(
"This is not your game!", ephemeral=True
)
else:
if not game.player2:
bot_choice = random.choice(game.OPTIONS)
user_choice = self.emoji.name
if user_choice == bot_choice:
game.embed.description = f"**Tie!**\nWe both picked {user_choice}"
else:
if game.check_win(bot_choice, user_choice):
game.embed.description = f"**You Won!**\nYou picked {user_choice} and I picked {bot_choice}."
else:
game.embed.description = f"**You Lost!**\nI picked {bot_choice} and you picked {user_choice}."
self.view.disable_all()
self.view.stop()
else:
if self.get_choice(interaction.user):
return await interaction.response.send_message(
"You have already chosen!", ephemeral=True
)
other_player_choice = self.get_choice(interaction.user, other=True)
if interaction.user == game.player1:
game.player1_choice = self.emoji.name
if not other_player_choice:
game.embed.description += f"\n\n{game.player1.mention} has chosen...\n*Waiting for {game.player2.mention} to choose...*"
else:
game.player2_choice = self.emoji.name
if not other_player_choice:
game.embed.description += f"\n\n{game.player2.mention} has chosen...\n*Waiting for {game.player1.mention} to choose...*"
if game.player1_choice and game.player2_choice:
if game.player1_choice == game.player2_choice:
game.embed.description = f"**Tie!**\nBoth {game.player1.mention} and {game.player2.mention} picked {game.player1_choice}."
else:
who_won = (
game.player1
if game.check_win(game.player2_choice, game.player1_choice)
else game.player2
)
game.embed.description = (
f"**{who_won.mention} Won!**"
f"\n\n{game.player1.mention} chose {game.player1_choice}."
f"\n{game.player2.mention} chose {game.player2_choice}."
)
self.view.disable_all()
self.view.stop()
return await interaction.response.edit_message(
embed=game.embed, view=self.view
)
class RPSView(BaseView):
game: BetaRockPaperScissors
def __init__(
self,
game: BetaRockPaperScissors,
*,
button_style: discord.ButtonStyle,
timeout: float,
) -> None:
super().__init__(timeout=timeout)
self.button_style = button_style
self.game = game
for option in self.game.OPTIONS:
self.add_item(RPSButton(option, style=self.button_style))
class BetaRockPaperScissors(RockPaperScissors):
"""
RockPaperScissors(buttons) game
"""
player1: discord.User
embed: discord.Embed
def __init__(self, other_player: Optional[discord.User] = None) -> None:
self.player2 = other_player
if self.player2:
self.player1_choice: Optional[str] = None
self.player2_choice: Optional[str] = None
async def start(
self,
ctx: commands.Context[commands.Bot],
*,
button_style: discord.ButtonStyle = discord.ButtonStyle.blurple,
embed_color: DiscordColor = DEFAULT_COLOR,
timeout: Optional[float] = None,
) -> discord.Message:
if ctx.author == self.player2:
embed = discord.Embed(title=f"{WARNING_ALT} Access Denied", description="You cannot play against yourself!", color=0x000000)
return await ctx.reply(embed=embed)
"""
Starts the Rock Paper Scissors (buttons) game.
Parameters
----------
ctx : commands.Context
The context of the invoking command.
button_style : discord.ButtonStyle, optional
The primary button style to use, by default discord.ButtonStyle.blurple.
embed_color : DiscordColor, optional
The color of the game embed, by default DEFAULT_COLOR.
timeout : Optional[float], optional
The timeout for the view, by default None.
Returns
-------
discord.Message
Returns the game message.
"""
self.player1 = ctx.author
self.embed = discord.Embed(
title="Rock Paper Scissors",
description="Select a button to play!",
color=discord.Color.random(),
)
self.view = RPSView(self, button_style=button_style, timeout=timeout)
self.message = await ctx.send(embed=self.embed, view=self.view)
await self.view.wait()
return self.message

View File

@@ -0,0 +1,128 @@
from __future__ import annotations
from typing import ClassVar, Optional
import discord
from discord.ext import commands
from ..tictactoe import Tictactoe
from ..utils import *
class TTTButton(discord.ui.Button["TTTView"]):
def __init__(self, label: str, style: discord.ButtonStyle, *, row: int, col: int):
super().__init__(
label=label,
style=style,
row=row,
)
self.col = col
async def callback(self, interaction: discord.Interaction) -> None:
user = interaction.user
game = self.view.game
if user not in (game.cross, game.circle):
return await interaction.response.send_message(
"You are not part of this game!", ephemeral=True
)
if user != game.turn:
return await interaction.response.send_message(
"it is not your turn!", ephemeral=True
)
self.label = game.player_to_emoji[user]
self.disabled = True
game.board[self.row][self.col] = self.label
game.turn = game.circle if user == game.cross else game.cross
tie = all(button.disabled for button in self.view.children)
if game_over := game.is_game_over(tie=tie):
if game.winning_indexes:
self.view.disable_all()
game.create_streak()
self.view.stop()
embed = game.make_embed(game_over=game_over or tie)
await interaction.response.edit_message(embed=embed, view=self.view)
class TTTView(BaseView):
def __init__(self, game: BetaTictactoe, *, timeout: float) -> None:
super().__init__(timeout=timeout)
self.game = game
for x, row in enumerate(game.board):
for y, square in enumerate(row):
button = TTTButton(
label=square,
style=self.game.button_style,
row=x,
col=y,
)
self.add_item(button)
class BetaTictactoe(Tictactoe):
"""
Tictactoe(buttons) game
"""
BLANK: ClassVar[str] = "\u200b"
CIRCLE: ClassVar[str] = "O"
CROSS: ClassVar[str] = "X"
def create_streak(self) -> None:
chunked = chunk(self.view.children, count=3)
for row, col in self.winning_indexes:
button: TTTButton = chunked[row][col]
button.style = self.win_button_style
async def start(
self,
ctx: commands.Context[commands.Bot],
button_style: discord.ButtonStyle = discord.ButtonStyle.green,
*,
embed_color: DiscordColor = DEFAULT_COLOR,
win_button_style: discord.ButtonStyle = discord.ButtonStyle.red,
timeout: Optional[float] = None,
) -> discord.Message:
"""
starts the tictactoe(buttons) game
Parameters
----------
ctx : commands.Context
the context of the invokation command
button_style : discord.ButtonStyle, optional
the primary button style to use, by default discord.ButtonStyle.green
embed_color : DiscordColor, optional
the color of the game embed, by default DEFAULT_COLOR
win_button_style : discord.ButtonStyle, optional
the button style to use to show the winning line, by default discord.ButtonStyle.red
timeout : Optional[float], optional
the timeout for the view, by default None
Returns
-------
discord.Message
returns the game message
"""
self.embed_color = discord.Color.random()
self.button_style = button_style
self.win_button_style = win_button_style
self.view = TTTView(self, timeout=timeout)
self.message = await ctx.send(embed=self.make_embed(), view=self.view)
await double_wait(
wait_for_delete(ctx, self.message, user=(self.cross, self.circle)),
self.view.wait(),
)
return self.message

View File

@@ -0,0 +1,137 @@
from __future__ import annotations
from typing import Optional, Literal
import random
import discord
from discord.ext import commands
from ..twenty_48 import Twenty48
from ..utils import DiscordColor, DEFAULT_COLOR, BaseView
class Twenty48_Button(discord.ui.Button["BaseView"]):
def __init__(self, game: BetaTwenty48, emoji: str) -> None:
self.game = game
style = (
discord.ButtonStyle.red if emoji == "⏹️" else discord.ButtonStyle.blurple
)
super().__init__(
style=style, emoji=discord.PartialEmoji(name=emoji), label="\u200b"
)
async def callback(self, interaction: discord.Interaction) -> None:
if interaction.user != self.game.player:
return await interaction.response.send_message(
"This isn't your game!", ephemeral=True
)
emoji = str(self.emoji)
if emoji == "⏹️":
self.view.stop()
return await interaction.message.delete()
elif emoji == "➡️":
self.game.move_right()
elif emoji == "⬅️":
self.game.move_left()
elif emoji == "⬇️":
self.game.move_down()
elif emoji == "⬆️":
self.game.move_up()
lost = self.game.spawn_new()
won = self.game.check_win()
if won or lost:
self.view.disable_all()
self.view.stop()
if lost:
self.game.embed = discord.Embed(
description="Game Over! You lost.",
color=self.game.embed_color,
)
if self.game._render_image:
image = await self.game.render_image()
await interaction.response.edit_message(
attachments=[image], embed=self.game.embed
)
else:
board_string = self.game.number_to_emoji()
await interaction.response.edit_message(
content=board_string, embed=self.game.embed
)
class BetaTwenty48(Twenty48):
view: discord.ui.View
"""
Twenty48(buttons) game
"""
async def start(
self,
ctx: commands.Context[commands.Bot],
*,
win_at: Literal[2048, 4096, 8192] = 8192,
timeout: Optional[float] = None,
delete_button: bool = False,
embed_color: DiscordColor = DEFAULT_COLOR,
**kwargs,
) -> discord.Message:
"""
starts the 2048(buttons) game
Parameters
----------
ctx : commands.Context
the context of the invokation command
win_at : Literal[2048, 4096, 8192], optional
the tile to stop the game / win at, by default 8192
timeout : Optional[float], optional
the timeout for the view, by default None
delete_button : bool, optional
specifies whether or not to add a stop button, by default False
embed_color : DiscordColor, optional
the color of the game embed, by default DEFAULT_COLOR
Returns
-------
discord.Message
returns the game message
"""
self.win_at = win_at
self.embed_color = discord.Color.random()
self.player = ctx.author
self.view = BaseView(timeout=timeout)
self.board[random.randrange(4)][random.randrange(4)] = 2
self.board[random.randrange(4)][random.randrange(4)] = 2
if delete_button:
self._controls.append("⏹️")
for button in self._controls:
self.view.add_item(Twenty48_Button(self, button))
if self._render_image:
image = await self.render_image()
self.message = await ctx.send(file=image, view=self.view, **kwargs)
else:
board_string = self.number_to_emoji()
self.message = await ctx.send(
content=board_string, view=self.view, **kwargs
)
await self.view.wait()
return self.message

View File

@@ -0,0 +1,139 @@
from __future__ import annotations
from typing import Optional
import discord
from discord.ext import commands
from ..wordle import Wordle
from ..utils import DiscordColor, DEFAULT_COLOR, BaseView
class WordInput(discord.ui.Modal, title="Word Input"):
word = discord.ui.TextInput(
label=f"Input your guess",
style=discord.TextStyle.short,
required=True,
min_length=5,
max_length=5,
)
def __init__(self, view: WordleView) -> None:
super().__init__()
self.view = view
async def on_submit(self, interaction: discord.Interaction) -> None:
content = self.word.value.lower()
game = self.view.game
if content not in game._valid_words:
return await interaction.response.send_message(
"That is not a valid word!", ephemeral=True
)
else:
won = game.parse_guess(content)
buf = await game.render_image()
embed = discord.Embed(title="Wordle!", color=self.view.game.embed_color)
embed.set_image(url="attachment://wordle.png")
file = discord.File(buf, "wordle.png")
if won:
await interaction.message.reply(
"Game Over! You won!", mention_author=True
)
elif lost := len(game.guesses) >= 6:
await interaction.message.reply(
f"Game Over! You lose, the word was: **{game.word}**",
mention_author=True,
)
if won or lost:
self.view.disable_all()
self.view.stop()
return await interaction.response.edit_message(
embed=embed, attachments=[file], view=self.view
)
class WordInputButton(discord.ui.Button["WordleView"]):
def __init__(self, *, cancel_button: bool = False):
super().__init__(
label="Cancel" if cancel_button else "Make a guess!",
style=discord.ButtonStyle.red
if cancel_button
else discord.ButtonStyle.blurple,
)
async def callback(self, interaction: discord.Interaction) -> None:
game = self.view.game
if interaction.user != game.player:
return await interaction.response.send_message(
"This isn't your game!", ephemeral=True
)
else:
if self.label == "Cancel":
await interaction.response.send_message(
f"Game Over! the word was: **{game.word}**"
)
await interaction.message.delete()
return self.view.stop()
else:
return await interaction.response.send_modal(WordInput(self.view))
class WordleView(BaseView):
def __init__(self, game: BetaWordle, *, timeout: float):
super().__init__(timeout=timeout)
self.game = game
self.add_item(WordInputButton())
self.add_item(WordInputButton(cancel_button=True))
class BetaWordle(Wordle):
player: discord.User
"""
Wordle(buttons) game
"""
async def start(
self,
ctx: commands.Context[commands.Bot],
*,
embed_color: DiscordColor = DEFAULT_COLOR,
timeout: Optional[float] = None,
) -> discord.Message:
"""
starts the wordle(buttons) game
Parameters
----------
ctx : commands.Context
the context of the invokation command
embed_color : DiscordColor, optional
the color of the game embed, by default DEFAULT_COLOR
timeout : Optional[float], optional
the timeout for the view, by default None
Returns
-------
discord.Message
returns the game message
"""
self.embed_color = discord.Color.random()
self.player = ctx.author
buf = await self.render_image()
embed = discord.Embed(title="Wordle!", color=self.embed_color)
embed.set_image(url="attachment://wordle.png")
self.view = WordleView(self, timeout=timeout)
self.message = await ctx.send(
embed=embed,
file=discord.File(buf, "wordle.png"),
view=self.view,
)
await self.view.wait()
return self.message