| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081 |
- import { Router, Request, Response } from 'express';
- import type { WordBank, GuessResponse, CachedResult } from '@wordle/shared';
- import { isValidGuess, getTargetWord } from '../validate.js';
- import { computeFeedback } from '../feedback.js';
- import { computeReplays } from '@wordle/shared';
- import { runSolverInWorker } from '../solver-pool.js';
- export function createGuessRouter(wordBank: WordBank, cache: Map<number, CachedResult>): Router {
- const router = Router();
- router.post('/', (req: Request, res: Response) => {
- const { wordId, guess, tryNo } = req.body ?? {};
- // Validate request shape
- if (typeof wordId !== 'number' || typeof guess !== 'string' || typeof tryNo !== 'number') {
- res.status(400).json({ error: 'Invalid request: wordId (number), guess (string), and tryNo (number) required' });
- return;
- }
- if (guess.length !== 5) {
- res.status(400).json({ error: 'Guess must be exactly 5 letters' });
- return;
- }
- // Validate the guess is a real word
- if (!isValidGuess(wordBank, guess)) {
- const response: GuessResponse = { valid: false };
- res.json(response);
- return;
- }
- // Look up target word
- const target = getTargetWord(wordBank, wordId);
- if (!target) {
- res.status(404).json({ error: `Word ${wordId} not found` });
- return;
- }
- // Compute feedback
- const colors = computeFeedback(target.word, guess);
- const correct = colors.every((c) => c === 'g');
- const isLastAttempt = tryNo >= 6;
- const response: GuessResponse = {
- valid: true,
- colors,
- correct,
- };
- // Reveal word on loss (last attempt, not correct)
- if (isLastAttempt && !correct) {
- response.word = target.word;
- }
- // Compute difficulty and replays on final guess (win or loss)
- if (correct || isLastAttempt) {
- let result = cache.get(wordId);
- if (!result) {
- // Cache miss — compute with 3 fast solvers for immediate response
- result = computeReplays(target.word, wordBank.guessable);
- cache.set(wordId, result);
- // Fire background computation with all 4 solvers (including entropy)
- // in a worker thread so the ~14s entropy run never blocks the API.
- const targetWord = target.word;
- const guessable = wordBank.guessable;
- runSolverInWorker({ target: targetWord, allWords: guessable, mode: 'full' })
- .then((full) => { cache.set(wordId, full); })
- .catch((err) => { console.error('Solver worker failed:', err); });
- }
- response.difficulty = result.difficulty;
- response.replays = result.replays;
- }
- res.json(response);
- });
- return router;
- }
|