selena/modules/games/wordle.py

193 lines
7.0 KiB
Python
Raw Normal View History

import discord
from discord import app_commands
import random
import logging
import sqlite3
from cryptography.fernet import Fernet
import datetime
import asyncio
class WordleGame:
def __init__(self, user, word, date):
self.user = user
self.word = word
self.date = date
self.guesses = []
def guess_word(self, guess):
self.guesses.append(guess)
return self.check_guess(guess)
def check_guess(self, guess):
feedback = [''] * 5
for i, letter in enumerate(guess):
if letter == self.word[i]:
feedback[i] = '🟩'
elif letter in self.word:
feedback[i] = '🟨'
return ''.join(feedback)
def is_complete(self):
return self.guesses and self.guesses[-1] == self.word
def render_board(self):
return "\n".join([f"Guess {i+1}: {guess} -> {self.check_guess(guess)}" for i, guess in enumerate(self.guesses)])
class Wordle:
def __init__(self, bot):
self.bot = bot
self.games = {}
self.logger = logging.getLogger('Wordle')
self.logger.setLevel(logging.DEBUG)
handler = logging.FileHandler(filename='log/selena.log', encoding='utf-8', mode='w')
handler.setFormatter(logging.Formatter('%(asctime)s:%(levelname)s:%(name)s:%(message)s'))
self.logger.addHandler(handler)
self.db_path = 'data/selena.db'
self.key = self.load_key()
self.cipher_suite = Fernet(self.key)
self.words = self.load_words()
self.ensure_table_exists()
def load_key(self):
with open('data/wordlist.key', 'rb') as key_file:
return key_file.read()
def load_words(self):
with open('data/encrypted_word_list.bin', 'rb') as f:
encrypted_words = f.read().splitlines()
return [self.cipher_suite.decrypt(word).decode() for word in encrypted_words]
def ensure_table_exists(self):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
CREATE TABLE IF NOT EXISTS wordle (
user_id TEXT NOT NULL,
date TEXT NOT NULL,
word TEXT NOT NULL,
guesses TEXT,
PRIMARY KEY (user_id, date)
);
""")
conn.commit()
conn.close()
def get_today_word(self):
today = datetime.date.today()
word_index = (today - datetime.date(2022, 1, 1)).days % len(self.words)
return self.words[word_index]
async def start_game(self, interaction):
user_id = str(interaction.user.id)
today = datetime.date.today().isoformat()
word = self.get_today_word()
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("SELECT * FROM wordle WHERE user_id = ? AND date = ?", (user_id, today))
row = cursor.fetchone()
if row:
await interaction.response.send_message("You have already played today's Wordle.", ephemeral=True)
return
else:
cursor.execute("INSERT INTO wordle (user_id, date, word) VALUES (?, ?, ?)", (user_id, today, word))
conn.commit()
conn.close()
thread = await interaction.channel.create_thread(name=f"Wordle: {interaction.user.display_name}", type=discord.ChannelType.public_thread)
self.games[thread.id] = WordleGame(interaction.user, word, today)
initial_message = await thread.send(f"{interaction.user.mention}, welcome to today's Wordle! Start guessing the 5-letter word.", view=WordleView(self.bot))
self.games[thread.id].message = initial_message
await interaction.response.send_message("Game started in a new thread!", ephemeral=True)
async def guess_word(self, interaction, guess):
game = self.games.get(interaction.channel_id)
if not game:
await interaction.response.send_message("There is no game in progress in this thread.", ephemeral=True)
return
if len(guess) != 5:
await interaction.response.send_message("Your guess must be a 5-letter word.", ephemeral=True)
return
feedback = game.guess_word(guess.lower())
if game.is_complete():
await self.update_game_message(game, interaction, f"{interaction.user.mention} guessed the word! The word was **{game.word}**.\n{game.render_board()}")
del self.games[interaction.channel_id]
await self.record_win(interaction.user.id)
await interaction.channel.send("This thread will be archived in 2 minutes.")
await asyncio.sleep(120)
await interaction.channel.archive()
else:
await self.update_game_message(game, interaction, f"{interaction.user.mention} guessed **{guess}**. Feedback: {feedback}\n{game.render_board()}")
async def update_game_message(self, game, interaction, content, view=None):
try:
await game.message.edit(content=content, view=view)
except discord.NotFound:
game.message = await interaction.channel.send(content=content, view=view)
async def record_win(self, user_id):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO wordle_stats (user_id, wins)
VALUES (?, 1)
ON CONFLICT(user_id)
DO UPDATE SET wins = wins + 1
""", (user_id,))
conn.commit()
conn.close()
async def record_loss(self, user_id):
conn = sqlite3.connect(self.db_path)
cursor = conn.cursor()
cursor.execute("""
INSERT INTO wordle_stats (user_id, losses)
VALUES (?, 1)
ON CONFLICT(user_id)
DO UPDATE SET losses = losses + 1
""", (user_id,))
conn.commit()
conn.close()
def setup(self, tree: app_commands.CommandTree):
@tree.command(name="start_wordle", description="Start a game of Wordle")
async def start_wordle_command(interaction: discord.Interaction):
await self.start_game(interaction)
if not tree.get_command("start_wordle"):
tree.add_command(start_wordle_command)
class WordleView(discord.ui.View):
def __init__(self, bot):
super().__init__(timeout=None)
self.bot = bot
@discord.ui.button(label="Guess Word", style=discord.ButtonStyle.primary)
async def guess_word_button(self, interaction: discord.Interaction, button: discord.ui.Button):
await interaction.response.send_modal(GuessInput(self.bot))
class GuessInput(discord.ui.Modal, title="Wordle Guess"):
guess = discord.ui.TextInput(label="Your Guess", placeholder="Enter a 5-letter word...", max_length=5)
def __init__(self, bot):
super().__init__()
self.bot = bot
async def on_submit(self, interaction: discord.Interaction):
await self.bot.wordle_module.guess_word(interaction, self.guess.value)
def setup(bot):
wordle = Wordle(bot)
wordle.setup(bot.tree)
bot.wordle_module = wordle