{"id":51321540,"url":"https://github.com/agea/cruciverba","last_synced_at":"2026-07-01T14:01:33.097Z","repository":{"id":365242927,"uuid":"1270872984","full_name":"agea/cruciverba","owner":"agea","description":null,"archived":false,"fork":false,"pushed_at":"2026-06-23T19:35:02.000Z","size":2548,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-23T21:21:22.945Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"https://agea.github.io/cruciverba/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/agea.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-16T06:02:08.000Z","updated_at":"2026-06-23T19:35:08.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/agea/cruciverba","commit_stats":null,"previous_names":["agea/cruciverba"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/agea/cruciverba","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agea%2Fcruciverba","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agea%2Fcruciverba/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agea%2Fcruciverba/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agea%2Fcruciverba/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/agea","download_url":"https://codeload.github.com/agea/cruciverba/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/agea%2Fcruciverba/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35009272,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-01T14:01:32.290Z","updated_at":"2026-07-01T14:01:33.089Z","avatar_url":"https://github.com/agea.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Cruciverba 🇮🇹\n\nA generator and player for **Italian crosswords**, running entirely in the browser. No backend, no build step, no external dependencies — just HTML and vanilla JavaScript. Installable as a PWA and fully playable **offline**, tuned for iPad and desktop.\n\n**▶️ Play it: https://agea.github.io/cruciverba/**\n\n---\n\n## ✨ Features\n\n- **Automatic generation** of dense, *Settimana Enigmistica*–style grids with black squares, with square and landscape presets from **5×5** up to **25×13**.\n- **Interactive solving**: pick a clue, type with the physical or on-screen keyboard, with highlighting of the active word and crossing cell.\n- **Helpers**: check wrong letters, reveal a cell or a whole word, clear.\n- **Persistence**: the game state is saved in `localStorage`, so you can pick up where you left off.\n- **Game timer** and automatic completion detection.\n- **\"Pencil-on-paper\" design**, touch-friendly and **offline-first** (PWA with a service worker).\n\n---\n\n## 🏗️ Architecture\n\nThree decoupled components:\n\n1. **Clue database** — [`cruciverba_db.json`](cruciverba_db.json): pure data, kept separate from logic.\n2. **Grid generator** — [`gen_dense.js`](gen_dense.js): the grid-construction algorithm, run inside a **Web Worker** so the UI never blocks.\n3. **UI** — the HTML app: grid rendering, input, helpers, persistence.\n\nThe worker loads `gen_dense.js`, which is precached by the service worker together with the database for offline PWA use.\n\n---\n\n## 🗂️ The database\n\nThe source database lives in [`voci/`](voci/): **26 CSV files**, one per initial letter, with **20,000 definition rows**. `node builddb.js` turns them into `cruciverba_db.json`, a compact JSON array of `[\"SOLUTION\", clue]` entries, where `clue` is either a **string** (one definition) or an **array of strings** (several definitions for the same solution). When a word has multiple clues, the generator picks one at random per puzzle, so the same answer can be asked differently from one grid to the next.\n\nThe clue database and definitions are licensed separately from the software: see [LICENSE-CONTENT.md](LICENSE-CONTENT.md).\n\n- **14,890 solutions / 20,000 clues / 3,653 multi-clue solutions.** Solutions are uppercase, letters **A–Z only** (accents and spaces stripped at build time).\n- Length distribution is deliberately skewed toward short words, which feed the dense crossings:\n\n| Letters | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14+ |\n|---|---|---|---|---|---|---|---|---|---|---|---|---|---|\n| Solutions | 183 | 286 | 970 | 2102 | 2640 | 2922 | 2268 | 1656 | 990 | 505 | 228 | 89 | 51 |\n\nThe `14+` bucket is made of 33 words of length 14, 13 words of length 15, 3 words of length 16, 1 word of length 18 and 1 word of length 19. Short slots (2–3 letters) lean on the classic Italian-puzzle style: initialism, car plates, musical notes and chemical symbols — and, being the most frequent, often carry several alternative clues.\n\n### Extending the database\n\n1. Add rows to the right file in [`voci/`](voci/) — one file per initial letter (`voci/A.csv`, `voci/B.csv`, …), each under the header `soluzione,definizione`. A word goes in the file of its first letter. Wrap a clue in double quotes if it contains a comma. To give a word **more than one clue**, add several rows with the same solution and different definitions.\n2. Run `node builddb.js`. It reads every `voci/*.csv`, **normalizes** (NFD → uppercase → A–Z only), **groups every distinct definition under its solution** (only exact `(solution, clue)` duplicates are dropped), **drops** invalid entries, sorts, and rewrites `cruciverba_db.json`. (A single legacy `voci.csv` is still accepted as a fallback.)\n\n\u003e 💡 Solutions shorter than 2 letters or without a clue are discarded automatically. Duplicate solutions are kept as one JSON entry with all distinct clues attached.\n\n---\n\n## ⚙️ The dense generator (`gen_dense.js`)\n\nProduces dense grids in the Italian style: a filled rectangular grid with **black squares**, where every white run of length ≥ 2 (across or down) is a database word with its clue.\n\n**Pipeline:**\n\n1. **Word bank** — the DB is indexed by length and by `(position, letter)`, to quickly fetch candidates for a partially filled slot.\n2. **Black-square pattern** — randomly generated with controlled density, then carved around a near-central crossing between one long across answer and one long down answer. Over-long non-seed white runs are split (`maxRun`) to keep the fill tractable; isolated white cells are removed; black squares are normalized to avoid 2×2 black blocks and black runs longer than 3 cells; among valid candidates the generator favors grids with fewer black squares and more long answers.\n3. **Slot extraction** — all white runs ≥ 2, across and down, plus a cell → slot map.\n4. **Filling (backtracking)** — the central crossing is pre-filled first, then most-constrained-slot selection (propagation from already-filled slots + a static seed on the most-crossed ones), **forward-checking** on crossings, no repeated words.\n5. **Fallback cascade** — if a configuration can't be completed, it retries with gradually more black squares before falling back to a smaller grid: a valid grid is preferred over failing.\n\nDuring generation the worker emits throttled progress updates by phase, attempted patterns and backtracking activity. The displayed percentage is intentionally conservative because backtracking progress is not linear.\n\n**Preset sizes**\n\n| Shape | Sizes |\n|---|---|\n| Square | 5×5, 7×7, 9×9, 11×11, 13×13 |\n| Landscape | 11×7, 13×9, 17×11, 21×13, 25×13 |\n\n**Smoke-test timings** (Node, current DB, fixed seeds, current UI presets, zero clueless words):\n\n| Requested | Actual | Words | Black | Longest | 7+ words | Time |\n|---|---:|---:|---:|---:|---:|---:|\n| 5×5 | 5×5 | 10 | 1/25 (4.0%) | 5 | 0 | ~0.04 s |\n| 7×7 | 7×7 | 22 | 10/49 (20.4%) | 5 | 0 | ~0.02 s |\n| 9×9 | 9×9 | 29 | 15/81 (18.5%) | 7 | 7 | ~0.26 s |\n| 11×11 | 11×11 | 45 | 26/121 (21.5%) | 7 | 7 | ~3.75 s |\n| 13×13 | 13×13 | 55 | 39/169 (23.1%) | 8 | 13 | ~3.35 s |\n| 11×7 | 11×7 | 29 | 11/77 (14.3%) | 7 | 8 | ~4.45 s |\n| 13×9 | 13×9 | 38 | 27/117 (23.1%) | 8 | 9 | ~0.42 s |\n| 17×11 | 17×11 | 70 | 42/187 (22.5%) | 9 | 9 | ~5.11 s |\n| 21×13 | 21×13 | 95 | 66/273 (24.2%) | 12 | 17 | ~14.61 s |\n| 25×13 | 25×13 | 115 | 73/325 (22.5%) | 11 | 23 | ~6.77 s |\n\n### Output structure\n\n```js\n{\n  width, height,\n  cells: [[ null | { ch, num }, ... ], ...],  // null = black square\n  across: [{ num, clue, answer, row, col, len }, ...],\n  down:   [{ num, clue, answer, row, col, len }, ...],\n  wordCount,\n  ghosts  // words without a clue (expected: 0)\n}\n```\n\n---\n\n## 📁 Project structure\n\n| File | Role |\n|---|---|\n| [`voci/`](voci/) | Database source, split by initial (`A.csv … Z.csv`) |\n| [`builddb.js`](builddb.js) | Build script: CSV → JSON |\n| `cruciverba_db.json` | Generated database, ignored by git locally and rebuilt in CI before deploy |\n| [`gen_dense.js`](gen_dense.js) | Dense generator (Web Worker) |\n| [`index.html`](index.html) | Playable PWA app |\n| [`sw.js`](sw.js) · [`manifest.webmanifest`](manifest.webmanifest) · [`icons/`](icons/) | PWA assets, offline cache and update flow |\n| [`version.json`](version.json) | Build metadata used by the deployed app to detect updates |\n\n---\n\n## 🚀 Development\n\nNo toolchain required.\n\n```bash\n# rebuild the database after editing any voci/*.csv\nnode builddb.js\n\n# refresh README database statistics after editing any voci/*.csv\nnode scripts/update-readme-stats.js\n\n# serve locally (a service worker needs an HTTP origin)\npython3 -m http.server 8000\n# then open http://localhost:8000\n```\n\nEnable the included pre-commit hook with:\n\n```bash\ngit config core.hooksPath .githooks\n```\n\nThe hook runs `node scripts/update-readme-stats.js` and stages `README.md` if the statistics changed.\n\nPushing to `main` triggers the GitHub Actions workflow, which rebuilds the database, writes deploy-time `version.json` metadata from the commit SHA and UTC build time, and deploys the static site to **GitHub Pages**.\n\n### Offline use\n\nFull offline use requires opening the app at least once from GitHub Pages (or any HTTP origin), so the service worker can cache `index.html`, `gen_dense.js`, `cruciverba_db.json`, `README.md` and the other assets. The service worker also refreshes `version.json` and `cruciverba_db.json` from the network when available, then shows an in-app update prompt for a newly deployed version. Opening directly via `file://` is not the main target, because browsers restrict `fetch` and service workers outside an HTTP/HTTPS origin.\n\n---\n\n## License\n\n- Software code: GNU General Public License v3.0 only. See [LICENSE](LICENSE).\n- Crossword entries, clue definitions and generated database content: Creative Commons Attribution-NonCommercial 4.0 International. See [LICENSE-CONTENT.md](LICENSE-CONTENT.md).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagea%2Fcruciverba","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fagea%2Fcruciverba","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fagea%2Fcruciverba/lists"}