202 lines
6.7 KiB
Python
202 lines
6.7 KiB
Python
"""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)
|