"""Implementation of Wordle""" import requests from PIL import Image, ImageDraw from .game_base import DatabaseGame, BaseDrawer from gwendolyn_old.exceptions import GameNotInDatabase IMAGE_MARGIN = 90 SQUARE_SIZE = 150 SQUARE_PADDING = 25 ROW_PADDING = 30 FONT_SIZE = 130 # COLORS BACKGROUND_COLOR = "#2c2f33" TEXT_COLOR = "#eee" WRITING_BORDER_COLOR = "#999" INACTIVE_BORDER_COLOR = "#555" INCORRECT_COLOR = "#444" CORRECT_COLOR = "#0a8c20" PRESENT_COLOR = "#0f7aa8" COLORS = [INCORRECT_COLOR, PRESENT_COLOR, CORRECT_COLOR] class WordleGame(DatabaseGame): """Implementation of wordle game.""" def __init__(self, bot): super().__init__(bot, "wordle", WordleDrawer) self._API_url = "https://api.wordnik.com/v4/words.json/randomWords?" # pylint: disable=invalid-name api_key = self.bot.credentials["wordnik_key"] self._APIPARAMS = { # pylint: disable=invalid-name "hasDictionaryDef": True, "minCorpusCount": 5000, "maxCorpusCount": -1, "minDictionaryCount": 1, "maxDictionaryCount": -1, "limit": 1, "api_key": api_key } async def start(self, ctx, letters: int): if self._test_document(str(ctx.channel_id)): await ctx.send("There is already a wordle game in this channel.") self.bot.log("There was already a game going on") return params = self._APIPARAMS params["minLength"] = letters params["maxLength"] = letters word = "-" while "-" in word or "." in word: response = requests.get(self._API_url, params=params) if response.json() == []: ctx.send("Could not find a word. Try again") return word = list(response.json()[0]["word"].upper()) self.bot.log(f"Found the word \"{''.join(word)}\"") game = {"word": word, "guesses": [], "results": []} await ctx.send("Starting a wordle game.") await self._start_new(ctx.channel, game, delete=False) def _get_result(self, guess: list[str], word: list[str]): result = ["_" for _ in guess] for i, letter in enumerate(guess): if letter == word[i]: result[i] = "*" for i, letter in enumerate(guess): if letter in word and result[i] != "*": same_letter = guess[:i].count(letter) same_letter += len( [ l for j,l in enumerate(guess[i:]) if guess[i:][j] == letter and result[i:][j] == "*" ] ) if same_letter < word.count(letter): result[i] = "-" return result async def guess(self, ctx, guess: str): if not guess.isalpha(): await ctx.send("You can only use letters in your guess") return guess = list(guess.upper()) try: game = self.access_document(str(ctx.channel_id)) except GameNotInDatabase: await ctx.send("No game in channel") return if len(guess) != len(game['word']): await ctx.send( f"Your guess must be {len(game['word'])} letters long") return await ctx.send(f"Guessed {''.join(guess)}") result = self._get_result(guess, game['word']) updater = { "$set": { f"guesses.{len(game['guesses'])}": guess, f"results.{len(game['guesses'])}": result } } self._update_document(str(ctx.channel_id), updater) if result == ["*" for _ in game['word']]: await ctx.send("You guessed the word! Adding 15 GwendoBucks to your account") self.bot.money.addMoney(f"#{ctx.author_id}", 15) await self._end_game(ctx.channel) elif len(game['guesses']) == 5: await ctx.send(f"You used up all available guesses. The word was '{''.join(game['word'])}'") await self._end_game(ctx.channel) else: print(len(game['guesses'])) await self._delete_old_image(ctx.channel) await self._send_image(ctx.channel, delete=False) class WordleDrawer(BaseDrawer): def __init__(self, bot, game: WordleGame): super().__init__(bot, game) self.default_image = None self.default_color = BACKGROUND_COLOR self.default_size = (0, 0) def _get_size(self, game: dict): width = ( (len(game['word']) * SQUARE_SIZE) + ((len(game['word']) - 1) * SQUARE_PADDING) + (2 * IMAGE_MARGIN) ) height = ( (6 * SQUARE_SIZE) + (5 * ROW_PADDING) + (2 * IMAGE_MARGIN) ) size = (width, height) return size def _draw_row(self, row, drawer, font, word: str, colors: list[str] = None, border_color: str = None): if colors is None: colors = [BACKGROUND_COLOR for _ in range(len(word))] for i, letter in enumerate(word): y_pos = IMAGE_MARGIN + (row * (SQUARE_SIZE + ROW_PADDING)) x_pos = IMAGE_MARGIN + (i * (SQUARE_SIZE + SQUARE_PADDING)) top_left = (x_pos, y_pos) bottom_right = (x_pos + SQUARE_SIZE, y_pos + SQUARE_SIZE) drawer.rounded_rectangle( (top_left,bottom_right), 10, colors[i], border_color, 3 ) text_pos = ( x_pos + (SQUARE_SIZE//2), y_pos + int(SQUARE_SIZE * 0.6) ) drawer.text(text_pos, letter, TEXT_COLOR, font=font, anchor="mm") def _determine_colors(self, results): return [COLORS[["_","-","*"].index(symbol)] for symbol in results] def _draw_image(self, game: dict, image: Image.Image): drawer = ImageDraw.Draw(image) font = self._get_font(FONT_SIZE) for i, guess in enumerate(game['guesses']): colors = self._determine_colors(game['results'][i]) self._draw_row(i, drawer, font, guess, colors) if len(game["guesses"]) < 6: self._draw_row( len(game['guesses']), drawer, font, " "*len(game['word']), border_color=WRITING_BORDER_COLOR ) for i in range(5 - len(game['guesses'])): self._draw_row( len(game['guesses']) + i + 1, drawer, font, " "*len(game['word']), border_color=INACTIVE_BORDER_COLOR ) return super()._draw_image(game, image)