MapPuzzle.xyz is a platform that immerses you in hours of entertainment through flags, maps, and puzzles from various parts of the world. The game is designed for players of all ages and skill levels, offering a comprehensive and enriching educational experience.
Currently, there are two different games: Geographical Puzzles and Guess the Flag.
- Choose from available puzzles representing different regions of the world, including countries, states, or provinces.
- In the interface, the map is displayed on the right with its borders, while on the left, there is a list of elements with their respective silhouettes. Your task is to place each piece in its corresponding location.
- Additionally, every time a piece is placed on the map, players can access Wikipedia data about the place they are exploring. This allows them to obtain additional information about the geography, history, culture, and other areas related to the place, helping them learn more about the world around them.
- Observe a waving flag and the silhouette of the country on the right.
- Choose from six available options to correctly identify the country to which it belongs.
- Once the game is finished, you can explore the map and obtain additional information about the country from Wikipedia.
Both games offer detailed tracking of your successes, the list of remaining elements, errors made, and the time devoted to the activity.
Players can choose the map they want to play, filtering by continent and region. One interesting feature of the game is that it allows players to translate the names of the puzzle pieces into different languages. This enriches their gaming experience and helps them develop their language skills.
You can play the game in the following link: MapPuzzle.xyz
MapPuzzle.xyz is a React application built with Vite, drawing its maps with deck.gl. The published site is nothing but static files plus a small read-only PHP gateway over a SQLite file: there is no Node.js on the server. The map editor and its Node backend run locally only, to author the content that gets uploaded.
- React 18 for the interface, Vite 6 for the build and dev server, with
offline support through
vite-plugin-pwa. - deck.gl renders the map and its pieces over WebGL.
- SQLite holds the puzzles, the Wikipedia links, the flags and the 73k name
translations. In production it is read-only, reached through the PHP gateway
in
apps/game/public/backendPHP/; locally the Node backend answers instead. The client never opens the database itself: it used to, through sql.js over range requests, which cost 1.3 MB of WebAssembly and a worker on every visit. - Express + TypeORM is the editor's backend. It writes the database, imports shapefiles and pulls content from Wikipedia. Local only.
- Shapefiles are read directly with the
shapefilepackage. PostgreSQL and PostGIS are no longer involved anywhere, and the piece silhouettes are generated at runtime from the geometry instead of being stored in the map.
The repository is a monorepo. npm workspaces cover packages/*; the apps are
built through their own Vite configs from the root.
| Path | What it is |
|---|---|
apps/game |
MapPuzzle and FlagsQuiz. The only thing deployed. |
apps/editor |
The map editor, a separate client. |
apps/backend |
Express + TypeORM. Authoring only, never published. |
packages/core |
Code both clients share: services, geometry, UI pieces. |
packages/shared |
Type declarations only, the contract between the two sides. |
data/ |
What the editor produces: maps, flags, sitemap and the SQLite file. |
scripts/ |
Build and release helpers: prerender, publish-db, deploy. |
build/ |
The output, and exactly what gets uploaded. |
Node.js 22 or newer — the prerender step reads the database through
node:sqlite, which is not in earlier versions. Tested on Node 24.
One pinned dependency worth knowing about: "react-map-gl": "5.3.21". From 6.0.0
onwards it requires a Mapbox access token, and that means a paid plan.
.npmrc sets legacy-peer-deps, and npm install fails without it: two
dependencies declare peers that cannot hold at once — react-bootstrap-table-next
asks for React 16, and a react-native reached through @react-three/fiber asks
for React 19. Neither is a problem in practice; the reasons are in the file.
Thirteen, and each one is a job someone actually does.
Developing
| Script | What it does |
|---|---|
dev |
The game against the local Node backend. Needs backend running. |
dev-php-backend |
The game against a PHP backend on port 8888, to try changes to the gateway itself. |
editor |
The map editor, on port 3001. Always needs the Node backend. |
backend |
Express + TypeORM on port 5000. |
Checking
| Script | What it does |
|---|---|
typecheck |
All three projects. |
typecheck:game, typecheck:editor, typecheck:backend |
One at a time. |
lint |
ESLint over the game, the editor and the packages. The backend keeps its own config and an older toolchain — eslint-config-airbnb-typescript@12 pins @typescript-eslint@4, which ESLint 8 cannot run — so it is not in this pass. |
Releasing
| Script | What it does |
|---|---|
build |
The production build, then the prerender step. What gets deployed. |
preview |
Serves that build on port 3000. The port is fixed on purpose: the production PHP gateway only accepts that origin, so any other one fails CORS and the game loads without data. |
publish-db |
Copies the authoring database over the published one, showing both digests first. --check fails instead of writing. |
deploy |
Uploads over FTP, comparing the generated content by size and sending only what differs. |
deploy:app |
The same, skipping that comparison — the app shell only, which is the usual case after a code change. |
--dry-run prints a deploy plan without uploading anything. FTP credentials come
from .env.deploy, which is gitignored. The prerender step can be run on its own
against an existing build with node scripts/prerender.mjs, and --check there
reports what it would write.
Every puzzle has a page of its own — /map/<slug>/, and /flag-quiz/<slug>/
for the ones with flags — and scripts/prerender.mjs writes a real HTML file
for each at build time, carrying its own title, description, canonical link and
the region names as text. They are files rather than rewrite rules because the
host answers 404 to any path that is not one.
Each page exists in all seven interface languages: /es/map/<slug>/,
/fr/map/<slug>/ and so on, with the default language keeping the bare path so
English is not duplicated at two addresses. The title, the description and the
region names are in that language — none of it newly translated, the interface
already carried the puzzle names and the editor imported the region ones — and
hreflang ties the seven copies together, with x-default on English. That is
833 pages, plus a home page per language.
The older query form, /?map=<slug>, still works and is rewritten to the
canonical path once the app boots. Slugs are stored with underscores and use
hyphens in URLs. The same step regenerates sitemap-index.xml, which is the
sitemap robots.txt declares, with all 840 urls in it. It is the only thing
that writes a sitemap: the editor used to have a button for it, which after the
languages landed would have replaced 840 urls with the 120 it knew about.
Because a page is served from its own directory, anything addressed from the
site root has to say so: siteAsset() in packages/core/src/lib/data.ts is
what pins content paths — maps, flags, logos, textures — to /.
The interface is a set of glass panels floating over the map. Everything that
defines the look is a CSS custom property declared once, at the top of
apps/game/src/styles/MapPuzzle.css, so a change lands everywhere at once.
- Glass surfaces.
--glass-bg,--glass-borderand--glass-shadowwithbackdrop-filter, on the tool panel, the piece list, the top bar and the dialogs — eighteen places in the main stylesheet, two more in the quiz. - Light and dark. The same tokens redefined under
[data-bs-theme="dark"], from#f1f5f9on white glass to#090d16on slate. The choice is the player's and is remembered on their device. - Typography. Outfit for titles, Inter for text, both self-hosted as variable fonts — one file per family covering weights 100 to 900, so the page asks nothing of a font CDN and no weight costs an extra download.
- Custom cursors.
--cursor-graband--cursor-grabbingpoint at SVGs, so the pointer says whether a piece can be taken and whether it is being held. - Accent. One
--accent-colorwith a matching--accent-glowfor focus and hover, blue on both themes. - Bootstrap underneath. Its own
--bs-*variables are overridden rather than fought, and the iconography is Bootstrap Icons. - Responsive.
styles/responsive.css, including specific work for ultrawide screens, where the quiz's radial fill needed its own handling.
The pieces keep their bright, contrasting colours: they are the one thing that has to read instantly against the map.
Alongside the redesign the client was made lighter — cursor tracking and the deck.gl layers were reworked, and the two secondary screens (the flags quiz and the donate page) load as separate chunks instead of riding in the main bundle.
This project was developed by Alejandro Aranda, and is a part of the MapPuzzle.gl project.
This project is licensed under the MIT license, is free to use, modify and distribute.
If you have any questions, you can contact me at: https://aaranda.es/en/contact/
If you want to support the project, you can donate at: https://github.com/sponsors/alexwing

