Skip to content

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

ย 

History

19 Commits

Folders and files

Repository files navigation

๐ŸŒ EthnoGuessr

Guess where they're from. A geography guessing game where you identify a person's origin based on their photo โ€” powered by Leaflet maps, PostGIS scoring, and Supabase.

Think GeoGuessr, but for people and cultures.


โœจ Features

Feature Description
๐ŸŽฎ Core Game View a person's image, click a world map to guess their origin
๐Ÿ“ Haversine Scoring Exponential decay scoring (max 5000 pts) based on distance accuracy
๐Ÿ† Global Leaderboard Top 50 players, auto-aggregated scores, medal rankings
๐Ÿ” GitHub OAuth One-click sign-in via GitHub โ€” no passwords needed
๐Ÿ› ๏ธ Admin Panel Create levels: upload images, pick locations on map, set difficulty
๐Ÿ—บ๏ธ Leaflet + OpenStreetMap Interactive maps for both guessing and admin location selection
๐Ÿ›ก๏ธ Anti-Cheat Rate limiting (15 guesses/min), unique constraint (1 guess/level/user)
๐Ÿ“ PostGIS Spatial geography types, spatial indexes, precise coordinate storage
๐Ÿ”’ Row Level Security All tables protected with Supabase RLS policies

๐Ÿ› ๏ธ Tech Stack

Layer Technology
Framework Next.js 15 (App Router)
Language TypeScript
Styling TailwindCSS v4
Maps Leaflet + OpenStreetMap
Database PostgreSQL + PostGIS (via Supabase)
Auth Supabase Auth (GitHub OAuth)
Storage Supabase Storage (level images)
Deployment Vercel / any Node.js host

๐Ÿ“ Project Structure

src/
โ”œโ”€โ”€ app/
โ”‚   โ”œโ”€โ”€ layout.tsx                  # Root layout (Inter font, dark theme)
โ”‚   โ”œโ”€โ”€ page.tsx                    # Landing page (hero + features)
โ”‚   โ”œโ”€โ”€ globals.css                 # Tailwind + custom dark styles
โ”‚   โ”œโ”€โ”€ login/page.tsx              # GitHub OAuth sign-in
โ”‚   โ”œโ”€โ”€ game/page.tsx               # Game page (random unplayed level)
โ”‚   โ”œโ”€โ”€ leaderboard/page.tsx        # Global leaderboard (top 50, ISR)
โ”‚   โ”œโ”€โ”€ admin/page.tsx              # Admin panel (role-gated)
โ”‚   โ”œโ”€โ”€ auth/
โ”‚   โ”‚   โ”œโ”€โ”€ callback/route.ts       # OAuth code exchange
โ”‚   โ”‚   โ””โ”€โ”€ signout/route.ts        # Sign out handler
โ”‚   โ””โ”€โ”€ api/
โ”‚       โ”œโ”€โ”€ guess/route.ts          # Guess submission API
โ”‚       โ””โ”€โ”€ admin/levels/route.ts   # Level CRUD API (admin only)
โ”œโ”€โ”€ components/
โ”‚   โ”œโ”€โ”€ Navbar.tsx                  # Navigation with auth state
โ”‚   โ”œโ”€โ”€ GameMap.tsx                 # Leaflet map for guessing
โ”‚   โ”œโ”€โ”€ GameClient.tsx              # Game UI (image, controls, score)
โ”‚   โ”œโ”€โ”€ AdminMap.tsx                # Leaflet map for admin location picker
โ”‚   โ””โ”€โ”€ AdminClient.tsx             # Admin UI (level creation + management)
โ”œโ”€โ”€ lib/
โ”‚   โ”œโ”€โ”€ supabase/
โ”‚   โ”‚   โ”œโ”€โ”€ client.ts               # Browser Supabase client
โ”‚   โ”‚   โ”œโ”€โ”€ server.ts               # Server Supabase client
โ”‚   โ”‚   โ””โ”€โ”€ middleware.ts           # Session refresh + route protection
โ”‚   โ””โ”€โ”€ scoring.ts                  # Haversine distance + score calculation
โ”œโ”€โ”€ types/
โ”‚   โ””โ”€โ”€ database.ts                 # TypeScript interfaces
โ””โ”€โ”€ middleware.ts                    # Root middleware
supabase/
โ””โ”€โ”€ schema.sql                      # Full database schema + triggers

๐Ÿš€ Getting Started

Prerequisites

1. Clone & Install

