Wordle bot.
This commit is contained in:
parent
29d64f93a4
commit
cce785e975
173
bot_snek_wordle.py
Normal file
173
bot_snek_wordle.py
Normal file
@ -0,0 +1,173 @@
|
|||||||
|
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())
|
Loadingβ¦
Reference in New Issue
Block a user