A Chrome Dinosaur Game clone with a pixel-art horse and jockey. Jump over racing obstacles, climb the global leaderboard, and chase ghost markers left by top players.
Play now at horse.horse
- Pixel-art horse + jockey with running, jumping, ducking, and idle fidget animations
- Racing obstacles β hurdles (low + tall), hedges, oxers (double fences), water jumps, and birds
- Obstacle grouping β multiple obstacles spawn together as speed increases
- Global leaderboard β top 10 scores displayed with country flags, powered by Cloudflare D1
- Ghost markers β top 100 leaderboard entries become country flag checkpoints on the track; pass one and you'll see the flag, rank, and score with a chime
- Global play counter β total games played across all players, displayed in the HUD
- Day/night cycle β palette inverts every 700 points
- Web Audio SFX β jump, hit, score milestone, and ghost-pass sounds, all synthesized in the browser
- Mobile support β tap to jump, swipe down to duck, responsive canvas scaling
- Top 10 result screen β game over shows your rank highlighted in gold when you place
- Full Chrome Dino parity β delta-time physics, variable jump height, speed drop (fast fall), gap-based spawning, tab-pause, restart cooldown, score flash, blink animation, intro slide, bumpy horizon, dynamic clouds
| Idle | Running | Jumping | Game Over |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Action | Keyboard | Mobile |
|---|---|---|
| Jump | Space / Arrow Up |
Tap |
| Duck | Arrow Down |
Swipe down |
| Fast fall | Arrow Down while airborne |
Swipe down while airborne |
| Restart | Space / Arrow Up / Tap after death |
Tap |
- Frontend β vanilla JS, single-file game (
js/game.js), Canvas 2D, no build step, no dependencies - Backend β Cloudflare Workers + D1 (serverless SQLite) for the leaderboard API and play counter
- Hosting β Cloudflare Workers with static assets, zone-routed to
horse.horse - Analytics β Google Analytics (G-SECK3FR5SR)
- SEO β Open Graph, Twitter Cards, VideoGame + FAQPage structured data (JSON-LD)
index.html <- game shell: canvas + structured data + meta tags
js/game.js <- entire game in a single IIFE (~1300 lines)
worker.js <- Cloudflare Worker: leaderboard API + stats + static fallback
css/style.css <- centered layout, pixelated canvas rendering
og-image.png <- Open Graph social preview image (1200x630)
terms.html <- Terms of Service
privacy.html <- Privacy Policy
wrangler.toml.example <- Cloudflare config template (copy to wrangler.toml)
| Endpoint | Method | Description |
|---|---|---|
/api/session |
POST |
Issues a server-tracked session token; required before score submission |
/api/scores |
GET |
Returns top 100 scores as JSON, cached 30s |
/api/scores |
POST |
Submit a score with the issued session token |
/api/stats |
GET |
Returns { totalPlays } global play count, cached 10s |
Anti-cheat measures:
- Session tokens are server-issued only β clients cannot mint their own
- Session must be β₯3s and β€30min old (measured on the server clock)
- Client-reported
playTimemust match server-measured session age - Score rate capped at 20/sec against server-measured session age
- Sessions are single-use β atomically consumed on submission (replay-proof)
- Session issuance rate-limited to 30/min/IP
- Max speed validation (game caps at 12, rejects >12.5 or <7 on scoring games)
- Obstacle count sanity check (min 1 per 200 score, max 1 per score point)
- Score cap at 99,999
- Only top-100-worthy scores are stored
# Start the local server
node server.js
# Open http://localhost:8765No build step, no npm install, no dependencies. Just a static file server.
# Copy and fill in your Cloudflare IDs
cp wrangler.toml.example wrangler.toml
# Edit wrangler.toml with your account_id, zone_id, database_id
# Create the D1 database and tables
npx wrangler d1 create horse-leaderboard
npx wrangler d1 execute horse-leaderboard --remote --command "CREATE TABLE IF NOT EXISTS scores (id INTEGER PRIMARY KEY AUTOINCREMENT, score INTEGER NOT NULL, country TEXT DEFAULT 'XX', session_id TEXT, max_speed REAL, obstacles_passed INTEGER, created_at DATETIME DEFAULT CURRENT_TIMESTAMP)"
npx wrangler d1 execute horse-leaderboard --remote --command "CREATE TABLE IF NOT EXISTS sessions (sid TEXT PRIMARY KEY, ip TEXT NOT NULL, iat INTEGER NOT NULL, consumed INTEGER NOT NULL DEFAULT 0)"
npx wrangler d1 execute horse-leaderboard --remote --command "CREATE INDEX IF NOT EXISTS idx_sessions_ip_iat ON sessions(ip, iat); CREATE INDEX IF NOT EXISTS idx_sessions_iat ON sessions(iat)"
npx wrangler d1 execute horse-leaderboard --remote --command "CREATE TABLE IF NOT EXISTS stats (key TEXT PRIMARY KEY, value INTEGER DEFAULT 0)"
npx wrangler d1 execute horse-leaderboard --remote --command "INSERT OR IGNORE INTO stats (key, value) VALUES ('total_plays', 0)"
# Deploy
npx wrangler deployRequires a Cloudflare account with:
- Workers plan
- D1 database
- DNS zone for your domain
Built with vibes by @heathermhuang and Claude.
MIT



