# ╔══════════════════════════════════════════════════════════════════╗ # ║ ║ # ║ ░█▀▀░█▀█░█▀▄░█▀▀░█░█ ░█▀▄░█▀▀░█░█░█▀▀ ║ # ║ ░█░░░█░█░█░█░█▀▀░▄▀▄ ░█░█░█▀▀░▀▄▀░▀▀█ ║ # ║ ░▀▀▀░▀▀▀░▀▀░░▀▀▀░▀░▀ ░▀▀░░▀▀▀░░▀░░▀▀▀ ║ # ║ ║ # ║ © 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