2024-07-05 17:19:28 -04:00
import discord
2024-07-05 22:57:00 -04:00
from discord import app_commands
2024-07-05 17:19:28 -04:00
import random
import logging
import sqlite3
from cryptography . fernet import Fernet
2024-07-05 22:57:00 -04:00
import datetime
import asyncio
2024-07-05 17:19:28 -04:00
class WordleGame :
2024-07-05 22:57:00 -04:00
def __init__ ( self , user , word , date ) :
self . user = user
self . word = word
self . date = date
2024-07-05 17:19:28 -04:00
self . guesses = [ ]
2024-07-05 22:57:00 -04:00
def guess_word ( self , guess ) :
self . guesses . append ( guess )
return self . check_guess ( guess )
2024-07-05 17:19:28 -04:00
2024-07-05 22:57:00 -04:00
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 )
2024-07-05 17:19:28 -04:00
2024-07-05 22:57:00 -04:00
def is_complete ( self ) :
return self . guesses and self . guesses [ - 1 ] == self . word
2024-07-05 17:19:28 -04:00
2024-07-05 22:57:00 -04:00
def render_board ( self ) :
return " \n " . join ( [ f " Guess { i + 1 } : { guess } -> { self . check_guess ( guess ) } " for i , guess in enumerate ( self . guesses ) ] )
2024-07-05 17:19:28 -04:00
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 '
2024-07-05 22:57:00 -04:00
self . key = self . load_key ( )
self . cipher_suite = Fernet ( self . key )
self . words = self . load_words ( )
2024-07-05 17:19:28 -04:00
self . ensure_table_exists ( )
2024-07-05 22:57:00 -04:00
def load_key ( self ) :
2024-07-05 17:19:28 -04:00
with open ( ' data/wordlist.key ' , ' rb ' ) as key_file :
2024-07-05 22:57:00 -04:00
return key_file . read ( )
2024-07-05 17:19:28 -04:00
2024-07-05 22:57:00 -04:00
def load_words ( self ) :
2024-07-05 17:19:28 -04:00
with open ( ' data/encrypted_word_list.bin ' , ' rb ' ) as f :
encrypted_words = f . read ( ) . splitlines ( )
2024-07-05 22:57:00 -04:00
return [ self . cipher_suite . decrypt ( word ) . decode ( ) for word in encrypted_words ]
2024-07-05 17:19:28 -04:00
def ensure_table_exists ( self ) :
conn = sqlite3 . connect ( self . db_path )
cursor = conn . cursor ( )
cursor . execute ( """
2024-07-05 22:57:00 -04:00
CREATE TABLE IF NOT EXISTS wordle (
2024-07-05 17:19:28 -04:00
user_id TEXT NOT NULL ,
date TEXT NOT NULL ,
2024-07-05 22:57:00 -04:00
word TEXT NOT NULL ,
2024-07-05 17:19:28 -04:00
guesses TEXT ,
2024-07-05 22:57:00 -04:00
PRIMARY KEY ( user_id , date )
2024-07-05 17:19:28 -04:00
) ;
""" )
conn . commit ( )
conn . close ( )
2024-07-05 22:57:00 -04:00
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 ]
2024-07-05 17:19:28 -04:00
2024-07-05 22:57:00 -04:00
async def start_game ( self , interaction ) :
user_id = str ( interaction . user . id )
today = datetime . date . today ( ) . isoformat ( )
word = self . get_today_word ( )
2024-07-05 17:19:28 -04:00
conn = sqlite3 . connect ( self . db_path )
cursor = conn . cursor ( )
2024-07-05 22:57:00 -04:00
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 ( )
2024-07-05 17:19:28 -04:00
conn . close ( )
2024-07-05 22:57:00 -04:00
async def record_loss ( self , user_id ) :
2024-07-05 17:19:28 -04:00
conn = sqlite3 . connect ( self . db_path )
cursor = conn . cursor ( )
2024-07-05 22:57:00 -04:00
cursor . execute ( """
INSERT INTO wordle_stats ( user_id , losses )
VALUES ( ? , 1 )
ON CONFLICT ( user_id )
DO UPDATE SET losses = losses + 1
""" , (user_id,))
2024-07-05 17:19:28 -04:00
conn . commit ( )
conn . close ( )
2024-07-05 22:57:00 -04:00
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 )
2024-07-05 17:19:28 -04:00
def setup ( bot ) :
wordle = Wordle ( bot )
wordle . setup ( bot . tree )
bot . wordle_module = wordle