mirror of
https://github.com/tiennm99/miti99bot.git
synced 2026-09-14 10:19:57 +00:00
feat(semantle): source target pool from google-10000-english dictionary
The ~250-word hand-curated TARGET_POOL was too small for long-term play. Replaces it with a build-script-generated dictionary: - scripts/build-semantle-words.js fetches first20hours/google-10000-english (no-swears variant), filters to 4–10 ASCII letters, drops the top-200 most frequent function words, and writes src/modules/semantle/words-data.js as a static ES-module export. - wordlist.js now just re-exports that data via TARGET_POOL + pickFromPool. - package.json: new build:semantle-words script; chained into `npm run build` alongside build:wordle-data so `npm run deploy` regenerates automatically. Pool size: ~250 → 7953 words. Same ConceptNet verify-and-fallback flow, so low-quality picks still cost at most one extra concept lookup.
This commit is contained in:
+2
-1
@@ -9,8 +9,9 @@
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "wrangler dev",
|
||||
"build": "npm run build:wordle-data",
|
||||
"build": "npm run build:wordle-data && npm run build:semantle-words",
|
||||
"build:wordle-data": "node scripts/build-wordle-data.js",
|
||||
"build:semantle-words": "node scripts/build-semantle-words.js",
|
||||
"scrape:loldle-data": "node scripts/scrape-loldle-data.js",
|
||||
"deploy": "npm run build && wrangler deploy && npm run db:migrate && npm run register",
|
||||
"db:migrate": "node scripts/migrate.js",
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
#!/usr/bin/env node
|
||||
/**
|
||||
* @file build-semantle-words — fetches a common-English word list (10k
|
||||
* by Google Ngram frequency, curated by first20hours) and writes the
|
||||
* 4–10 letter alphabetic subset to src/modules/semantle/words-data.js.
|
||||
*
|
||||
* Why this list:
|
||||
* - Sorted by frequency → easy top-N trimming to drop function words
|
||||
* (`the`, `of`, `and`) that make terrible guessing targets.
|
||||
* - Already de-swear-ed. No further blocklist needed.
|
||||
* - Tiny (~90 KB raw, ~30 KB gzipped) — comfortably inside the Worker
|
||||
* size budget.
|
||||
*
|
||||
* Source: https://github.com/first20hours/google-10000-english
|
||||
* Credits: Josh Kaufman (first20hours) — list derived from Peter Norvig's
|
||||
* Google Ngram analysis.
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/build-semantle-words.js
|
||||
*/
|
||||
|
||||
import { writeFileSync } from "node:fs";
|
||||
import { resolve } from "node:path";
|
||||
|
||||
const SOURCE_URL =
|
||||
"https://raw.githubusercontent.com/first20hours/google-10000-english/master/google-10000-english-no-swears.txt";
|
||||
|
||||
// Skip the top-N most frequent words — too common, lousy puzzles.
|
||||
const SKIP_TOP_N = 200;
|
||||
const MIN_LEN = 4;
|
||||
const MAX_LEN = 10;
|
||||
|
||||
const root = resolve(import.meta.dirname, "..");
|
||||
const dst = resolve(root, "src/modules/semantle/words-data.js");
|
||||
|
||||
const res = await fetch(SOURCE_URL);
|
||||
if (!res.ok) throw new Error(`fetch failed: ${res.status} ${res.statusText}`);
|
||||
const text = await res.text();
|
||||
|
||||
const lines = text.split(/\r?\n/).map((w) => w.trim().toLowerCase());
|
||||
|
||||
// Preserve original frequency order while filtering — downstream consumers
|
||||
// can still sample uniformly, but the index itself stays a frequency rank.
|
||||
const words = Array.from(
|
||||
new Set(
|
||||
lines
|
||||
.slice(SKIP_TOP_N)
|
||||
.filter((w) => w.length >= MIN_LEN && w.length <= MAX_LEN && /^[a-z]+$/.test(w)),
|
||||
),
|
||||
);
|
||||
|
||||
if (words.length === 0) throw new Error("no words parsed from source");
|
||||
|
||||
const body = words.map((w) => ` "${w}",`).join("\n");
|
||||
const out = [
|
||||
"// Auto-generated from https://github.com/first20hours/google-10000-english",
|
||||
"// Credits: Josh Kaufman (first20hours) — common English words by Google Ngram frequency.",
|
||||
`// Filter: ${MIN_LEN}–${MAX_LEN} ASCII letters, skip top ${SKIP_TOP_N} most common.`,
|
||||
"// Regenerate with: node scripts/build-semantle-words.js",
|
||||
"export default [",
|
||||
body,
|
||||
"];",
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
writeFileSync(dst, out);
|
||||
console.log(`wrote ${dst} (${words.length} words)`);
|
||||
@@ -29,10 +29,16 @@ ignored (no cost, no stat inflation).
|
||||
carries at least one edge.
|
||||
|
||||
Because ConceptNet has no random-word endpoint, the target pool ships in
|
||||
`wordlist.js` (~250 curated English words, 4–10 letters, all alphabetic).
|
||||
`words-data.js` — ~8k common English words (4–10 ASCII letters) derived
|
||||
from Google Ngram frequency via the public
|
||||
[google-10000-english](https://github.com/first20hours/google-10000-english)
|
||||
list, with the top-200 most frequent function words stripped.
|
||||
Each new round picks locally, verifies via the concept endpoint, and falls
|
||||
back to an unverified pick after a few misses.
|
||||
|
||||
Regenerate the list with `npm run build:semantle-words` (chained into the
|
||||
main `npm run build` that `npm run deploy` invokes).
|
||||
|
||||
Every guess costs **two** ConceptNet calls (concept edges + relatedness)
|
||||
issued in parallel. Typical latency ~300–600ms round-trip from Cloudflare
|
||||
Workers; `api-client.js` enforces a 5s timeout and surfaces a "Upstream
|
||||
@@ -44,7 +50,8 @@ hiccup" message on failure.
|
||||
plus lower-level `concept` / `relatedness`) with `UpstreamError` metadata.
|
||||
Preserves the earlier word2sim response shape so the rest of the module
|
||||
didn't need rewriting.
|
||||
- `wordlist.js` — curated local target pool and `pickFromPool()`.
|
||||
- `words-data.js` — auto-generated dictionary (regenerate via `npm run build:semantle-words`).
|
||||
- `wordlist.js` — thin wrapper exposing `TARGET_POOL` and `pickFromPool()` over the dictionary.
|
||||
- `state.js` — KV persistence for game + stats. Target stored lowercased.
|
||||
- `lookup.js` — guess normalization and shape validation.
|
||||
- `format.js` — warmth-percent and emoji-bucket formatters.
|
||||
@@ -84,4 +91,5 @@ across all rounds.
|
||||
## Credits
|
||||
|
||||
- Similarity + vocabulary: [ConceptNet 5](https://conceptnet.io) by Robyn Speer et al.
|
||||
- Target dictionary: [google-10000-english](https://github.com/first20hours/google-10000-english) by Josh Kaufman, derived from Peter Norvig's Google Ngram analysis.
|
||||
- Game concept: [Semantle](https://semantle.com/) by David Turner.
|
||||
|
||||
@@ -1,75 +1,18 @@
|
||||
/**
|
||||
* @file Curated target-word pool for the semantle module.
|
||||
* @file Target-word pool for the semantle module.
|
||||
*
|
||||
* ConceptNet has no random-word endpoint, so we ship a hand-picked list of
|
||||
* common, game-friendly English nouns/verbs/adjectives (4–10 ASCII letters).
|
||||
* The list is small on purpose — every entry is a reasonable Semantle target,
|
||||
* which matters more than raw size. Expand freely as the game matures.
|
||||
*
|
||||
* Entries are lowercase and alphabetic only (the similarity endpoint accepts
|
||||
* these directly as `/c/en/<word>` concept IDs).
|
||||
* The raw dictionary lives in `words-data.js` (auto-generated by
|
||||
* `scripts/build-semantle-words.js` from the public google-10000-english
|
||||
* list, filtered to 4–10 ASCII letters with the top-200 most frequent
|
||||
* function words stripped). This module just exposes the list and a
|
||||
* uniform random pick so api-client.js can stay focused on HTTP.
|
||||
*/
|
||||
|
||||
// biome-ignore format: keep the list compact and grep-friendly
|
||||
export const TARGET_POOL = [
|
||||
// nature / geography
|
||||
"ocean", "mountain", "forest", "desert", "river", "valley", "garden", "island",
|
||||
"beach", "cave", "meadow", "glacier", "volcano", "canyon", "jungle", "lake",
|
||||
"hill", "plateau", "cliff", "harbor", "coast", "swamp", "prairie", "tundra",
|
||||
"delta", "creek", "stream", "pebble", "boulder", "horizon", "iceberg", "dune",
|
||||
// weather / time
|
||||
"winter", "summer", "autumn", "spring", "morning", "evening", "midnight",
|
||||
"sunrise", "sunset", "thunder", "rainbow", "blizzard", "breeze", "drought",
|
||||
"storm", "shadow", "twilight", "decade",
|
||||
// people / relations
|
||||
"friend", "family", "mother", "father", "brother", "sister", "child",
|
||||
"stranger", "neighbor", "partner", "sibling", "elder", "infant",
|
||||
// arts
|
||||
"music", "dance", "poem", "story", "painting", "theater", "cinema", "novel",
|
||||
"symphony", "sculpture", "sketch", "ballet", "opera", "concert",
|
||||
// objects
|
||||
"computer", "phone", "camera", "robot", "engine", "wheel", "pencil", "hammer",
|
||||
"mirror", "bicycle", "umbrella", "lantern", "compass", "anchor", "blanket",
|
||||
"candle", "cushion", "kettle", "ladder", "needle", "paper", "pillow",
|
||||
"scissors", "telescope", "throne", "vase", "window", "zipper", "bottle",
|
||||
"basket", "bridge", "tower",
|
||||
// animals
|
||||
"eagle", "tiger", "dolphin", "rabbit", "snake", "salmon", "wolf", "horse",
|
||||
"butterfly", "elephant", "panda", "falcon", "sparrow", "penguin", "octopus",
|
||||
"beetle", "crow", "dragon", "hawk", "jaguar", "kangaroo", "lion", "monkey",
|
||||
"otter", "parrot", "raccoon", "squirrel", "turtle", "whale", "bear", "fox",
|
||||
"shark",
|
||||
// food
|
||||
"apple", "bread", "cheese", "coffee", "sugar", "pepper", "potato", "orange",
|
||||
"honey", "chocolate", "cinnamon", "almond", "berry", "butter", "grape",
|
||||
"lemon", "olive", "tomato", "walnut", "yogurt", "ginger",
|
||||
// emotions / abstract
|
||||
"love", "anger", "fear", "courage", "sorrow", "wonder", "dream", "memory",
|
||||
"silence", "laughter", "hope", "delight", "regret", "trust", "justice",
|
||||
"freedom", "honor", "peace", "victory", "promise", "secret", "truth",
|
||||
"wisdom", "mystery", "destiny", "patience", "loyalty",
|
||||
// professions
|
||||
"teacher", "doctor", "artist", "farmer", "pilot", "soldier", "writer",
|
||||
"sailor", "hunter", "engineer", "chef", "dentist", "nurse", "judge",
|
||||
"scientist",
|
||||
// body
|
||||
"shoulder", "finger", "elbow", "throat", "pulse", "tongue", "ankle", "spine",
|
||||
"muscle", "nerve", "eyelid", "beard", "tooth", "thumb", "knee",
|
||||
// verbs / actions
|
||||
"gather", "wonder", "linger", "stumble", "whisper", "shimmer", "wander",
|
||||
"rescue", "gallop", "imagine", "pursue", "retreat", "thrive", "squeeze",
|
||||
"shatter", "tremble", "travel", "borrow", "descend", "inherit", "vanish",
|
||||
"forget", "invite", "resist", "settle",
|
||||
// adjectives
|
||||
"ancient", "gentle", "fragile", "massive", "glowing", "rugged", "silent",
|
||||
"frozen", "steady", "brittle", "clever", "gloomy", "cheerful", "noisy",
|
||||
"silver", "golden", "radiant", "distant", "graceful", "humble", "vivid",
|
||||
"quiet", "sacred", "sudden", "tender", "wealthy", "generous", "majestic",
|
||||
];
|
||||
import WORDS from "./words-data.js";
|
||||
|
||||
/**
|
||||
* @returns {string} — a random lowercase word from the pool.
|
||||
*/
|
||||
export const TARGET_POOL = WORDS;
|
||||
|
||||
/** @returns {string} — a random lowercase word from the pool. */
|
||||
export function pickFromPool() {
|
||||
return TARGET_POOL[Math.floor(Math.random() * TARGET_POOL.length)];
|
||||
return WORDS[Math.floor(Math.random() * WORDS.length)];
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user