more words

This commit is contained in:
2026-10-24 14:29:08 -04:00
parent eae0dba308
commit f07b5fdadf
13 changed files with 3563 additions and 434 deletions

View File

@@ -1,37 +1,9 @@
import fs from 'node:fs';
import wordlistEnglish from 'wordlist-english';
import { buildWordSet, getCommonnessScore } from './lexicon-sources.mjs';
import { capWordList } from '../src/engine/word-cap.js';
// Valid 2-letter English words accepted in standard games
const validTwo = new Set([
'am', 'an', 'as', 'at', 'be', 'by', 'do', 'go', 'he', 'hi',
'if', 'in', 'is', 'it', 'me', 'my', 'no', 'of', 'oh', 'on',
'or', 'so', 'to', 'up', 'us', 'we'
]);
// Build standard clean dictionary (excluding vulgarities, numbers, non-alpha)
const cleanWordSet = new Set();
for (const raw of wordlistEnglish.english) {
const w = raw.toLowerCase().trim();
if (w.length === 2 && validTwo.has(w)) {
cleanWordSet.add(w);
} else if (w.length >= 3 && w.length <= 16 && /^[a-z]+$/.test(w)) {
cleanWordSet.add(w);
}
}
// Commonness tiers for intelligent word quality ranking
const t10 = new Set(wordlistEnglish['english/10'] || []);
const t20 = new Set(wordlistEnglish['english/20'] || []);
const t35 = new Set(wordlistEnglish['english/35'] || []);
const t50 = new Set(wordlistEnglish['english/50'] || []);
function getCommonnessScore(word) {
if (t10.has(word)) return 4;
if (t20.has(word)) return 3;
if (t35.has(word)) return 2;
if (t50.has(word)) return 1;
return 0;
}
// Clean dictionary incl. regional spellings + proper-word allowlist
// (same source as src/data/lexicon.js)
const cleanWordSet = buildWordSet();
// Build prefix set for super fast solver pruning (up to 6 letters)
const prefixSet = new Set();
@@ -117,13 +89,8 @@ export function solveBoard(quartets, maxWords = 30) {
return a.word.localeCompare(b.word);
});
// Cap words at maxWords (default: 30). Target quartiles and single-tile words
// (tiles that are words on their own) always keep their spot.
if (maxWords && words.length > maxWords) {
const reserved = words.filter(w => w.isTargetQuartile || w.tileCount === 1);
const rest = words.filter(w => !w.isTargetQuartile && w.tileCount !== 1);
words = [...reserved, ...rest.slice(0, Math.max(0, maxWords - reserved.length))];
}
// Cap words at maxWords (default: 30) while keeping a fair share of 2-tile words.
words = capWordList(words, maxWords);
// Final display sorting: Target quartiles first, then points desc, then alphabetical
words.sort((a, b) => {