6.2 KiB
Phase 02 — Emoji module (loldle-emoji)
Context
- Research: overview + emoji
- Template:
src/modules/loldle/(classic) - Dependency: phase 01 (
emojis.jsonwritten by scraper,normalizehelper).
Overview
Priority: P1 (ship first — simplest mode). Status: pending.
Guess the champion from an emoji clue. All text-rendered — Telegram renders emojis natively. No images, no audio, no DDragon.
Key insights
- Emoji sequences are handcrafted by loldle.net (not algorithmic). We only have what they ship — can't compute our own.
- Progressive reveal on loldle.net works by "unlock one emoji per wrong guess". On Telegram, we keep it simpler: show all emojis upfront, fewer guesses. Saves edit-message roundtrips.
- Reuse classic's
stats:<subject>shape verbatim.
Requirements
Functional
/loldle_emoji→ show current round or start fresh; submit a guess if<champion>arg provided./loldle_emoji_giveup→ reveal answer, record loss./loldle_emoji_stats→ show per-subject play stats.- 5 guesses per round.
- Subject = user (DM) or chat (group), same rule as classic.
- Champion name lookup identical to classic (case/space/punctuation- insensitive, unique-prefix fallback).
Non-functional
- Pure KV storage (no D1). Auto-prefixed key
loldle-emoji:game:<subject>. - Round state:
{ target, guesses, startedAt }— same shape as classic. - Champion pool comes from
emojis.json(phase 01). Only champions with a non-empty emoji string are eligible.
Architecture
src/modules/loldle-emoji/
├── index.js # { name, commands, init } export
├── handlers.js # handleEmoji, handleGiveup, handleStats
├── state.js # loadGame/saveGame/clearGame/loadStats/recordResult
├── lookup.js # findChampion over emojis.json (thin wrapper, uses util/normalize-name.js)
├── render.js # board render: emoji block + guesses list
├── emojis.json # [{ championName, emojis:"🦊✨💫" }, ...] (generated)
└── README.md # usage + data source notes
Related code files
Modify
src/modules/index.js— add"loldle-emoji"to static import map.wrangler.toml[vars].MODULES— append,loldle-emoji..env.deploy(local) — append,loldle-emojito MODULES.
Create
- Six files listed in Architecture above.
Delete: none.
Implementation steps
-
Copy
src/modules/loldle/state.js→src/modules/loldle-emoji/state.js. ChangeMAX_GUESSES = 5. No other changes needed; same KV shape. -
Create
lookup.js:import { normalize } from "../../util/normalize-name.js"; export function findChampion(pool, input) { const q = normalize(input); if (!q) return null; const exact = pool.find((c) => normalize(c.championName) === q); if (exact) return exact; const prefix = pool.filter((c) => normalize(c.championName).startsWith(q)); return prefix.length === 1 ? prefix[0] : null; } -
Create
render.js— render the current board:🎭 <emoji sequence> Guesses (<n>/<MAX>): • <name1> ❌ • <name2> ❌HTML-escape every champion name via
src/util/escape-html.js. -
Create
handlers.jsmodelled onloldle/handlers.js:- Same subject resolution (
getSubject). - Same arg parsing.
pickRandomChampion()picks fromemojisData(skip champions with empty emoji string, if any).- Win / loss / giveup messages reuse classic's tone; swap "classic" → "emoji" in copy. No stickers for v1 (YAGNI — can add later).
- On win: "🎉 Got it! — solved in Nx/5" + stats update + KV clear.
- On loss: "❌ Answer was " + stats update + KV clear.
- Same subject resolution (
-
Create
index.js:import { handleGiveup, handleEmoji, handleStats } from "./handlers.js"; let db = null; export default { name: "loldle-emoji", init: async ({ db: store }) => { db = store; }, commands: [ { name: "loldle_emoji", visibility: "public", description: "Emoji loldle — guess the champion from emojis", handler: (ctx) => handleEmoji(ctx, db) }, { name: "loldle_emoji_giveup", visibility: "public", description: "Reveal the current emoji answer", handler: (ctx) => handleGiveup(ctx, db) }, { name: "loldle_emoji_stats", visibility: "public", description: "Show your emoji stats (wins, streak)", handler: (ctx) => handleStats(ctx, db) }, ], }; -
Register — add to
src/modules/index.jsimport map, MODULES env. -
Write minimal README.md — commands table, KV prefix, data source note ("regenerated by
npm run scrape:loldle-data"). -
Run
npm run dev, point a test bot at it, verify:/loldle_emojishows emojis + empty board./loldle_emoji Ahri(assuming Ahri is the answer) wins./loldle_emoji_giveupreveals answer./loldle_emoji_statsreports play counts.
Todo
- Create folder + 6 files per Architecture
- Copy state.js from classic, lower MAX_GUESSES to 5
- Import normalize helper from util
- Render board with HTML-escape
- Handlers ported from classic, tone tweaked
- Register in
src/modules/index.js+ MODULES env - Write README
- Local smoke-test (
npm run dev+ test bot)
Success criteria
- Module loads at
installDispatcherwithout conflicts. /loldle_emojiruns end-to-end against loldle.net data.- Stats persist across rounds, isolated from classic loldle.
Risks
| Risk | Mitigation |
|---|---|
emojis.json missing some champions |
Pool is whatever loldle.net provides; guard null at render time |
| Emoji rendering differs across Telegram clients | Use standard Unicode emojis (loldle.net already does); no skin-tone variants |
| Player confusion vs classic (two loldle-like commands) | Distinct command name + distinct welcome copy |
Security
- Input passes through
normalize(strips non-alphanum) before comparison. - HTML escape all user-submitted and champion-name text in reply.
Next steps
Phase 06 covers tests. Phase 03 (quote) can be built in parallel — same template.