---
title: "How I Built a Guess-the-Country Border Game"
description: "Building a free world-map quiz where you name 235 countries from their border shapes — the Robinson projection, SVG paths, zoom and pan, and a flag gotcha on Windows."
pubDate: 2026-06-30
tags: ["Web", "Games", "Geography", "SVG"]
readingTime: "6 min read"
canonicalUrl: https://metehanyengil.com/articles/building-guess-the-country
author: "Metehan Yengil"
---

I wanted a small geography game with one specific twist: you guess a country from the
**shape of its borders**, not from a flag or a satellite drop. Most "guess the country"
games lean on flags or location. Borders felt like the harder, more interesting signal —
and a good excuse to learn how world maps actually get drawn on the web. The result is
[Guess the Country](/projects/guess-the-country), a free browser game with all 235
countries and territories. Here's how it came together.

## Borders as data, not an image

The first decision was to treat the map as **data**, not a picture. A flat PNG can't
highlight one country at a time, and that highlight is the whole game. So I started from
[Natural Earth](https://www.naturalearthdata.com/)'s public-domain admin-0 country
dataset (the 50m layer), which gives every country as GeoJSON polygons in longitude /
latitude. I simplified it locally and filtered out non-playable scraps, landing on 235
border features.

GeoJSON coordinates are geographic, though — degrees on a globe. To draw them on a flat
screen you need a **projection**.

## Projecting the globe with Robinson

I went with the Robinson projection. It isn't equal-area or conformal; it's the
compromise projection that just *looks like a world map* to most people — the one you
grew up seeing on classroom walls. That familiarity matters when the entire game is
recognising shapes.

Robinson is defined by a lookup table of stretch factors at every five degrees of
latitude, interpolated in between. Each `[lon, lat]` becomes an `{x, y}` on a fixed
1000×540 canvas, and each country's polygons turn into an SVG `path` string of
`M … L … Z` subpaths. Multi-polygon nations (think Indonesia or Canada's islands) just
append more subpaths. I also compute each country's centroid from its largest ring so the
"mystery" pulse marker lands on the mainland instead of floating in the sea.

## The map that wouldn't stop stretching

The first painful bug wasn't math — it was one SVG attribute. The map distorted every
time the window resized: Greenland ballooned, Chile went stubby. The culprit was
`preserveAspectRatio="none"`, which forces the viewBox to fill its container regardless of
proportions.

Switching to `preserveAspectRatio="xMidYMid meet"` fixed it instantly. Now the map keeps
its aspect ratio and letterboxes inside whatever space it's given — it behaves like a
fixed image, but it's still live vector graphics underneath, so per-country hit-testing
keeps working. That "feels like one image, acts like data" property is exactly what the
game needs.

## Zoom and pan without breaking clicks

Tiny nations — Andorra, Tuvalu, the Caribbean — are unguessable on a world-scale map, so
the map needed zoom and pan. I wrapped all the map content in a single transform group and
drove it with a `{ scale, x, y }` state object.

Two details took the most care:

- **Zoom toward the cursor.** Scaling around the origin makes the map lurch away from
  where you're looking. The fix is to convert the cursor to SVG user-space with
  `getScreenCTM().inverse()`, find where that point sits inside the content group, then
  re-translate after scaling so the same point stays under the cursor. It makes zoom feel
  anchored instead of jumpy.
- **Don't let a drag fire a guess.** Panning and selecting a country are both pointer
  interactions on the same element. I track movement during a drag and only treat a
  pointer-up as a country click if it moved less than a few pixels. Above that threshold,
  it was a pan, and the click is ignored.

The wheel listener also has to be registered as non-passive, or the browser scrolls the
page instead of letting the game zoom.

## The flag gotcha nobody warns you about

In the country picker I wanted a flag next to each name. The obvious move is flag emoji —
🇧🇷, 🇯🇵, and so on. They work beautifully on macOS, iOS, and Android.

They do **not** render on Windows. Windows ships no flag glyphs in its emoji font, so the
regional-indicator pairs fall back to showing the two-letter country code as plain text.
Since I develop on Windows, I caught it immediately, but it's the kind of thing that
silently ships if you only test on a Mac.

The fix was [flagcdn.com](https://flagcdn.com/) PNG images keyed by ISO alpha-2 code. The
Natural Earth data uses alpha-3 ids (`TUR`, `USA`), so I keep an inline alpha-3 → alpha-2
map. The handful of entities with no flagcdn flag — Northern Cyprus, Somaliland — fall
back to a coloured chip in their continent's colour, which ties into the rest of the
palette anyway.

## Making the continents the visual language

One small thing I'm happy with: every part of the UI speaks in **continent colours**.
Solved countries fill with their continent's hue, the legend uses it, the picker rows
carry it as a left stripe, and when you reselect a solved border the panel reveals its
flag, name, and a continent-coloured hint chip. None of that is essential to play, but it
makes the board feel like one coherent object instead of a grid of controls.

## What I'd reach for next

If I extend it, the obvious additions are a daily-puzzle mode, a flag-guessing mode (the
flag images are already loaded), and difficulty tiers that hide the continent hint. But
the core — guess the country from its border shape — is the part I set out to build.

You can [play Guess the Country here](/projects/guess-the-country). It's free, runs in the
browser, and saves your progress locally. If you beat it without zooming into the Pacific,
I don't believe you.
