MOTO: The Midnight Ride — A Full Browser Game Built From Scratch
What started as a meme coin project turned into something nobody expected: a full-featured browser game with physics, power-ups, leaderboards, wallet integration, anti-cheat, daily tournaments, and a quest system.
All running on vanilla JavaScript, Phaser 3, and a PHP backend. Total infrastructure cost: $3/month.
Play it now: motorcyclediaries.fun/game
This is the technical deep-dive into how we built it.
The Game: What Is It?
MOTO: The Midnight Ride is a neon-fueled endless runner where you ride a motorcycle through an infinite highway, dodging obstacles (we call them "FUD"), collecting diaries and coins, grabbing power-ups, and competing for the daily Grand Prix leaderboard.
Think Chrome Dino meets Subway Surfers, but on Solana with a meme coin twist.
Core Mechanics
- Jump (SPACE / tap top) — leap over low obstacles
- Duck (DOWN / tap bottom) — slide under high obstacles
- Dash (SHIFT) — burst forward with invincibility frames
- Collect — Diaries (+100pts), Coins (+50pts)
- Power-ups — Shield, Nitro, Magnet, 2x Score multiplier
- Progressive difficulty — Speed increases every stage
Tech Stack
Game Engine: Phaser 3 (Canvas renderer)
Frontend: Vanilla HTML/CSS/JS
Backend API: PHP with JSON storage
Wallet: Multi-wallet Solana (Phantom, Solflare, Backpack, Trust, Coinbase, OKX)
Hosting: Hostinger shared hosting + Cloudflare CDN
Total Cost: ~$3/month
No React. No Next.js. No Firebase. No AWS. Just raw code on cheap shared hosting.
Game Architecture
Phaser 3 Setup
We use Phaser 3 with Arcade Physics. The game runs at 800x400 and auto-scales to fit any screen:
phaserGame = new Phaser.Game({
type: Phaser.CANVAS,
width: 800,
height: 400,
parent: 'gameContainer',
backgroundColor: '#06060e',
physics: {
default: 'arcade',
arcade: { gravity: { y: 1200 }, debug: false }
},
scale: {
mode: Phaser.Scale.FIT,
autoCenter: Phaser.Scale.CENTER_BOTH,
},
scene: [BootScene, PlayScene],
});
Why Canvas over WebGL? Compatibility. Our target audience includes mobile users on older Android devices and people clicking links from Telegram. Canvas mode ensures the game runs everywhere.
Two-Scene Architecture
- BootScene — Loads all assets (sprites, sounds, textures)
- PlayScene — The actual game loop
This separation ensures assets are fully loaded before gameplay starts, preventing visual glitches.
The Road System: Infinite Scrolling
The neon highway is built with parallax scrolling layers:
- Background — Deep space/city gradient (slowest)
- Mid-ground — Distant buildings and neon lights
- Road surface — The actual riding lane (fastest)
- Foreground — Speed lines and particle effects
Each layer scrolls at a different speed, creating depth. The road itself is an infinitely tiling sprite that accelerates as stages progress.
Obstacle & Collectible Spawning
Obstacles spawn with increasing frequency and variety as the player progresses through stages. The spawner uses a weighted random system:
- Early game: Simple low obstacles (jump over them)
- Mid game: Mix of low and high obstacles (jump or duck)
- Late game: Rapid sequences requiring quick reflexes
Collectibles (diaries and coins) spawn in patterns — arcs, lines, zigzags — giving players something to chase between obstacles.
Power-Up System
Four power-ups drop randomly:
| Power-Up | Effect | Duration |
|---|---|---|
| 🛡️ Shield | Absorbs one hit | Until triggered |
| 🚀 Nitro | Speed boost + invincible | 3 seconds |
| 🧲 Magnet | Auto-collect nearby items | 5 seconds |
| 2️⃣ Double | 2x score multiplier | 8 seconds |
Power-ups have visual indicators — the rider glows, particles change color, and the HUD updates to show active effects.
Solana Wallet Integration
Here's where it gets interesting. The game connects to Solana wallets for identity and leaderboard tracking.
Multi-Wallet Support
We support 6 wallets with auto-detection and mobile deep linking:
const WALLETS = {
phantom: {
getProvider: () => window.phantom?.solana || window.solana,
deepLink: () => `https://phantom.app/ul/browse/${pageUrl}`,
},
solflare: {
getProvider: () => window.solflare,
deepLink: () => `https://solflare.com/ul/browse/?url=${pageUrl}`,
},
// + Backpack, Trust, Coinbase, OKX
};
The key insight: On mobile, if a wallet app isn't installed, we use deep links to open the game inside the wallet's built-in browser. This gives us wallet access without any extension installation.
If the user doesn't want to connect a wallet, they can play in Demo Mode — full game, just no leaderboard entries.
Read-Only Access
We only read the public address. No transaction signing, no token transfers, no approvals. The wallet connection is purely for identity:
const resp = await provider.connect();
walletPublicKey = (resp.publicKey || provider.publicKey).toString();
A prominent security message reassures users:
"Your wallet is safe. Connecting only lets us verify your address — we cannot access your funds."
Scoring & Anti-Cheat
Score Breakdown
Every game over screen shows a detailed score breakdown:
🛣️ Distance 342m +342
📔 Diaries ×5 +500
🪙 Coins ×12 +600
⚡ Power-ups +200
─────────────────────────
TOTAL 1,642
Scores are calculated server-side to prevent client manipulation. The game sends raw metrics (distance, items collected, duration) and the server computes the final score.
Anti-Cheat Measures
- Server-side session tracking — Each game starts with a server-generated session ID
- Duration validation — Impossibly short games with high scores are rejected
- Rate limiting — Maximum submissions per time window
- Score reasonability — Scores are validated against expected ranges for the reported distance/time
Daily Grand Prix Tournament
Every day, a new tournament runs automatically:
- 24-hour cycles — Timer resets at midnight UTC
- Live leaderboard — Top 3 shown as a podium (🥇🥈🥉)
- Real-time countdown — Visible both in-game and on the connect screen
- Auto-refresh — Leaderboard updates after every game
setInterval(() => {
if (countdownSeconds > 0) {
countdownSeconds--;
renderGpTimer();
}
}, 1000);
The countdown shows in the top banner with a prominent neon design — it's the first thing players see.
Quest System
Daily quests give players goals beyond just high scores:
- "Ride 500m in a single run"
- "Collect 10 diaries"
- "Play 3 games today"
- "Score over 1,000 points"
Quests are fetched per-wallet from the API and progress updates in real-time on the dashboard. Completed quests award bonus points.
Streak System
Play every day to build a streak:
- Day 1: No bonus
- Day 2: 🔥 +10% score bonus
- Day 3: 🔥🔥 +20% score bonus
- Day 7+: 🔥🔥🔥🔥🔥🔥🔥 +50% score bonus (cap)
Streaks are displayed prominently with fire emojis. Miss a day, streak resets to zero. It's a powerful retention mechanic.
Share on X Integration
After every game, players can share their score on X/Twitter with one click:
function shareScore() {
const text = `🏍️ I rode ${distance}m and scored ${score} ` +
`in MOTO: The Midnight Ride!\n\n` +
`🎮 Play: motorcyclediaries.fun/game\n` +
`$MOTO #Solana @motodiariesfun`;
window.open(`https://x.com/intent/tweet?text=${encodeURIComponent(text)}`);
}
Sharing also earns a server-tracked bonus — encouraging viral growth.
Referral System
Every player gets unique referral links. When someone clicks your link and:
- Visits the site: +5 points
- Connects wallet: +50 points
- Plays first game: +100 points
Referral tracking uses server-side code attribution with daily caps to prevent abuse.
Sound Design
The game has a complete audio system:
- Background music — Synthwave/neon theme (Web Audio API)
- Sound effects — Jump, collect, power-up, crash, stage transition
- Toggle button — Respects user preference, persists in localStorage
All audio is generated procedurally using the Web Audio API — no external audio files needed. This keeps the total game payload tiny.
Mobile Optimization
The game works on mobile through several adaptations:
- Touch controls — Tap top half = jump, tap bottom half = duck
-
Responsive scaling —
Phaser.Scale.FITensures the game canvas fills any screen - Deep linking — Opens inside wallet apps' built-in browsers
- Performance — Canvas renderer (not WebGL) for maximum compatibility
-
Viewport lock —
maximum-scale=1.0, user-scalable=noprevents accidental zoom
Game Over: The Fun Part
When you crash, a random humorous message appears:
"Your bike just filed for divorce!"
"Was that a crash or modern art? Hard to tell!"
"Your insurance company just blocked your number!"
"You crashed so hard, Bitcoin felt it!"
There are 25+ unique phrases that rotate randomly. It turns the frustration of dying into a moment of humor.
The Dashboard Connection
The game feeds into a comprehensive Rider Dashboard where connected players can see:
- Best Score / Total Rides / Streak
- Daily Quests progress
- Grand Prix standings
- Ride History with per-game breakdowns
- Achievements & Badges (First Ride, Veteran, Legend, etc.)
- Referral Stats with unique links
- Meme Contest integration with voting and sharing
The dashboard is a separate page that fetches all data from the same PHP API.
Backend: PHP API
The entire backend is a single PHP file that handles:
GET ?action=leaderboard → Daily top scores
GET ?action=profile → Player stats + badges
GET ?action=quests → Daily quest progress
GET ?action=history → Ride history
POST ?action=start_session → Begin new game (anti-cheat)
POST ?action=submit_score → End game + validate score
POST ?action=ref_code → Generate referral code
GET ?action=ref_stats → Referral statistics
GET ?action=announcements → Community announcements
GET ?action=community_stats → Global stats
Data is stored in JSON files with file locking (LOCK_EX). For our scale (hundreds of players, not millions), this is simpler and faster than a database.
Performance Stats
| Metric | Value |
|---|---|
| Game engine | Phaser 3 (Canvas) |
| Total JS size | ~180KB (Phaser) + ~25KB (game logic) |
| API response time | <50ms |
| First paint | <1.5s |
| Mobile compatible | Yes (iOS + Android) |
| Wallet support | 6 wallets |
| Total hosting cost | ~$3/month |
| Framework dependencies | 0 (besides Phaser) |
What We Learned
1. Phaser 3 is incredibly powerful for browser games
With Arcade Physics, sprite management, and auto-scaling, you can build a polished game without touching Unity or Unreal.
2. Canvas > WebGL for reach
WebGL is faster, but Canvas works everywhere. When your users are clicking links from Telegram on random phones, compatibility wins.
3. Wallet integration is easier than you think
Solana wallet providers inject a simple API. provider.connect() gives you a public key. That's it. No smart contracts needed for identity.
4. Anti-cheat needs server-side sessions
Client-side games are inherently insecure. Server sessions + validation + rate limiting makes cheating annoying enough that most won't bother.
5. Humor retains players
The random game-over phrases generate more engagement than any fancy animation. People screenshot them, share them, come back to see new ones.
Try It
- Play the game: motorcyclediaries.fun/game
- See the dashboard: motorcyclediaries.fun/dashboard
- Meme contest: motorcyclediaries.fun/memes
- Join the community: t.me/motodiariesbot
- Follow on X: @motodiariesfun
You can play in demo mode without a wallet, or connect any Solana wallet to compete on the leaderboard.
The Bigger Picture
This game is part of the Motorcycle Diaries ecosystem — a meme coin project built with $0 budget by three friends. No VC funding. No paid promoters. Just code, creativity, and stubbornness.
The game, the meme contest, the dashboard, the Telegram bot, the email verification system — all of it was built from scratch. Every line of code.
If you're a game dev thinking about adding crypto/wallet integration, it's simpler than the Web3 hype makes it sound. Connect wallet, read public key, use it as an identity. Done.
Born to Ride. Forced to HODL. Never stop building.
This is part of the Motorcycle Diaries Dev Log series. Read the previous post: We Built a Full Meme Contest Platform in One Day
$MOTO is a meme token on Solana for entertainment purposes. Not financial advice. DYOR.
Top comments (0)