174 lines
5.6 KiB
Python
Raw Normal View History

2025-06-28 08:26:32 +02:00
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 5letter 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, gameshowstyle 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 5letter 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 5letter 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())