← Examples
countries · flags · interactive game · Natural Earth

Flag Guess Game

A flag appears — click the country you think it belongs to. Get it right and your score climbs; skip if you're stumped. The map only includes countries large enough to click comfortably (filtered by the areakm2 property), and the flags come straight from flagpedia.net by ISO2 code.

Flag to guess
Loading countries…

No country names on hover — that would give it away. Guess from the shape and location, or skip to set a flag aside — skipped flags come back around later.

API Call

https://api.mapjson.com/v1/geo?layer=countries&detail=medium&properties=name,iso2,areakm2&format=geojson

Flags: https://flagpedia.net/data/flags/h240/{iso2}.png — e.g. .../ua.png for Ukraine.

Code

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Flag Guess Game</title>
  <script src="https://cdn.jsdelivr.net/npm/d3@7/dist/d3.min.js"></script>
  <style>
    body { font-family: sans-serif; margin: 2rem; }
    .game-bar { display: flex; align-items: center; gap: 1rem; margin-bottom: 1rem; }
    .flag { height: 74px; border: 1px solid #ddd; background: #fff; }
    #map-wrap { position: relative; background: #eaf0f2; max-width: 960px; }
    #map-wrap svg { width: 100%; display: block; }
    .score { position: absolute; top: 10px; right: 10px; background: #1a1a1a; color: #fff;
             font: 13px monospace; padding: 6px 12px; border-radius: 20px; display: flex; gap: 12px; }
    .s-ok { color: #7fdca9; } .s-no { color: #f2a0a0; }
  </style>
</head>
<body>
  <div class="game-bar">
    <img class="flag" id="flag" alt="Flag to guess">
    <div id="prompt-msg">Loading…</div>
    <button id="skip" type="button">Skip →</button>
  </div>
  <div id="map-wrap"><div class="score" id="score"></div></div>

  <script>
    const OCEAN = "#eaf0f2", LAND = "#c9c3b6", STROKE = "#ffffff",
          CORRECT = "#4caf7d", WRONG = "#d6534e", HILITE = "#555";
    const MIN_AREA_KM2 = 25000;   // drop countries too small to click comfortably
    const flagURL = (iso2) => `https://flagpedia.net/data/flags/h240/${iso2.toLowerCase()}.png`;

    const width = 960, height = 500;
    const wrap = document.getElementById("map-wrap");
    const flag = document.getElementById("flag");
    const promptEl = document.getElementById("prompt-msg");
    const scoreEl = document.getElementById("score");

    const svg = d3.select(wrap).append("svg")
      .attr("viewBox", `0 0 ${width} ${height}`).style("width", "100%").style("height", "auto");
    svg.append("rect").attr("width", width).attr("height", height).attr("fill", OCEAN);
    const g = svg.append("g");
    const projection = d3.geoNaturalEarth1();
    const path = d3.geoPath(projection);

    let paths, queue = [], qi = 0, target = null, locked = false, correct = 0, wrong = 0;

    d3.json("https://api.mapjson.com/v1/geo?layer=countries&detail=medium&properties=name,iso2,areakm2&format=geojson")
      .then((geo) => {
        const eligible = geo.features.filter((d) => {
          const p = d.properties;
          return p.iso2 && p.name && p.name !== "Antarctica" && (p.areakm2 || 0) >= MIN_AREA_KM2;
        });
        projection.fitExtent([[6, 6], [width - 6, height - 6]], { type: "FeatureCollection", features: eligible });

        paths = g.selectAll("path").data(eligible).join("path")
          .attr("d", path).attr("fill", LAND).attr("stroke", STROKE).attr("stroke-width", 0.6)
          .style("cursor", "pointer")
          .on("mouseenter", function () { if (!locked) d3.select(this).attr("stroke", HILITE).attr("stroke-width", 1).raise(); })
          .on("mouseleave", function () { d3.select(this).attr("stroke", STROKE).attr("stroke-width", 0.6); })
          .on("click", (event, d) => guess(d));

        queue = shuffle(eligible.slice());
        updateScore();
        next();
      });

    function shuffle(a) { for (let i = a.length - 1; i > 0; i--) { const j = (Math.random() * (i + 1)) | 0; const t = a[i]; a[i] = a[j]; a[j] = t; } return a; }
    function paint(d, color) { paths.filter((x) => x === d).attr("fill", color); }
    function updateScore() { scoreEl.innerHTML = `<span class="s-ok">✓ ${correct}</span><span class="s-no">✗ ${wrong}</span>`; }

    function next() {
      if (qi >= queue.length) { shuffle(queue); qi = 0; }
      target = queue[qi++]; locked = false;
      paths.attr("fill", LAND);
      promptEl.textContent = "Which country flies this flag?"; promptEl.className = "";
      flag.src = flagURL(target.properties.iso2);
    }

    function guess(d) {
      if (locked) return;
      locked = true;
      if (d.properties.iso2 === target.properties.iso2) {
        correct++; paint(d, CORRECT);
        promptEl.textContent = `✓ Correct — ${target.properties.name}`; promptEl.className = "ok";
        updateScore(); setTimeout(next, 900);
      } else {
        wrong++; paint(d, WRONG); paint(target, CORRECT);
        promptEl.textContent = `✗ You picked ${d.properties.name} — it was ${target.properties.name}`; promptEl.className = "no";
        updateScore(); setTimeout(next, 1600);
      }
    }

    document.getElementById("skip").addEventListener("click", () => {
      if (locked) return;
      queue.push(target); // "skip for now" — re-ask this flag later, no reveal
      next();
    });

    // A handful of territories have no flag on flagpedia — skip if the image fails.
    flag.addEventListener("error", () => { if (!locked) next(); });
  </script>
</body>
</html>