Update configuration and README for improved setup and security; disable default API and emoji sync, and enhance Discord permission checks in API routes.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
182
bot/games/button_games/lights_out.py
Normal file
182
bot/games/button_games/lights_out.py
Normal file
@@ -0,0 +1,182 @@
|
||||
# ╔══════════════════════════════════════════════════════════════════╗
|
||||
# ║ ║
|
||||
# ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
|
||||
# ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
|
||||
# ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
|
||||
# ║ ║
|
||||
# ║ © 2026 CodeX Devs — All Rights Reserved ║
|
||||
# ║ ║
|
||||
# ║ discord ── https://discord.gg/codexdev ║
|
||||
# ║ youtube ── https://youtube.com/@CodeXDevs ║
|
||||
# ║ github ── https://github.com/RayExo ║
|
||||
# ║ ║
|
||||
# ╚══════════════════════════════════════════════════════════════════╝
|
||||
|
||||
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
|
||||
Reference in New Issue
Block a user