Files
tiennm99bot/plans/260424-2215-loldle-new-modes/phase-02-emoji-module.md
T

6.2 KiB

Phase 02 — Emoji module (loldle-emoji)

Context

  • Research: overview + emoji
  • Template: src/modules/loldle/ (classic)
  • Dependency: phase 01 (emojis.json written by scraper, normalize helper).

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

Modify

  • src/modules/index.js — add "loldle-emoji" to static import map.
  • wrangler.toml [vars].MODULES — append ,loldle-emoji.
  • .env.deploy (local) — append ,loldle-emoji to MODULES.

Create

  • Six files listed in Architecture above.

Delete: none.

Implementation steps

  1. Copy src/modules/loldle/state.js → src/modules/loldle-emoji/state.js. Change MAX_GUESSES = 5. No other changes needed; same KV shape.

  2. 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;
    }
    
  3. 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.

  4. Create handlers.js modelled on loldle/handlers.js:

    • Same subject resolution (getSubject).
    • Same arg parsing.
    • pickRandomChampion() picks from emojisData (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.
  5. 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) },
      ],
    };
    
  6. Register — add to src/modules/index.js import map, MODULES env.

  7. Write minimal README.md — commands table, KV prefix, data source note ("regenerated by npm run scrape:loldle-data").

  8. Run npm run dev, point a test bot at it, verify:

    • /loldle_emoji shows emojis + empty board.
    • /loldle_emoji Ahri (assuming Ahri is the answer) wins.
    • /loldle_emoji_giveup reveals answer.
    • /loldle_emoji_stats reports 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 installDispatcher without conflicts.
  • /loldle_emoji runs 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.