fertig-classic-games/server/words/wordRoutes.js

146 lines
8.1 KiB
JavaScript

import { Router } from 'express';
import fs from 'node:fs';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const WORDLIST_PATH = path.join(__dirname, '../data/wordlists/enable1.txt');
// Common words used to build a player-friendly Wordle answer pool.
// These are well-known 5-letter English words that make fair Wordle puzzles.
const COMMON_WORDS = new Set([
'ABOUT','ABOVE','ABUSE','ACTOR','ACUTE','ADMIT','ADOPT','ADULT','AFTER',
'AGAIN','AGENT','AGREE','AHEAD','ALARM','ALBUM','ALERT','ALIGN','ALIVE',
'ALLEY','ALLOW','ALONE','ALONG','ALTER','ANGEL','ANGLE','ANGRY','ANKLE',
'ANNEX','ANNOY','APART','APPLE','APPLY','APRIL','ARENA','ARGUE','ARISE',
'ARMOR','ARRAY','ARROW','ASIDE','ASKED','ASSET','AVOID','AWARD','AWARE',
'AWFUL','BADLY','BAKER','BASIC','BASIS','BATCH','BEACH','BEGAN','BEGIN',
'BEING','BELOW','BENCH','BIBLE','BIRTH','BLACK','BLADE','BLAME','BLAND',
'BLANK','BLAST','BLAZE','BLEED','BLEND','BLESS','BLIND','BLOCK','BLOOD',
'BLOWN','BOARD','BONUS','BOOST','BOUND','BRAIN','BRAND','BRAVE','BREAD',
'BREAK','BREED','BRICK','BRIDE','BRIEF','BRING','BROAD','BROKE','BROOK',
'BROWN','BRUSH','BUILD','BUILT','BUNCH','BURNT','BUYER','CACHE','CANDY',
'CARGO','CARRY','CATCH','CAUSE','CHAIN','CHAIR','CHAOS','CHARM','CHASE',
'CHEAP','CHECK','CHEEK','CHEER','CHESS','CHEST','CHIEF','CHILD','CHINA',
'CIVIC','CIVIL','CLAIM','CLASS','CLEAN','CLEAR','CLERK','CLICK','CLIFF',
'CLIMB','CLING','CLOCK','CLOSE','CLOUD','COACH','COAST','COULD','COUNT',
'COURT','COVER','CRACK','CRAFT','CRASH','CRAZY','CREAM','CREEK','CRIME',
'CRISP','CROSS','CROWD','CROWN','CRUEL','CRUSH','CURVE','CYCLE','DAILY',
'DANCE','DEATH','DEBUT','DELAY','DENSE','DEPOT','DEPTH','DERBY','DEVIL',
'DIGIT','DIRTY','DOING','DOUBT','DOUGH','DRAFT','DRAIN','DRAMA','DRANK',
'DRAWN','DREAM','DRESS','DRIED','DRIFT','DRILL','DRINK','DRIVE','DROVE',
'DRUGS','DRUMS','DRUNK','DWELL','DYING','EAGER','EARLY','EARTH','EIGHT',
'ELECT','EMAIL','EMPTY','ENEMY','ENJOY','ENTER','ENTRY','EQUAL','ERROR',
'ESSAY','EVERY','EVENT','EXACT','EXIST','EXTRA','FAINT','FAIRY','FAITH',
'FALSE','FANCY','FAULT','FEAST','FENCE','FETCH','FEVER','FEWER','FIELD',
'FIFTH','FIFTY','FIGHT','FINAL','FLAIR','FLAME','FLASH','FLEET','FLESH',
'FLOAT','FLOOD','FLOOR','FLOUR','FLUID','FOCUS','FORCE','FORGE','FORUM',
'FOUND','FRANK','FRAUD','FRESH','FRONT','FROST','FROZE','FRUIT','FULLY',
'FUNDS','FUNNY','GHOST','GIANT','GIVEN','GLASS','GLOBE','GLOOM','GLOSS',
'GLOVE','GOING','GRACE','GRADE','GRAIN','GRAND','GRANT','GRASP','GRASS',
'GRAVE','GREAT','GREEN','GREET','GRIEF','GRIND','GROAN','GROSS','GROUP',
'GROVE','GROWN','GUARD','GUESS','GUEST','GUIDE','GUILT','GUISE','GUSTO',
'HABIT','HAPPY','HARSH','HEART','HEAVY','HENCE','HINGE','HONEY','HONOR',
'HORSE','HOTEL','HOUSE','HUMAN','HUMOR','HURRY','IDEAL','IMAGE','IMPLY',
'INBOX','INDEX','INNER','INPUT','INTER','INTRO','ISSUE','JAPAN','JOINT',
'JOUST','JUDGE','JUICE','JUICY','JUMBO','KARMA','KNIFE','KNOCK','KNOWN',
'LABEL','LARGE','LASER','LATER','LAUGH','LAYER','LEARN','LEASE','LEAVE',
'LEGAL','LEVEL','LIGHT','LIMIT','LINEN','LIVER','LOCAL','LODGE','LOGIC',
'LOOSE','LOVER','LOWER','LUCKY','LUNAR','LYING','MAGIC','MAJOR','MAKER',
'MARCH','MATCH','MAYOR','MEDIA','MERIT','METAL','MIGHT','MINOR','MINUS',
'MIXED','MODEL','MONEY','MONTH','MORAL','MOTOR','MOUNT','MOUSE','MOUTH',
'MOVED','MOVIE','MUSIC','NAIVE','NEVER','NIGHT','NOISE','NORTH','NOTED',
'NOVEL','NURSE','OCCUR','OFFER','OFTEN','ONSET','OPERA','ORBIT','ORDER',
'OTHER','OUTER','OUNCE','OWNER','PAINT','PANEL','PAPER','PARTY','PASTA',
'PATCH','PAUSE','PEACE','PEARL','PEDAL','PENNY','PERCH','PHASE','PHONE',
'PHOTO','PIANO','PIECE','PILOT','PITCH','PIXEL','PIZZA','PLACE','PLAIN',
'PLANE','PLANT','PLATE','PLAZA','PLEAD','PLUCK','PLUMB','PLUME','PLUMP',
'PLUNGE','POINT','POKER','POLAR','POUND','POWER','PRESS','PRICE','PRIDE',
'PRIME','PRINT','PRIOR','PRIZE','PROBE','PROOF','PROSE','PROUD','PROVE',
'PROXY','PULSE','PUPIL','QUEEN','QUERY','QUEST','QUEUE','QUICK','QUIET',
'QUITE','QUOTA','QUOTE','RADAR','RADIO','RAISE','RALLY','RANCH','RANGE',
'RAPID','RATIO','REACH','READY','REALM','REBEL','REFER','REIGN','RELAX',
'REPLY','RIGHT','RIGID','RISKY','RIVAL','RIVER','ROBOT','ROCKY','ROMAN',
'ROUGH','ROUND','ROUTE','ROYAL','RULER','RUMOR','RURAL','SAINT','SALAD',
'SAUCE','SCALE','SCALD','SCENE','SCOUT','SCREW','SEIZE','SENSE','SERVE',
'SEVEN','SHADE','SHAKE','SHALL','SHAME','SHAPE','SHARE','SHARP','SHELF',
'SHIFT','SHIRT','SHOCK','SHOOT','SHORT','SHOUT','SIGHT','SINCE','SIXTH',
'SIXTY','SIZED','SKILL','SLEEP','SLICE','SLIDE','SLOPE','SLUMP','SMALL',
'SMART','SMELL','SMILE','SMOKE','SOLAR','SOLID','SOLVE','SORRY','SOUND',
'SOUTH','SPACE','SPARK','SPEAK','SPELL','SPEND','SPICE','SPILL','SPINE',
'SPLIT','SPOKE','SPORT','SPRAY','SQUAD','STACK','STAFF','STAGE','STAIN',
'STAKE','STALE','STALL','STAND','START','STATE','STEAL','STEEL','STEEP',
'STICK','STILL','STOCK','STOOD','STORE','STORM','STORY','STRAP','STRAW',
'STRIP','STUDY','STUFF','STYLE','SUGAR','SUITE','SUPER','SURGE','SWAMP',
'SWEAR','SWEAT','SWEEP','SWEET','SWEPT','SWIFT','SWING','SWORE','SWORN',
'TABLE','TAKEN','TASTE','TEACH','TEETH','TENSE','TERMS','THANK','THEIR',
'THEME','THERE','THESE','THICK','THING','THINK','THIRD','THOSE','THREE',
'THREW','THROW','TIGHT','TIMER','TIRED','TITLE','TODAY','TOKEN','TONAL',
'TOPIC','TOTAL','TOUCH','TOUGH','TRACE','TRACK','TRADE','TRAIL','TRAIN',
'TRAIT','TRASH','TREAT','TREND','TRIAL','TRIBE','TRICK','TRIED','TROOP',
'TRUCK','TRULY','TRUMP','TRUNK','TRUTH','TUMOR','TWICE','TWIST','TYPICAL',
'ULTRA','UNDER','UNION','UNITE','UNITY','UNTIL','UPPER','UPSET','URBAN',
'USAGE','UTTER','VALID','VALUE','VALVE','VERSE','VIDEO','VIGOR','VIRAL',
'VIRUS','VISIT','VITAL','VIVID','VOICE','VOTER','WAIST','WASTE','WATCH',
'WATER','WEARY','WEIGH','WEIRD','WHILE','WHITE','WHOLE','WHOSE','WIDER',
'WITCH','WOMAN','WOMEN','WORLD','WORRY','WORSE','WORST','WORTH','WOULD',
'WOUND','WRIST','WRITE','WROTE','YOUNG','YOURS','YOUTH','ZONAL',
]);
// ── Load word sets at startup ─────────────────────────────────────────────────
let allFiveLetterWords = null; // Set<string> — all valid 5-letter ENABLE words
let answerPool = null; // string[] — shuffleable answer list
function loadWordLists() {
if (allFiveLetterWords) return;
let raw = '';
try {
raw = fs.readFileSync(WORDLIST_PATH, 'utf8');
} catch (err) {
console.warn('[words] ENABLE word list not found, using fallback.');
raw = [...COMMON_WORDS].join('\n');
}
const enableFive = new Set(
raw.split('\n')
.map(w => w.trim().toUpperCase())
.filter(w => w.length === 5 && /^[A-Z]{5}$/.test(w)),
);
allFiveLetterWords = enableFive;
// Answer pool: prefer curated common words that are also in ENABLE;
// supplement with additional ENABLE words up to a healthy pool size.
const curated = [...COMMON_WORDS].filter(w => enableFive.has(w));
answerPool = curated.length >= 200 ? curated : [...enableFive];
console.log(`[words] loaded ${enableFive.size} valid 5-letter words, ${answerPool.length} answer candidates`);
}
loadWordLists();
// ── Router ────────────────────────────────────────────────────────────────────
const router = Router();
// GET /api/words/wordle/start
// Returns a random answer word plus the full valid-word pool for the client.
router.get('/wordle/start', (_req, res) => {
const answer = answerPool[Math.floor(Math.random() * answerPool.length)];
res.json({ answer, validWords: [...allFiveLetterWords] });
});
// POST /api/words/wordle/validate { word: string }
// Quick single-word validation (used as a lightweight alternative to the full pool).
router.post('/wordle/validate', (req, res) => {
const word = (req.body?.word ?? '').trim().toUpperCase();
if (!/^[A-Z]{5}$/.test(word)) {
return res.json({ valid: false });
}
res.json({ valid: allFiveLetterWords.has(word) });
});
export default router;