Files
miti99bot/src/modules/semantle/README.md
T
tiennm99 b459cc9ee7 refactor(semantle): swap word2sim backend for ConceptNet
ConceptNet provides a free public /relatedness endpoint (returns cosine-like
[-1, 1]) and /c/en/{term} for vocabulary check. No random-word endpoint, so
we ship a curated local target pool in wordlist.js (~250 words) and verify
each pick via the concept endpoint with a fallback to an unverified pick.

Each guess now makes two parallel ConceptNet calls (concept + relatedness)
instead of a single word2sim call. Slightly higher latency but zero hosting
cost and no dependency on the self-hosted word2sim instance.

- api-client.js rewritten; UpstreamError replaces Word2SimError (aliased
  for backwards compat with older imports).
- wordlist.js added (curated target pool + pickFromPool).
- handlers.js: drops RANDOM_FILTERS (no filtering needed; pool is curated).
- index.js: drops WORD2SIM_API_URL env var; ConceptNet base hardcoded.
- wrangler.toml + .dev.vars.example: drop WORD2SIM_API_URL.
- api-client tests rewritten for ConceptNet shape; total tests 336 → 341.
2026-04-22 23:07:54 +07:00

3.7 KiB
Raw Blame History

Semantle Module

Semantic-similarity guessing game. A secret word is picked from a local curated pool and validated against ConceptNet; each guess is scored by ConceptNet's relatedness API against the target. Unlimited guesses per round — you play until you get the exact word (case-insensitive).

Commands

Command Visibility Description
/semantle public Show current board or submit a word guess
/semantle_giveup public Reveal the answer and end the round (next /semantle starts a fresh one)
/semantle_stats public Show wins / best count / averages

Submit with /semantle <word> (e.g. /semantle ocean). Matching is case-insensitive. Out-of-vocabulary words don't count toward the guess tally. Repeating a prior guess replies with a 🔁 already guessed notice and is ignored (no cost, no stat inflation).

Data source

ConceptNet 5 — free public API, no auth, ~300k English concepts including multi-word phrases. Two endpoints:

  • GET /relatedness?node1=/c/en/X&node2=/c/en/Y — per-guess similarity, returns { value: number ∈ [-1, 1] }.
  • GET /c/en/{term} — vocabulary check: term is in vocab iff the response carries at least one edge.

Because ConceptNet has no random-word endpoint, the target pool ships in wordlist.js (~250 curated English words, 410 letters, all alphabetic). Each new round picks locally, verifies via the concept endpoint, and falls back to an unverified pick after a few misses.

Every guess costs two ConceptNet calls (concept edges + relatedness) issued in parallel. Typical latency ~300600ms round-trip from Cloudflare Workers; api-client.js enforces a 5s timeout and surfaces a "Upstream hiccup" message on failure.

Architecture

  • api-client.js — ConceptNet HTTP wrapper (randomWord, similarity, 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().
  • 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.
  • render.js — Telegram HTML <pre> monospace board, sorted by similarity desc, capped at top 15 rows to stay under Telegram's message-length limit.
  • handlers.js — subject resolution (user in DMs, chat in groups) + the three command entry points.

Subject resolution: private chats track per-user games; groups track per-chat shared games. Mirrors loldle/wordle.

Storage

KV namespace prefix: semantle:

Key Value
game:<subject> { target, startedAt, solved, guesses[] } — active round (TTL 7 days). target stored lowercased.
stats:<subject> { played, solved, totalGuesses, bestGuessCount, lastResultAt }

Each guesses[] entry is { word, canonical, similarity }. The canonical form is lowercased on write so the solve check is a single string compare.

Config

No env vars. ConceptNet's public API base (https://api.conceptnet.io) is hardcoded in api-client.js; pass an override to createClient(url) if you need to point at a mirror or test double.

Why unlimited guesses?

Classic Semantle offers up to 100s of guesses per day, and the fun is in the hunt — not the timer. We keep rounds open indefinitely (TTL 7 days on KV) and measure skill via bestGuessCount, the fewest guesses to solve across all rounds.

Credits

  • Similarity + vocabulary: ConceptNet 5 by Robyn Speer et al.
  • Game concept: Semantle by David Turner.