from __future__ import annotations
import asyncio
import random
import re
from pathlib import Path
from typing import Dict , List
from snekbot . bot import Bot
from ai import AI
WORD_LIST_PATH = Path ( __file__ ) . with_suffix ( " .wordlist " )
if WORD_LIST_PATH . exists ( ) :
with WORD_LIST_PATH . open ( ) as _f :
DICTIONARY : List [ str ] = [ w . strip ( ) . lower ( ) for w in _f if len ( w . strip ( ) ) == 5 ]
else :
DICTIONARY : List [ str ] = [ ]
def squares ( secret : str , guess : str ) - > str :
""" Return 🟩🟨⬜ pattern for *guess* vs *secret*. """
res = [ " ⬜ " ] * 5
s_left = list ( secret )
g_left = list ( guess )
for i , ( g , s ) in enumerate ( zip ( g_left , s_left ) ) :
if g == s :
res [ i ] = " 🟩 "
s_left [ i ] = None
g_left [ i ] = None
for i , g in enumerate ( g_left ) :
if g and g in s_left :
res [ i ] = " 🟨 "
s_left [ s_left . index ( g ) ] = None
return " " . join ( res )
def _try_ai ( system_prompt : str , * * kwargs ) :
try :
kwargs [ " use_cache " ] = False
return AI ( timeout = 4 ) . prompt ( system_prompt , * * kwargs )
except Exception :
return None
def determine_action ( msg : str ) - > Dict [ str , str ] :
sys = (
" You are WordleBot ' s intent detector. \n "
" Reply ONLY with JSON: { ' action ' : ' new_game ' } | { ' action ' : ' guess ' , ' guess ' : ' xxxxx ' } | { ' action ' : ' unknown ' }. \n "
" If the user clearly explictely wants a new game choose new_game, else extract the first 5‑ letter word as guess. Chose unknown for anything else. \n "
f " Message: { msg } "
)
out = _try_ai ( sys , json = True )
if isinstance ( out , dict ) and out . get ( " action " ) != " unknown " :
print ( msg , out )
return out
if re . search ( r " \ b(new game|reset|restart|start) \ b " , msg . lower ( ) ) :
return { " action " : " new_game " }
m = re . search ( r " \ b[a-z] {5} \ b " , msg . lower ( ) )
return { " action " : " guess " , " guess " : m . group ( 0 ) if m else " " }
def say ( ctx : str ) - > str :
sys = (
" You are WordleBot, an upbeat, game‑ show‑ style host! Use emojis and exclamation marks, max 140 chars. \n "
" Respond ONLY with JSON: { ' text ' : ' ... ' } \n "
f " Context: { ctx } "
)
out = _try_ai ( sys , json = True )
if isinstance ( out , dict ) and " text " in out :
return out [ " text " ]
return ctx
def imagine_word ( ) - > str :
with open ( " wordle.wordlist " , " r " ) as f :
words = [ word . strip ( ) for word in f . read ( ) . split ( " \n " ) if word . strip ( ) ]
return random . choice ( words )
class WordleBot ( Bot ) :
MAX_TRIES = 6
def __init__ ( self , * args , * * kwargs ) :
super ( ) . __init__ ( * args , * * kwargs )
self . games : Dict [ str , Dict ] = { }
def _start_game ( self , chan : str ) :
self . games [ chan ] = { " word " : imagine_word ( ) , " guesses " : { } , " lost " : set ( ) }
def _add_guess ( self , chan : str , user : str , word : str ) :
self . games [ chan ] [ " guesses " ] . setdefault ( user , [ ] ) . append ( word )
def _log_state ( self , chan : str ) :
g = self . games . get ( chan )
if not g :
print ( f " [CHAN { chan } ] no active game " )
return
parts = (
[ f " word= { g [ ' word ' ] } " ]
+ [ f " { u } : { v } " for u , v in g [ " guesses " ] . items ( ) ]
+ [ f " lost: { g [ ' lost ' ] } " ]
)
print ( f " [CHAN { chan } ] " + " | " . join ( parts ) )
async def on_mention (
self , username : str , user_nick : str | None , channel_uid : str , message : str
) :
intent = determine_action ( message )
if intent [ " action " ] == " new_game " :
self . _start_game ( channel_uid )
self . _log_state ( channel_uid )
return await self . send_message (
channel_uid ,
say (
" 🎉 New round! A fresh 5‑ letter mystery awaits – fire your first shot! "
) ,
)
guess = intent . get ( " guess " , " " ) . lower ( )
if len ( guess ) != 5 or not guess . isalpha ( ) :
self . _log_state ( channel_uid )
return await self . send_message (
channel_uid , say ( " ⛔ Oops! I need a real 5‑ letter word – try again! " )
)
if channel_uid not in self . games :
self . _start_game ( channel_uid )
game = self . games [ channel_uid ]
self . _add_guess ( channel_uid , username , guess )
patt = squares ( game [ " word " ] , guess )
used = len ( game [ " guesses " ] [ username ] )
left = self . MAX_TRIES - used
if guess == game [ " word " ] :
msg = say (
f " 🏆 { guess . upper ( ) } { patt } – spotlight on { user_nick or username } ! Fancy another round? "
)
del self . games [ channel_uid ]
self . _log_state ( channel_uid )
return await self . send_message ( channel_uid , msg )
if left == 0 :
game [ " lost " ] . add ( username )
everyone = set ( game [ " guesses " ] . keys ( ) )
if game [ " lost " ] == everyone and everyone :
ans = game [ " word " ] . upper ( )
msg = say (
f " 🛑 All challengers down! The word was { ans } . Shall we spin up a new puzzle? "
)
del self . games [ channel_uid ]
self . _log_state ( channel_uid )
return await self . send_message ( channel_uid , msg )
bust_msg = say (
f " 💀 { guess . upper ( ) } { patt } . You ' re out of turns – cheer on the rest! "
)
self . _log_state ( channel_uid )
return await self . send_message ( channel_uid , bust_msg )
feedback = f " { patt } ( { left } left) "
self . _log_state ( channel_uid )
return await self . send_message ( channel_uid , feedback )
if __name__ == " __main__ " :
bot = WordleBot ( username = " wordlebot " , password = " thetopsecretpassword " )
asyncio . run ( bot . run ( ) )