guess.ts 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081
  1. import { Router, Request, Response } from 'express';
  2. import type { WordBank, GuessResponse, CachedResult } from '@wordle/shared';
  3. import { isValidGuess, getTargetWord } from '../validate.js';
  4. import { computeFeedback } from '../feedback.js';
  5. import { computeReplays } from '@wordle/shared';
  6. import { runSolverInWorker } from '../solver-pool.js';
  7. export function createGuessRouter(wordBank: WordBank, cache: Map<number, CachedResult>): Router {
  8. const router = Router();
  9. router.post('/', (req: Request, res: Response) => {
  10. const { wordId, guess, tryNo } = req.body ?? {};
  11. // Validate request shape
  12. if (typeof wordId !== 'number' || typeof guess !== 'string' || typeof tryNo !== 'number') {
  13. res.status(400).json({ error: 'Invalid request: wordId (number), guess (string), and tryNo (number) required' });
  14. return;
  15. }
  16. if (guess.length !== 5) {
  17. res.status(400).json({ error: 'Guess must be exactly 5 letters' });
  18. return;
  19. }
  20. // Validate the guess is a real word
  21. if (!isValidGuess(wordBank, guess)) {
  22. const response: GuessResponse = { valid: false };
  23. res.json(response);
  24. return;
  25. }
  26. // Look up target word
  27. const target = getTargetWord(wordBank, wordId);
  28. if (!target) {
  29. res.status(404).json({ error: `Word ${wordId} not found` });
  30. return;
  31. }
  32. // Compute feedback
  33. const colors = computeFeedback(target.word, guess);
  34. const correct = colors.every((c) => c === 'g');
  35. const isLastAttempt = tryNo >= 6;
  36. const response: GuessResponse = {
  37. valid: true,
  38. colors,
  39. correct,
  40. };
  41. // Reveal word on loss (last attempt, not correct)
  42. if (isLastAttempt && !correct) {
  43. response.word = target.word;
  44. }
  45. // Compute difficulty and replays on final guess (win or loss)
  46. if (correct || isLastAttempt) {
  47. let result = cache.get(wordId);
  48. if (!result) {
  49. // Cache miss — compute with 3 fast solvers for immediate response
  50. result = computeReplays(target.word, wordBank.guessable);
  51. cache.set(wordId, result);
  52. // Fire background computation with all 4 solvers (including entropy)
  53. // in a worker thread so the ~14s entropy run never blocks the API.
  54. const targetWord = target.word;
  55. const guessable = wordBank.guessable;
  56. runSolverInWorker({ target: targetWord, allWords: guessable, mode: 'full' })
  57. .then((full) => { cache.set(wordId, full); })
  58. .catch((err) => { console.error('Solver worker failed:', err); });
  59. }
  60. response.difficulty = result.difficulty;
  61. response.replays = result.replays;
  62. }
  63. res.json(response);
  64. });
  65. return router;
  66. }