git clone https://github.com/TAG-IIIT/Ethnoguessr.git
cd Ethnoguessr
npm install

2. Environment Variables

Copy the example and fill in your Supabase credentials:

cp .env.local.example .env.local
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
SUPABASE_SERVICE_ROLE_KEY=your-service-role-key

Find these in Supabase Dashboard โ†’ Settings โ†’ API

3. Database Setup

Run supabase/schema.sql in Supabase Dashboard โ†’ SQL Editor. This creates:

  • profiles โ€” user profiles (auto-created on GitHub login)
  • levels โ€” game levels with PostGIS locations
  • guesses โ€” user guesses with distance/score
  • leaderboard โ€” auto-aggregated scores via trigger

Then create the coordinate extraction function:

create or replace function get_level_coords(p_level_id uuid)
returns table(lat double precision, lng double precision)
language sql security definer
as $$
  select
    ST_Y(location::geometry) as lat,
    ST_X(location::geometry) as lng
  from public.levels
  where id = p_level_id and is_active = true;
$$;

4. GitHub OAuth Setup

  1. Create a GitHub OAuth App at github.com/settings/developers
    • Homepage URL: http://localhost:3000
    • Callback URL: https://<your-supabase-url>/auth/v1/callback
  2. Enable GitHub in Supabase Dashboard โ†’ Authentication โ†’ Providers
    • Paste the Client ID and Client Secret

5. Storage Bucket

  1. Go to Supabase Dashboard โ†’ Storage
  2. Create a bucket named level-images (set to Public)
  3. Add storage policies (run in SQL Editor):
create policy "Allow authenticated uploads"
on storage.objects for insert to authenticated
with check (bucket_id = 'level-images');

create policy "Allow public reads"
on storage.objects for select to public
using (bucket_id = 'level-images');

create policy "Allow authenticated updates"
on storage.objects for update to authenticated
using (bucket_id = 'level-images');

create policy "Allow authenticated deletes"
on storage.objects for delete to authenticated
using (bucket_id = 'level-images');

6. Run

npm run dev

Open http://localhost:3000 and sign in with GitHub.

7. Become Admin

After your first login, run in SQL Editor:

update public.profiles set role = 'admin' where username = 'YOUR_GITHUB_USERNAME';

You'll now see the Admin link in the navbar.


๐ŸŽฎ How It Works

Scoring Formula

Uses the Haversine formula to calculate great-circle distance, then applies exponential decay:

score = round(5000 ร— e^(-distance_km / 2000))
Distance Score
0 km (exact) 5000
100 km 4756
500 km 3894
1000 km 3033
2000 km 1839
5000 km 410
10000 km 34

Game Flow

  1. Load โ†’ Server fetches a random active level the user hasn't played
  2. View โ†’ User sees the person's image (signed URL, 1hr expiry)
  3. Guess โ†’ User clicks on the Leaflet map to place their guess
  4. Score โ†’ API calculates Haversine distance and exponential decay score
  5. Result โ†’ Shows distance, score bar, correct/guessed markers with dashed line
  6. Next โ†’ Proceeds to the next unplayed level

๐Ÿ—„๏ธ Database Schema

erDiagram
    auth_users ||--o| profiles : creates
    profiles ||--o{ guesses : makes
    profiles ||--o| leaderboard : aggregates
    levels ||--o{ guesses : receives

    profiles {
        uuid id PK
        text username UK
        text avatar_url
        text role
        timestamptz created_at
    }

    levels {
        uuid id PK
        text image_url
        geography location
        text hint
        text difficulty
        boolean is_active
        uuid created_by FK
        timestamptz created_at
    }

    guesses {
        uuid id PK
        uuid user_id FK
        uuid level_id FK
        geography guessed_location
        float distance_km
        int score
        timestamptz created_at
    }

    leaderboard {
        uuid user_id PK
        bigint total_score
        int games_played
        timestamptz updated_at
    }
Loading

๐Ÿ”’ Security

  • Row Level Security (RLS) on all tables
  • GitHub OAuth only โ€” no password storage
  • Rate limiting โ€” 15 guesses per minute per user
  • Unique constraint โ€” one guess per level per user
  • Admin role check โ€” server-side verification for all admin operations
  • Signed URLs โ€” level images use 1-hour expiry signed URLs
  • Middleware โ€” session refresh + route protection on every request

๐Ÿ“œ License

MIT


Built with โค๏ธ using Next.js, Supabase, and Leaflet

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages