Merge pull request #106 from NikolajDanger/wordle

Wordle
This commit is contained in:
Nikolaj Gade
2023-04-11 13:31:36 +02:00
committed by GitHub
7 changed files with 264 additions and 6 deletions

View File

@ -98,6 +98,23 @@ class HangmanCog(commands.Cog):
"""Start a game of hangman.""" """Start a game of hangman."""
await self.bot.games.hangman.start(ctx) await self.bot.games.hangman.start(ctx)
class WordleCog(commands.Cog):
"""Contains all the wordle commands."""
def __init__(self, bot):
"""Initialize the cog."""
self.bot = bot
@cog_ext.cog_subcommand(**params["wordle_start"])
async def wordle_start(self, ctx, letters = 5):
"""Start a game of wordle."""
await self.bot.games.wordle.start(ctx, letters)
@cog_ext.cog_subcommand(**params["wordle_guess"])
async def wordle_guess(self, ctx, guess):
"""Start a game of wordle."""
await self.bot.games.wordle.guess(ctx, guess)
class HexCog(commands.Cog): class HexCog(commands.Cog):
"""Contains all the hex commands.""" """Contains all the hex commands."""
@ -145,3 +162,4 @@ def setup(bot):
bot.add_cog(ConnectFourCog(bot)) bot.add_cog(ConnectFourCog(bot))
bot.add_cog(HangmanCog(bot)) bot.add_cog(HangmanCog(bot))
bot.add_cog(HexCog(bot)) bot.add_cog(HexCog(bot))
bot.add_cog(WordleCog(bot))

View File

@ -758,7 +758,7 @@ class Blackjack(CardGame):
game_id = datetime.datetime.now().strftime('%Y%m%d%H%M%S') game_id = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
new_game = { new_game = {
"_id": channel, "dealer hand": dealer_hand, "dealer hand": dealer_hand,
"dealer busted": False, "game_id": game_id, "dealer busted": False, "game_id": game_id,
"dealer blackjack": (self._calc_hand_value(dealer_hand) == 21), "dealer blackjack": (self._calc_hand_value(dealer_hand) == 21),
"user hands": {}, "all standing": False, "round": 0 "user hands": {}, "all standing": False, "round": 0

View File

@ -105,9 +105,11 @@ class DatabaseGame(GameBase):
file_pointer.write(str(old_image.id)) file_pointer.write(str(old_image.id))
async def _start_new(self, channel: Messageable, new_game: dict, async def _start_new(self, channel: Messageable, new_game: dict,
buttons: list[tuple[str, list]] = None): buttons: list[tuple[str, list]] = None,
delete = True):
new_game['_id'] = str(channel.id)
self._insert_document(new_game) self._insert_document(new_game)
await self._send_image(channel, buttons) await self._send_image(channel, buttons, delete)
async def _end_game(self, channel: Messageable): async def _end_game(self, channel: Messageable):
await self._delete_old_image(channel) await self._delete_old_image(channel)
@ -200,14 +202,22 @@ class BaseDrawer():
self.resources = game.resources self.resources = game.resources
game_name = game.game_name game_name = game.game_name
self.default_image = f"{self.resources}default_images/{game_name}.png" self.default_image = f"{self.resources}default_images/{game_name}.png"
self.default_size = None
self.default_color = None
self.images_path = f"{self.resources}images/{game_name}" self.images_path = f"{self.resources}images/{game_name}"
def _draw_image(self, game: dict, table: Image.Image): def _get_size(self, game: dict):
return self.default_size
def _draw_image(self, game: dict, image: Image.Image):
pass pass
def draw(self, channel: str): def draw(self, channel: str):
game = self.game.access_document(channel) game = self.game.access_document(channel)
image = Image.open(self.default_image) if self.default_image is not None:
image = Image.open(self.default_image)
else:
image = Image.new("RGB", self._get_size(game), self.default_color)
self._draw_image(game, image) self._draw_image(game, image)
self._save_image(image, channel) self._save_image(image, channel)

View File

@ -14,6 +14,7 @@ from .blackjack import Blackjack
from .connect_four import ConnectFour from .connect_four import ConnectFour
from .hangman import Hangman from .hangman import Hangman
from .hex import HexGame from .hex import HexGame
from .wordle import WordleGame
class Games(): class Games():
@ -46,3 +47,4 @@ class Games():
self.connect_four = ConnectFour(bot) self.connect_four = ConnectFour(bot)
self.hangman = Hangman(bot) self.hangman = Hangman(bot)
self.hex = HexGame(bot) self.hex = HexGame(bot)
self.wordle = WordleGame(bot)

View File

@ -0,0 +1,201 @@
"""Implementation of Wordle"""
import requests
from PIL import Image, ImageDraw
from .game_base import DatabaseGame, BaseDrawer
from gwendolyn.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)
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)

View File

@ -383,5 +383,31 @@
"required" : "true" "required" : "true"
} }
] ]
},
"wordle_start": {
"base": "wordle",
"name" : "start",
"description": "Start a game of wordle",
"options": [
{
"name": "letters",
"description" : "How many letters the word should be",
"type": 4,
"required": "false"
}
]
},
"wordle_guess": {
"base": "wordle",
"name" : "guess",
"description": "Guess a word in wordle",
"options": [
{
"name": "guess",
"description" : "Your guess",
"type": 3,
"required": "true"
}
]
} }
} }

View File

@ -107,7 +107,8 @@ class DatabaseFuncs():
"trivia questions", "trivia questions",
"blackjack games", "blackjack games",
"connect 4 games" "connect 4 games"
"hex games" "hex games",
"wordle games"
] ]
for game_type in game_types: for game_type in game_types:
self.bot.database[game_type].delete_many({}) self.bot.database[game_type].delete_many({})