diff --git a/src/modules/wordle/handlers.js b/src/modules/wordle/handlers.js index 1455b92..aa6d6cf 100644 --- a/src/modules/wordle/handlers.js +++ b/src/modules/wordle/handlers.js @@ -24,10 +24,6 @@ import wordsData from "./words-data.js"; const words = wordsData; const wordSet = makeWordSet(words); -// All replies use HTML parse mode so renderGuess/renderBoard
 blocks
-// render as a monospace code box; emoji markers then align with letter columns.
-const HTML = { parse_mode: "HTML" };
-
 /**
  * @param {import("grammy").Context} ctx
  * @returns {number|null}
@@ -90,8 +86,8 @@ export async function handleWordle(ctx, db) {
       ? `๐ŸŽ‰ Solved in ${game.guesses.length}/${MAX_GUESSES}. /wordle_new for another.`
       : game.giveup
         ? `๐Ÿณ๏ธ Gave up. Answer was ${game.target.toUpperCase()}. /wordle_new for another.`
-        : `Guess ${game.guesses.length}/${MAX_GUESSES}. Use /wordle <word>.`;
-    return ctx.reply(`${header}\n\n${renderBoard(game.guesses)}`, HTML);
+        : `Guess ${game.guesses.length}/${MAX_GUESSES}. Use \`/wordle \`.`;
+    return ctx.reply(`${header}\n\n${renderBoard(game.guesses)}`);
   }
 
   if (isFinished(game)) {
@@ -109,22 +105,20 @@ export async function handleWordle(ctx, db) {
   if (won) game.solved = true;
   await saveGame(db, subject, game);
 
-  const reply = renderGuess(results);
+  const reply = renderGuess(validated.word, results);
   if (won) {
     const s = await recordResult(db, subject, true);
     return ctx.reply(
       `${reply}\n\n๐ŸŽ‰ Solved in ${game.guesses.length}/${MAX_GUESSES}! Streak: ${s.streak}. /wordle_new for another.`,
-      HTML,
     );
   }
   if (game.guesses.length >= MAX_GUESSES) {
     await recordResult(db, subject, false);
     return ctx.reply(
       `${reply}\n\nโŒ Out of guesses. Answer was ${game.target.toUpperCase()}. /wordle_new to retry.`,
-      HTML,
     );
   }
-  return ctx.reply(`${reply}\n\nGuess ${game.guesses.length}/${MAX_GUESSES}.`, HTML);
+  return ctx.reply(`${reply}\n\nGuess ${game.guesses.length}/${MAX_GUESSES}.`);
 }
 
 /**
@@ -143,10 +137,7 @@ export async function handleNew(ctx, db) {
   }
 
   await startFreshGame(db, subject);
-  return ctx.reply(
-    `${prelude}๐Ÿ†• New round started. Use /wordle <word> to guess.`,
-    HTML,
-  );
+  return ctx.reply(`${prelude}๐Ÿ†• New round started. Use \`/wordle \` to guess.`);
 }
 
 /**
diff --git a/src/modules/wordle/render.js b/src/modules/wordle/render.js
index a35b1e6..2ff9d77 100644
--- a/src/modules/wordle/render.js
+++ b/src/modules/wordle/render.js
@@ -1,57 +1,38 @@
 /**
- * @file Render wordle comparison results as a Telegram monospace grid.
+ * @file Render wordle comparison results for Telegram.
  *
- * Each guess renders as two lines โ€” colored markers over the upper-case
- * letters โ€” wrapped in an HTML 
 block so Telegram renders it in
- * monospace and the emoji column widths line up with the letter columns.
+ * Uses the NYT Wordle share format โ€” guess word on one line, colored marker
+ * row below โ€” so there's no cross-client column-alignment dependency:
+ *
+ *   CRANE
+ *   ๐ŸŸฉ๐ŸŸจโฌœ๐ŸŸฉ๐ŸŸฉ
  *
  *   ๐ŸŸฉ correct ยท ๐ŸŸจ partial ยท โฌœ wrong
  *
- * Output strings are intended to be sent with `parse_mode: "HTML"`. All
- * game content is validated `[a-z]` so no HTML escaping is needed inside
- * the grid; only callers embedding user-controlled text around the grid
- * need to escape it.
+ * Output is plain text; no HTML parse mode required.
  */
 
 const MARKER = { correct: "๐ŸŸฉ", partial: "๐ŸŸจ", wrong: "โฌœ" };
 
-// Alignment under Telegram 
: the color markers are emoji; to guarantee
-// the letter row is the same width, render letters as emoji too. Unicode
-// block "Enclosed Alphanumeric Supplement" has emoji-class capitals at
-// U+1F170..FF189 (๐Ÿ…ฐ๐Ÿ…ฑ๐Ÿ…ฒ..๐Ÿ†‰ โ€” "Negative Squared Latin Capital Letter").
-// Because both rows draw from the emoji font, column widths match 1-to-1
-// on every client, regardless of the monospace font shipped with it.
-const EMOJI_A = 0x1f170;
-const ASCII_A = 0x41;
-
-function toEmojiLetter(ch) {
-  const code = ch.toUpperCase().charCodeAt(0);
-  if (code >= ASCII_A && code <= 0x5a) {
-    return String.fromCodePoint(EMOJI_A + (code - ASCII_A));
-  }
-  return ch;
-}
-
-function rowPair(results) {
+function rowPair({ word, results }) {
   const markers = results.map((r) => MARKER[r.result] ?? "โฌœ").join("");
-  const letters = results.map((r) => toEmojiLetter(r.letter)).join("");
-  return `${markers}\n${letters}`;
+  return `${word.toUpperCase()}\n${markers}`;
 }
 
 /**
- * Render a single guess row as an HTML 
 block.
+ * Render a single guess (word over colors).
+ * @param {string} word โ€” the submitted guess (lowercase a-z)
  * @param {ReturnType} results
  */
-export function renderGuess(results) {
-  return `
${rowPair(results)}
`; +export function renderGuess(word, results) { + return rowPair({ word, results }); } /** - * Render the full board (all prior guesses, blank-line separated) as a - * single HTML
 block, so all rows share one monospace code box.
+ * Render the full board (all prior guesses, blank-line separated).
  * @param {Array<{word:string, results: any[]}>} guesses
  */
 export function renderBoard(guesses) {
-  if (guesses.length === 0) return "No guesses yet. Reply with /wordle <word>.";
-  return `
${guesses.map((g) => rowPair(g.results)).join("\n\n")}
`; + if (guesses.length === 0) return "No guesses yet. Reply with `/wordle `."; + return guesses.map((g) => rowPair(g)).join("\n\n"); }