From 89924249474e8034f987cbf86d2b7a176c7d7b10 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Wed, 22 Apr 2026 13:33:46 +0700 Subject: [PATCH] refactor(loldle): store only championNames in KV, recompute rows on render MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Round state now keeps `guesses` as a plain string[] (the names the player tried) instead of caching full comparison results. The board view rehydrates rows at display time by re-running compareChampions against the current target. Smaller KV payloads, and the rendered board always reflects the live champions.json — useful if a weekly data refresh lands mid-round. --- src/modules/loldle/handlers.js | 24 +++++++++++++++++++++--- src/modules/loldle/state.js | 10 +++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/src/modules/loldle/handlers.js b/src/modules/loldle/handlers.js index 66ccd2f..32dc46a 100644 --- a/src/modules/loldle/handlers.js +++ b/src/modules/loldle/handlers.js @@ -55,6 +55,23 @@ function isFinished(game) { return game.solved || game.giveup || game.guesses.length >= MAX_GUESSES; } +/** + * Recompute comparison rows for each stored guess against the current target. + * Guesses that no longer resolve (e.g. champion removed from loldle.net) are + * dropped silently — an edge case for stale rounds spanning a data refresh. + */ +function rehydrateGuesses(game) { + const target = champions.find((c) => c.championName === game.target); + if (!target) return []; + const rows = []; + for (const name of game.guesses) { + const guess = champions.find((c) => c.championName === name); + if (!guess) continue; + rows.push({ champion: name, results: compareChampions(guess, target) }); + } + return rows; +} + /** * Load existing round, or create + persist a fresh random one. * A previously-finished round is discarded and replaced with a fresh one so @@ -112,13 +129,14 @@ export async function handleLoldle(ctx, db) { if (!arg) { const header = `Guess ${game.guesses.length}/${MAX_GUESSES}. Use /loldle <champion>.`; - return ctx.reply(`${header}\n\n${renderBoard(game.guesses)}`, { parse_mode: "HTML" }); + const board = renderBoard(rehydrateGuesses(game)); + return ctx.reply(`${header}\n\n${board}`, { parse_mode: "HTML" }); } const guess = findChampion(champions, arg); if (!guess) return ctx.reply(`Champion not found: "${arg}".`); - if (game.guesses.some((g) => g.champion === guess.championName)) { + if (game.guesses.includes(guess.championName)) { return ctx.reply( `🔁 ${escapeHtml(guess.championName)} was already guessed this round — try another champion.`, { parse_mode: "HTML" }, @@ -134,7 +152,7 @@ export async function handleLoldle(ctx, db) { ); } const results = compareChampions(guess, target); - game.guesses.push({ champion: guess.championName, results }); + game.guesses.push(guess.championName); const won = guess.championName === target.championName; if (won) game.solved = true; await saveGame(db, subject, game); diff --git a/src/modules/loldle/state.js b/src/modules/loldle/state.js index be39e9d..d4b7a1f 100644 --- a/src/modules/loldle/state.js +++ b/src/modules/loldle/state.js @@ -5,8 +5,12 @@ * can /loldle_giveup to reveal (a fresh round auto-starts). Streak = consecutive wins. * * Key layout (inside module-prefixed store): - * game: -> { target, guesses[], solved, giveup, startedAt } + * game: -> { target, guesses[championName...], solved, giveup, startedAt } * stats: -> { played, wins, streak, bestStreak, lastResultAt } + * + * Only championName strings are stored in `guesses` — comparison rows are + * recomputed at render time from the live champions.json. This keeps payloads + * tiny and avoids stale results if loldle.net data shifts mid-round. */ const MAX_GUESSES = 8; @@ -20,8 +24,8 @@ const statsKey = (subject) => `stats:${subject}`; /** * @typedef {object} GameState - * @property {string} target — champion id - * @property {Array<{champion:string, results:any[]}>} guesses + * @property {string} target — championName of the hidden champion + * @property {string[]} guesses — championNames already tried this round * @property {boolean} solved * @property {boolean} [giveup] * @property {number} [startedAt] — epoch ms