Files
Mace-AIO-Discord-Bot---With…/bot/games/wordle.py
2026-07-16 11:14:48 +05:30

197 lines
7.1 KiB
Python

# ╔══════════════════════════════════════════════════════════════════╗
# ║ ║
# ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║
# ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║
# ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║
# ║ ║
# ║ © 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 pathlib
import random
import asyncio
from typing import Optional, Final
from io import BytesIO
import discord
from discord.ext import commands
from PIL import Image, ImageDraw, ImageFont
from .utils import *
BORDER: Final[int] = 40
SQ: Final[int] = 100
SPACE: Final[int] = 10
WIDTH: Final[int] = BORDER * 2 + SQ * 5 + SPACE * 4
HEIGHT: Final[int] = BORDER * 2 + SQ * 6 + SPACE * 5
GRAY: Final[tuple[int, int, int]] = (119, 123, 125)
ORANGE: Final[tuple[int, int, int]] = (200, 179, 87)
GREEN: Final[tuple[int, int, int]] = (105, 169, 99)
LGRAY: Final[tuple[int, int, int]] = (198, 201, 205)
class Wordle:
"""
Wordle Game
"""
def __init__(self, word: Optional[str] = None, *, text_size: int = 55) -> None:
self.embed_color: Optional[DiscordColor] = None
parent = pathlib.Path(__file__).parent
self._valid_words = tuple(
open(parent / "assets/words.txt", "r").read().splitlines()
)
self._text_size = text_size
self._font = ImageFont.truetype(
str(parent / "assets/HelveticaNeuBold.ttf"), self._text_size
)
self.guesses: list[list[dict[str, str]]] = []
if word:
if len(word) != 5:
raise ValueError("Word must be of length 5")
if not word.isalpha():
raise ValueError("Word must be an alphabetical string")
self.word = word
else:
self.word: str = random.choice(self._valid_words)
def parse_guess(self, guess: str) -> bool:
self.guesses.append([])
for ind, l in enumerate(guess):
if l in self.word:
color = GREEN if self.word[ind] == l else ORANGE
else:
color = GRAY
self.guesses[-1].append({"letter": l, "color": color})
return guess == self.word
@executor()
def render_image(self) -> BytesIO:
with Image.new("RGB", (WIDTH, HEIGHT), (255, 255, 255)) as img:
cursor = ImageDraw.Draw(img)
x = y = BORDER
for i in range(6):
for j in range(5):
try:
letter = self.guesses[i][j]
color = letter["color"]
act_letter = letter["letter"]
except (IndexError, KeyError):
cursor.rectangle((x, y, x + SQ, y + SQ), outline=LGRAY, width=4)
else:
cursor.rectangle((x, y, x + SQ, y + SQ), width=0, fill=color)
cursor.text(
(x + SQ / 2, y + SQ / 2),
act_letter.upper(),
font=self._font,
anchor="mm",
fill=(255, 255, 255),
)
x += SQ + SPACE
x = BORDER
y += SQ + SPACE
buf = BytesIO()
img.save(buf, "PNG")
buf.seek(0)
return buf
async def start(
self,
ctx: commands.Context[commands.Bot],
*,
timeout: Optional[float] = None,
embed_color: DiscordColor = DEFAULT_COLOR,
) -> discord.Message:
"""
starts the wordle game
Parameters
----------
ctx : commands.Context
the context of the invokation command
timeout : Optional[float], optional
the timeout for when waiting, by default None
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()
buf = await self.render_image()
embed = discord.Embed(title="Wordle!", color=discord.Color.random())
embed.set_image(url="attachment://wordle.png")
embed.set_footer(text='Say "stop" to cancel the game!')
self.message = await ctx.send(embed=embed, file=discord.File(buf, "wordle.png"))
while not ctx.bot.is_closed():
def check(m: discord.Message) -> bool:
return (
(len(m.content) == 5 or m.content.lower() == "stop")
and m.author == ctx.author
and m.channel == ctx.channel
)
try:
guess: discord.Message = await ctx.bot.wait_for(
"message", timeout=timeout, check=check
)
except asyncio.TimeoutError:
break
content = guess.content.lower()
if content == "stop":
await ctx.send(f"Game Over! cancelled, the word was: **{self.word}**")
break
if content not in self._valid_words:
await ctx.send("That is not a valid word!")
else:
won = self.parse_guess(content)
buf = await self.render_image()
await self.message.delete()
embed = discord.Embed(title="Wordle!", color=discord.Color.random())
embed.set_image(url="attachment://wordle.png")
self.message = await ctx.send(
embed=embed, file=discord.File(buf, "wordle.png")
)
if won:
await ctx.send("Game Over! You won!")
break
elif len(self.guesses) >= 6:
await ctx.send(
f"Game Over! You lose, the word was: **{self.word}**"
)
break
return self.message