Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/igorek05m/daily-geogame/llms.txt

Use this file to discover all available pages before exploring further.

Understand the systems and calculations that power Daily GeoGame.

Guess validation

Country matching

When you submit a guess, the game:
  1. Normalizes your input (converts to lowercase, trims whitespace)
  2. Searches the country database for an exact name match
  3. Validates against 195+ countries from the RestCountries API
  4. Rejects the guess if the country name is invalid or already guessed
The country database includes both common names (“United States”) and official names. The game uses the common name for matching.

Duplicate prevention

The game tracks all your guesses in the session and prevents duplicate entries. If you try to guess the same country twice, you’ll see an “Already guessed!” alert.

Distance calculation

Haversine formula

Daily GeoGame uses the Haversine formula to calculate the great-circle distance between two points on a sphere (Earth). This provides accurate distances between countries based on their geographic centers.
function getDistanceFromLatLonInKm(lat1, lon1, lat2, lon2) {
  const R = 6371; // Earth's radius in km
  const dLat = deg2rad(lat2 - lat1);
  const dLon = deg2rad(lon2 - lon1);
  const a =
    Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) *
    Math.sin(dLon / 2) * Math.sin(dLon / 2);
  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
  return Math.round(R * c);
}
See app/lib/geoUtils.ts:5 for the implementation.
Distances are calculated from the latitude/longitude coordinates of each country’s geographic center, not political capitals.

Bearing and direction

Direction calculation

The bearing angle determines which direction to travel from your guess to reach the target country. The game calculates this using trigonometric functions on the latitude/longitude coordinates.
function getBearingAngle(lat1, lon1, lat2, lon2) {
  const startLat = deg2rad(lat1);
  const startLng = deg2rad(lon1);
  const destLat = deg2rad(lat2);
  const destLng = deg2rad(lon2);

  const y = Math.sin(destLng - startLng) * Math.cos(destLat);
  const x = Math.cos(startLat) * Math.sin(destLat) - 
            Math.sin(startLat) * Math.cos(destLat) * Math.cos(destLng - startLng);
  let brng = Math.atan2(y, x);
  return (brng * 180 / Math.PI + 360) % 360; // Convert to degrees
}
See app/lib/geoUtils.ts:33 for the implementation.

Visual representation

The bearing angle rotates an arrow icon (↑) using CSS transforms. An angle of 0° points north, 90° points east, 180° points south, and 270° points west.

Neighbor highlighting

Border detection

The game identifies neighboring countries using ISO 3166-1 alpha-3 country codes. When you make a guess, the system:
  1. Retrieves the target country’s borders array (e.g., ["DEU", "CZE", "SVK"] for Poland)
  2. Checks if your guessed country’s alpha-3 code is in that array
  3. Highlights the guess with a yellow pin icon if it’s a neighbor
  4. Updates the map to visually show border countries
See app/api/progress/route.ts:44-48 for the connection logic.

Geographic hierarchy

If your guess isn’t a neighbor, the game checks broader geographic connections:
  • Subregion match: Same subregion (e.g., “Central Europe”)
  • Region match: Same continent (e.g., “Europe”)
  • No connection: Different region entirely
This provides progressively weaker clues as you get farther from the target.

Progressive hint system

Hint generation

Hints are generated when the daily game is created (at midnight UTC). The system:
  1. Fetches data from the CIA World Factbook using the country’s FIPS code
  2. Organizes facts into themed packages (Geography, Economy, People, etc.)
  3. Extracts specific data points (climate, terrain, GDP, population, etc.)
  4. Stores up to 6 hint packages in the database
See app/api/daily/route.ts:114 for hint generation.

Hint unlocking

The unlock mechanism is controlled server-side to prevent cheating:
const guessesCount = progress?.guesses?.length || 0;
const allowedHintsCount = guessesCount + 1;
const isGameOver = guessesCount >= 6 || isWon;

const hints = hintPackages.map((pkg, index) => {
  if (isGameOver || index < allowedHintsCount) {
    return pkg; // Reveal hint
  }
  return {
    title: pkg.title,
    hints: pkg.hints.map(h => ({ 
      label: h.label,
      value: "???" // Hide value
    }))
  };
});
See app/api/daily/route.ts:139-156 for the unlock implementation.
Hints are obfuscated with ”???” until unlocked. The client never receives locked hint values, preventing inspection via browser dev tools.

Session persistence

Anonymous session tracking

Daily GeoGame uses session cookies to save your progress without requiring login:
  1. On your first visit, the server generates a UUID session ID
  2. The ID is stored in a secure, HTTP-only cookie (geo_session)
  3. Your guesses, wins, and stats are linked to this session ID in MongoDB
  4. The cookie persists for 10 years (maxAge: 60 * 60 * 24 * 365 * 10)
See app/api/progress/route.ts:18-22 for session creation.

Progress saving

After each guess, the game saves your progress via POST to /api/progress:
await collection.updateOne(
  { sessionId, date },
  { $set: { guesses, won: isWon, lastPlayed: new Date() } },
  { upsert: true }
);
This allows you to refresh the page or return later without losing your game state.
Progress is stored per date, so you can play previous days’ games and each maintains separate progress.

Daily reset

Country selection

Every day at midnight UTC, a new country is selected:
  1. The game randomly picks from the country database
  2. Fetches detailed data from RestCountries API
  3. Retrieves CIA World Factbook data using the country’s FIPS code
  4. Generates hint packages
  5. Stores the game in MongoDB with the date as the key
See app/api/daily/route.ts:71-137 for the daily game creation logic.

Date-based routing

The game uses YYYY-MM-DD date strings to identify each daily puzzle:
  • Default: Today’s date (new Date().toISOString().split('T')[0])
  • Archive: Access previous games via ?date=2024-03-15 query parameter
  • Validation: Dates must be between the game start date and today
This allows players to go back and play missed days.

Stats tracking

The game tracks both personal and global statistics:

Personal stats

  • Games played: Total number of days you’ve played
  • Wins: Number of correct guesses
  • Win rate: Percentage of games won
Personal stats are tied to your session ID.

Global stats

  • Total players: Number of unique sessions that played each day
  • Total winners: Players who guessed correctly
  • Global win rate: Percentage of all players who won
  • Guess distribution: Histogram showing which guess number players won on
Global stats are calculated server-side from all sessions for each date.
Stats are updated immediately after you win or use all 6 guesses, so you can see how you compare to other players.

Build docs developers (and LLMs) love