37.056 de indicii. Zero scrise de mână. Fiecare cuvânt din dex98 — de la APĂ (scor 4) la PINDARIC (scor 229) — are acum un hint metaforic și un scor de dificultate precis. Totul printr-un pipeline care trimite batch-uri de 100 cuvinte la Gemini 2.0 Flash Lite.
Problema
Spânzurătoarea are 5 niveluri de dificultate, de la Boboc la Academician. Fiecare nivel trebuie să selecteze cuvinte potrivite — suficient de ușoare pentru începători, suficient de grele pentru veterani. Dar fără un scor de dificultate per cuvânt, selecția e arbitrară.
Pe lângă asta, după ce alegi un cuvânt, ai nevoie de indicii progresive: hint2 (o propoziție metaforică despre cuvânt) și hint3 (indiciu în cuvinte cheie). 37.000 de cuvinte × 2 indicii fiecare = 74.000 de texte de generat.
Nu le poți scrie manual.
Pipeline-ul
Am scris un script Node.js care:
-
Deschide baza de date SQLite —
assets/hangman.db, tabeledex98cu coloanele: cuvânt, explicație, nivel, hint2, hint3, difficulty_score, categorie -
Selectează cuvintele neprocesate —
WHERE hint2 IS NULL, sortate aleator, cu limită opțională (--limit 500) -
Le împarte în batch-uri de câte 100 — fiecare batch e transformat într-un prompt JSON
-
Trimite la Gemini 3.1 Flash Lite — modelul gratuit, 15 RPM (requests per minute), 1500 RPD (requests per day)
-
Parsează răspunsul JSON — fiecare cuvânt primește
hint2+difficulty_score -
Salvează în DB — într-o tranzacție SQLite, cu
UPDATE dex98 SET hint2=?, difficulty_score=? WHERE _id=? -
Așteaptă delay-ul — 6 secunde între batch-uri să respecte limita de 10 RPM
Prompt-ul
Prompt-ul e calibrat fin. Fiecare cuvânt trimis include cuvântul, definiția și nivelul curent:
{"cuvant":"METEOROLOGIE","definitie":"știință care studiază atmosfera","nivel":4}
Modelul trebuie să întoarcă:
{"cuvant":"METEOROLOGIE","hint2":"Știința norilor și a vremii de mâine","difficulty_score":137}
hint2 e o propoziție metaforică, abstractă, care nu repetă cuvântul. Maximum 12 cuvinte. Scopul e să sugerezi sensul fără să-l dai pe față.
difficulty_score e un număr întreg între 0 și 255, fără rotunjire la multipli de 10. Am calibrat cu exemple precise:
| Cuvânt | Scor |
|---|---|
| APĂ | 4 |
| CASĂ | 7 |
| TATĂ | 6 |
| FEREASTRĂ | 42 |
| BICICLETĂ | 58 |
| METEOROLOGIE | 137 |
| PINDARIC | 229 |
| HAIDAMAC | 144 |
Factorii care cresc scorul: lungime >12 litere, litere rare (q, w, x, y, k), domeniu specializat, arhaism. Factorii care îl scad: verb la infinitiv comun, substantiv cotidian, lungime ≤5 litere.
Rate limiting
La 10 RPM și 100 cuvinte per batch, procesarea completă a 37.000 cuvinte durează ceva:
[ 0%] batch 001/370 APĂ, CASĂ, MÂNCA, TATĂ, FEREASTRĂ… ✓ 100
[ 1%] batch 002/370 BICICLETĂ, PRIETEN, METEOROLOGIE… ✓ 100
...
Am implementat trei mecanisme de siguranță:
const RPM_LIMIT = 10;
const RPD_LIMIT = 490;
const DELAY_MS = Math.ceil(60_000 / RPM_LIMIT); // 6 secunde
const RETRY_DELAY = 30_000;
- Delay între batch-uri: 6 secunde
- Retry pe 429: 3 reîncercări cu backoff de 30, 60, 90 secunde
- Limită zilnică: 490 request-uri, să nu depășească cele 1500 gratuite
Rezumatul
Pipeline-ul a procesat 37.057 cuvinte și a generat 37.056 de indicii — doar un cuvânt a rămas neprocesat (probabil un artifact). Scorurile de dificultate acoperă toată scala 2–255, cu o medie de 128.3 — exact ce îmi doream pentru o distribuție normală.
SELECT MIN(difficulty_score), MAX(difficulty_score),
ROUND(AVG(difficulty_score), 1), COUNT(*)
FROM dex98 WHERE difficulty_score IS NOT NULL;
-- 2 | 255 | 128.3 | 37056
Cum se folosește în joc
În joc, funcția getWordByDiffScore face o interogare pe baza scorului de dificultate și a categoriei selectate:
SELECT rowid, cuvant, hint2, hint3, nivel, difficulty_score
FROM dex98
WHERE difficulty_score BETWEEN ? AND ?
AND categorie IN (?)
AND rowid NOT IN (/* cuvinte deja văzute */)
ORDER BY RANDOM() LIMIT 1
Dacă nu găsește nimic în range-ul exact, lărgește căutarea cu ±30, iar ca ultimă soluție face fallback pe nivel (1–5). Fiecare cuvânt văzut e urmărit într-un cache SQLite separat (word_cache.db) ca să nu se repete în aceeași sesiune.
Pentru mine, cel mai satisfăcător moment a fost când am văzut prima dată un joc cu adevărat adaptiv — un începător primea cuvinte cu scor sub 50, iar un veteran trebuia să deslușească cuvinte de peste 200. Fără Gemini, asta ar fi însemnat să categorizez manual 37.000 de cuvinte.
Acum API-ul face treaba în jumătate de oră.
