If you enjoy casual games that are easy to learn but difficult to master, Egg Hill Racer is one of the most entertaining choices available. The game combines physics-based driving with a simple objective—carry a fragile egg across challenging hills without letting it fall.
Although the concept sounds straightforward, every hill, jump, and bump tests your driving skills. Accelerate too quickly, and the egg flies off. Brake too hard, and the vehicle loses momentum. Finding the perfect balance is what makes Egg Hill Racer incredibly addictive.
Whether you’re playing for a few minutes during a break or trying to beat your personal best, the game offers endless fun for players of all ages.
What Is Egg Hill Racer?
Egg Hill Racer is a physics-based driving game where players must transport an egg across rough terrain while keeping it safely balanced on top of their vehicle.
Instead of racing against opponents, your biggest challenge is controlling speed, maintaining balance, and traveling the longest possible distance.
The realistic physics engine ensures that every movement affects the egg’s position. Even a small mistake can end your run instantly.
Why Is Egg Hill Racer So Popular?
Several factors contribute to the popularity of Egg Hill Racer.
1. Easy to Learn
The controls are extremely simple. Most versions require only acceleration and braking.
Anyone can start playing within seconds.
2. Difficult to Master
While learning the controls takes only minutes, mastering the game requires excellent timing and precision.
Players constantly improve their driving techniques to achieve longer distances.
3. Endless Gameplay
Most versions feature endless terrain.
There is no finish line.
Instead, the objective is to beat your previous high score.
4. Realistic Physics
Unlike many arcade games, Egg Hill Racer depends heavily on realistic movement.
Every acceleration changes the egg’s position.
Every hill changes the vehicle’s balance.
5. Short Game Sessions
A single run usually lasts only a few minutes.
This makes the game perfect for:
- School breaks
- Office breaks
- Casual gaming
- Mobile gaming
Gameplay Overview
The gameplay revolves around one simple objective:
Drive as far as possible without dropping the egg.
Players control:
- Acceleration
- Braking
The game automatically calculates:
- Vehicle momentum
- Egg movement
- Gravity
- Hill slope
- Landing force
Because of this, every run feels unique.
Controls
Most versions use very simple controls.
Mobile
- Left Button – Brake
- Right Button – Accelerate
PC
Usually:
- Arrow Keys
- A/D Keys
- Mouse Buttons
Some browser versions may offer customizable controls.
Game Features
Physics-Based Driving
The physics engine makes every hill different.
Unlike traditional racing games, speed alone does not guarantee success.
Multiple Vehicles
Many versions allow players to unlock different vehicles.
Examples include:
- Jeep
- Pickup Truck
- SUV
- Monster Truck
- Sports Car
Each vehicle handles differently.
Coin Collection
Coins appear throughout the track.
Players collect them to unlock:
- New vehicles
- Cosmetic upgrades
- Performance improvements
Increasing Difficulty
As players travel farther:
- Hills become steeper.
- Jumps become larger.
- Landings become rougher.
- Balance becomes more challenging.
Best Beginner Tips
Drive Slowly
The biggest mistake beginners make is driving too fast.
Slow driving improves stability.
Avoid Sudden Braking
Hard braking throws the egg forward.
Gentle braking is much safer.
Climb Hills Carefully
Approach steep hills with controlled speed.
Too much acceleration causes the egg to bounce.
Land Smoothly
After every jump:
- Reduce speed
- Keep the vehicle level
- Avoid nose-diving
Smooth landings protect the egg.
Collect Coins Safely
Don’t risk dropping the egg just to collect every coin.
Sometimes it’s better to skip difficult coins.
Advanced Strategies
Experienced players focus on maintaining momentum rather than maximum speed.
Keep Constant Speed
Consistent speed reduces bouncing.
Read the Terrain
Look ahead whenever possible.
Prepare for:
- Large hills
- Deep valleys
- Sharp landings
Control Air Time
When airborne:
Reduce acceleration.
Keep the vehicle balanced.
Learn Vehicle Characteristics
Different vehicles react differently.
Some:
- Climb better
- Jump farther
- Stay balanced longer
Experiment before choosing your favorite.
Common Mistakes
Many new players repeatedly make the same errors.
Driving Too Fast
Speed creates instability.
Ignoring Terrain
Every hill requires a different approach.
Panic Braking
Sudden braking often throws the egg off the vehicle.
Chasing Coins
Distance is usually more valuable than coins.
Why Physics Makes the Game Unique
Most racing games focus on speed.
Egg Hill Racer focuses on:
- Balance
- Weight distribution
- Suspension
- Gravity
- Momentum
These mechanics make every attempt feel different.
No two runs are exactly the same.
Best Vehicles
Depending on the version, players often prefer vehicles with stable suspension.
Popular choices include:
- Jeep
- SUV
- Pickup Truck
These usually provide better stability than lightweight sports cars.
How to Get Higher Scores
To improve your personal best:
- Stay calm.
- Drive smoothly.
- Don’t rush.
- Avoid unnecessary jumps.
- Practice regularly.
- Learn terrain patterns.
- Upgrade vehicles wisely.
Consistency beats speed.
Is Egg Hill Racer Good for Kids?
Yes.
The game contains:
- No violence
- No graphic content
- Simple controls
- Family-friendly gameplay
It also encourages patience and hand-eye coordination.
Parents should still monitor playtime to maintain a healthy balance between gaming and other activities.
Can You Play Offline?
Some mobile versions support offline gameplay after installation.
Browser-based versions generally require an internet connection, while downloadable apps may not.
Availability depends on the specific version you choose.
Tips for Mobile Players
- Use both thumbs.
- Hold your phone steadily.
- Avoid playing with low battery if performance drops.
- Keep your screen clean for better control.
- Turn off unnecessary notifications to avoid interruptions.
Tips for PC Players
- Use a responsive keyboard.
- Play in full-screen mode if available.
- Avoid running too many background applications.
- Practice using keyboard controls for smoother acceleration and braking.
Why Players Keep Coming Back
Egg Hill Racer offers the perfect combination of simplicity and challenge.
Every failure feels fair because it results from player input rather than random chance.
This creates a rewarding gameplay loop:
- Play
- Improve
- Beat your previous score
- Repeat
That sense of constant improvement is what keeps players engaged.
Frequently Asked Questions
Is Egg Hill Racer free?
Many browser and mobile versions are free to play, though some may include optional advertisements or in-app purchases.
Can I play Egg Hill Racer online?
Yes. Several gaming websites offer browser versions that run without requiring a download.
Is there an ending?
Most versions feature endless gameplay where the objective is to travel as far as possible.
Does the game require fast reflexes?
Yes, but smooth control and patience are often more important than quick reactions.
Which vehicle is best?
There is no universally best vehicle. Players should choose the one that matches their driving style and offers the balance they prefer.
Can children play Egg Hill Racer?
Yes. The game is generally suitable for players of all ages due to its simple mechanics and non-violent gameplay.
Final Thoughts
Egg Hill Racer proves that a great game doesn’t need complicated mechanics or high-end graphics to be enjoyable. Its clever combination of realistic physics, simple controls, and endless challenges makes it appealing to beginners and experienced players alike.
Success in Egg Hill Racer comes from patience, precision, and practice rather than speed alone. Every hill presents a new challenge, every jump tests your control, and every successful run motivates you to go even farther.
Whether you’re looking for a quick gaming break or aiming to set a new high score, Egg Hill Racer offers an engaging experience that is easy to pick up and difficult to put down. If you enjoy physics-based driving games that reward skill and consistency, Egg Hill Racer deserves a place on your playlist.
Crashed!
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>Egg Hill Racer</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; -webkit-tap-highlight-color: transparent; user-select:none; }
html, body { width:100%; height:100%; overflow:hidden; background:#0e1620; font-family: 'Trebuchet MS', 'Segoe UI', sans-serif; }
#wrap {
position:relative;
width:100%; height:100%;
max-width:960px; max-height:640px;
margin:auto;
inset:0;
position:absolute;
border-radius:14px;
overflow:hidden;
box-shadow: 0 0 0 6px #0e1620, 0 20px 60px rgba(0,0,0,0.6);
}
canvas { display:block; width:100%; height:100%; touch-action:none; }
#hud {
position:absolute; inset:0; pointer-events:none;
font-weight:800; color:#fff;
}
.coinBadge {
position:absolute; top:14px; left:14px;
background:#f5a623; border:3px solid #d98c0f;
border-radius:24px; padding:6px 16px 6px 10px;
display:flex; align-items:center; gap:6px;
box-shadow: 0 3px 0 #b9700a;
font-size:18px; color:#fff; text-shadow:0 1px 2px rgba(0,0,0,.3);
}
.coinIcon {
width:22px; height:22px; border-radius:50%;
background:radial-gradient(circle at 35% 30%, #fff3c4, #ffcf3f 55%, #e8a415 100%);
border:2px solid #c9860a;
}
.bestBadge {
position:absolute; top:10px; right:14px; text-align:center;
color:#ffd35c; font-size:12px; letter-spacing:1px;
text-shadow:0 2px 3px rgba(0,0,0,.5);
}
.bestBadge .num { font-size:20px; color:#fff; display:block; }
#distWrap {
position:absolute; top:14px; left:50%; transform:translateX(-50%);
width:52%; text-align:center;
}
#distTrack {
height:10px; border-radius:6px; background:rgba(0,0,0,.55);
position:relative; overflow:hidden; border:2px solid rgba(0,0,0,.4);
}
#distFill {
height:100%; width:0%; background:linear-gradient(90deg,#3a3a3a,#111);
transition: width .15s linear;
}
#distText {
margin-top:6px; font-size:20px; color:#ffd35c; text-shadow:0 2px 3px rgba(0,0,0,.5);
}
#pauseBtn {
position:absolute; bottom:14px; left:14px;
width:52px; height:52px; border-radius:50%;
background:#f5a623; border:3px solid #d98c0f; box-shadow:0 3px 0 #b9700a;
display:flex; align-items:center; justify-content:center;
pointer-events:auto; cursor:pointer;
}
#pauseBtn svg { width:20px; height:20px; }
.pedal {
position:absolute; bottom:10px; width:19%; max-width:140px; aspect-ratio: 1/1.4;
background:linear-gradient(160deg,#8b93a0,#5a6270);
border-radius:14px; border:3px solid #454c58;
box-shadow: 0 6px 0 #3a4048, inset 0 2px 4px rgba(255,255,255,.25);
pointer-events:auto; cursor:pointer;
display:grid; grid-template-columns:1fr 1fr; gap:8%; padding:12%;
}
.pedal.active { transform: translateY(4px); box-shadow: 0 2px 0 #3a4048, inset 0 2px 4px rgba(255,255,255,.25); }
.pedal span {
border-radius:50%; background:radial-gradient(circle at 35% 30%, #e2564a, #a8241c 70%);
border:2px solid #7a180f;
}
#brakePedal { left:2%; }
#gasPedal { right:2%; }
.gauge {
position:absolute; bottom:8px; width:17%; max-width:120px; aspect-ratio:1/1;
border-radius:50%; background:radial-gradient(circle at 40% 35%, #2a2a2a, #0a0a0a 75%);
border:5px solid #1a1a1a; box-shadow:0 4px 10px rgba(0,0,0,.5), inset 0 0 10px rgba(0,0,0,.8);
}
#rpmGauge { left:34%; }
#boostGauge { right:34%; }
.gauge .label { position:absolute; top:28%; left:0; right:0; text-align:center; font-size:10px; color:#ddd; letter-spacing:1px; }
.gauge .needle {
position:absolute; left:50%; top:50%; width:2px; height:38%;
background:#e8433a; transform-origin:bottom center; border-radius:2px;
transform: translate(-50%,-100%) rotate(-60deg);
box-shadow: 0 0 4px rgba(232,67,58,.8);
}
.gauge .hub { position:absolute; left:50%; top:50%; width:10px; height:10px; background:#ccc; border-radius:50%; transform:translate(-50%,-50%); }
#overlay {
position:absolute; inset:0; background:rgba(10,14,22,.82);
display:none; align-items:center; justify-content:center; flex-direction:column;
color:#fff; text-align:center; z-index:20; pointer-events:auto;
}
#overlay h1 { font-size:34px; color:#ffd35c; text-shadow:0 3px 4px rgba(0,0,0,.5); margin-bottom:6px; }
#overlay p { font-size:16px; margin:3px 0; color:#dce3ee; }
#overlay .row { display:flex; gap:22px; margin:16px 0; }
#overlay .stat { background:rgba(255,255,255,.08); padding:10px 18px; border-radius:12px; }
#overlay .stat b { display:block; font-size:22px; color:#fff; }
#overlay button {
margin-top:14px; padding:12px 32px; font-size:18px; font-weight:800;
background:#f5a623; border:3px solid #d98c0f; box-shadow:0 4px 0 #b9700a;
border-radius:30px; color:#fff; cursor:pointer;
}
#overlay button:active { transform:translateY(3px); box-shadow:0 1px 0 #b9700a; }
#startHint {
position:absolute; bottom:38%; left:50%; transform:translateX(-50%);
color:#fff; font-size:15px; background:rgba(0,0,0,.4); padding:8px 16px; border-radius:20px;
display:none; pointer-events:none;
}
</style>
</head>
<body>
<div id="wrap">
<canvas id="game"></canvas>
<div id="hud">
<div class="coinBadge"><span class="coinIcon"></span><span id="coinCount">0</span></div>
<div id="distWrap">
<div id="distTrack"><div id="distFill"></div></div>
<div id="distText">0 m</div>
</div>
<div class="bestBadge">BEST<span class="num" id="bestNum">0 m</span></div>
<div id="pauseBtn">
<svg viewBox="0 0 24 24" fill="white"><rect x="5" y="4" width="5" height="16" rx="1"/><rect x="14" y="4" width="5" height="16" rx="1"/></svg>
</div>
<div class="gauge" id="rpmGauge"><div class="label">RPM</div><div class="needle" id="rpmNeedle"></div><div class="hub"></div></div>
<div class="gauge" id="boostGauge"><div class="label">BOOST</div><div class="needle" id="boostNeedle"></div><div class="hub"></div></div>
<div class="pedal" id="brakePedal"><span></span><span></span><span></span><span></span><span></span><span></span></div>
<div class="pedal" id="gasPedal"><span></span><span></span><span></span><span></span><span></span><span></span></div>
<div id="startHint">Hold the right pedal to drive!</div>
</div>
<div id="overlay">
<h1 id="overlayTitle">Crashed!</h1>
<div class="row">
<div class="stat">DISTANCE<b id="finalDist">0 m</b></div>
<div class="stat">COINS<b id="finalCoins">0</b></div>
<div class="stat">BEST<b id="finalBest">0 m</b></div>
</div>
<button id="restartBtn">Try Again</button>
</div>
</div>
<script>
(function(){
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
const wrap = document.getElementById('wrap');
let W, H, DPR;
function resize(){
DPR = Math.min(window.devicePixelRatio||1, 2);
const r = wrap.getBoundingClientRect();
W = r.width; H = r.height;
canvas.width = W*DPR; canvas.height = H*DPR;
ctx.setTransform(DPR,0,0,DPR,0,0);
}
window.addEventListener('resize', resize);
resize();
// ---------- Game state ----------
let coins = 0;
let best = 0;
let running = true;
let paused = false;
let gameOver = false;
let time = 0;
const groundSplit = 0.62; // fraction of height where sky/dirt divides (road baseline area)
// Terrain generation (procedural, smooth rolling hills)
const SCALE_PX_PER_M = 12; // pixels per meter horizontally at car
function terrainHeight(xMeters){
// sum of sines for rolling hills, plus a flat starting pad
if (xMeters < 6) return 0;
const x = xMeters - 6;
let h = 0;
h += Math.sin(x*0.045)*38;
h += Math.sin(x*0.11 + 1.4)*18;
h += Math.sin(x*0.021 + 0.6)*55;
h += Math.sin(x*0.003 + 3.1)*70;
return h;
}
function terrainSlope(xMeters){
const d = 0.4;
return (terrainHeight(xMeters+d) - terrainHeight(xMeters-d)) / (d*2);
}
// Coins placed along track
let coinMarkers = [];
function ensureCoins(uptoMeters){
let lastM = coinMarkers.length ? coinMarkers[coinMarkers.length-1].m : 20;
while(lastM < uptoMeters + 200){
lastM += 14 + Math.random()*22;
coinMarkers.push({m:lastM, taken:false});
}
}
ensureCoins(300);
// Trees / decoration (parallax, purely visual, deterministic pseudo-random)
function seededRand(seed){ let s = seed*9301+49297; return ((s % 233280)/233280); }
let decorations = [];
function ensureDecor(uptoMeters){
let lastM = decorations.length ? decorations[decorations.length-1].m : 0;
let i = decorations.length;
while(lastM < uptoMeters + 250){
lastM += 18 + seededRand(i*7.31)*30;
const r = seededRand(i*3.7+1);
decorations.push({m:lastM, type: r<0.5?'tree':(r<0.8?'palm':'rock')});
i++;
}
}
ensureDecor(300);
// Car physics
const car = {
xM: 0, // position in meters
v: 0, // velocity m/s
angle: 0, // current visual rotation (radians)
wheelBase: 2.0, // meters between wheels
crashed: false,
airTime: 0,
};
const GRAVITY_ACCEL = 9.0; // effect of slope on velocity
const ENGINE_ACCEL = 7.5;
const BOOST_ACCEL = 13.5;
const BRAKE_DECEL = 10.0;
const ROLL_FRICTION = 1.1;
const MAX_SPEED = 26;
const MAX_REVERSE = -7;
let gasHeld = false;
let brakeHeld = false;
let boostFuel = 100;
let boostActive = false;
function resetGame(){
car.xM = 0; car.v = 0; car.angle = 0; car.crashed = false; car.airTime = 0;
coins = 0;
boostFuel = 100;
gameOver = false;
time = 0;
coinMarkers.forEach(c=>c.taken=false);
updateHUD();
document.getElementById('overlay').style.display = 'none';
document.getElementById('startHint').style.display = 'block';
setTimeout(()=>{ document.getElementById('startHint').style.display='none'; }, 2400);
}
function updateHUD(){
document.getElementById('coinCount').textContent = coins;
const distM = Math.max(0, Math.floor(car.xM));
document.getElementById('distText').textContent = distM + ' m';
document.getElementById('bestNum').textContent = Math.max(best, distM) + ' m';
const trackTotal = 400; // visual progress bar caps at 400m for the fill bar sense
document.getElementById('distFill').style.width = Math.min(100, (distM/trackTotal)*100) + '%';
}
function endRun(reason){
if (gameOver) return;
gameOver = true;
running = false;
const distM = Math.floor(car.xM);
if (distM > best) best = distM;
document.getElementById('overlayTitle').textContent = reason;
document.getElementById('finalDist').textContent = distM + ' m';
document.getElementById('finalCoins').textContent = coins;
document.getElementById('finalBest').textContent = best + ' m';
document.getElementById('overlay').style.display = 'flex';
}
// ---------- Input ----------
function bindPedal(el, onDown, onUp){
el.addEventListener('pointerdown', e=>{ e.preventDefault(); el.classList.add('active'); onDown(); });
['pointerup','pointerleave','pointercancel'].forEach(ev=>{
el.addEventListener(ev, e=>{ el.classList.remove('active'); onUp(); });
});
}
const gasPedal = document.getElementById('gasPedal');
const brakePedal = document.getElementById('brakePedal');
bindPedal(gasPedal, ()=>{ gasHeld = true; if(gameOver) resetGame(); }, ()=>{ gasHeld = false; });
bindPedal(brakePedal, ()=>{ brakeHeld = true; }, ()=>{ brakeHeld = false; });
window.addEventListener('keydown', e=>{
if (e.code === 'ArrowRight' || e.code === 'Space' || e.code === 'KeyW'){ gasHeld = true; if(gameOver) resetGame(); }
if (e.code === 'ArrowLeft' || e.code === 'KeyS'){ brakeHeld = true; }
if (e.code === 'ShiftLeft' || e.code === 'ShiftRight'){ boostActive = true; }
});
window.addEventListener('keyup', e=>{
if (e.code === 'ArrowRight' || e.code === 'Space' || e.code === 'KeyW'){ gasHeld = false; }
if (e.code === 'ArrowLeft' || e.code === 'KeyS'){ brakeHeld = false; }
if (e.code === 'ShiftLeft' || e.code === 'ShiftRight'){ boostActive = false; }
});
document.getElementById('pauseBtn').addEventListener('click', ()=>{
if (gameOver) return;
paused = !paused;
});
document.getElementById('restartBtn').addEventListener('click', resetGame);
// ---------- Physics update ----------
function update(dt){
if (paused || gameOver) return;
time += dt;
// Boost kicks in automatically once you're already rolling and holding gas, as long as fuel remains
let usingBoost = gasHeld && !brakeHeld && boostFuel > 0 && car.v > 5;
if (usingBoost) {
boostFuel = Math.max(0, boostFuel - dt*28);
} else {
boostFuel = Math.min(100, boostFuel + dt*6);
}
const slope = terrainSlope(car.xM);
const slopeAngle = Math.atan(slope/ (SCALE_PX_PER_M)); // approx radians of incline
let accel = 0;
if (gasHeld) accel += usingBoost ? BOOST_ACCEL : ENGINE_ACCEL;
if (brakeHeld) accel -= BRAKE_DECEL;
// gravity component along slope (uphill slows you, downhill speeds you)
accel += -slope * 0.14 * GRAVITY_ACCEL;
// rolling friction
accel += -Math.sign(car.v) * ROLL_FRICTION * Math.min(1, Math.abs(car.v)/6);
car.v += accel*dt;
car.v = Math.max(MAX_REVERSE, Math.min(MAX_SPEED, car.v));
car.xM += car.v*dt;
if (car.xM < 0) car.xM = 0;
// visual angle follows terrain slope smoothly
const targetAngle = Math.atan2(terrainHeight(car.xM+car.wheelBase/2)-terrainHeight(car.xM-car.wheelBase/2), car.wheelBase*SCALE_PX_PER_M);
car.angle += (targetAngle - car.angle) * Math.min(1, dt*8);
// crash if too steep flip (nose/tail dragging) - simplistic: extreme angle sustained
if (Math.abs(car.angle) > 1.15){
endRun('Flipped Over!');
}
// coin collection
ensureCoins(car.xM);
ensureDecor(car.xM);
for (const c of coinMarkers){
if (!c.taken && Math.abs(c.m - car.xM) < 0.9){
c.taken = true; coins++;
}
}
updateHUD();
updateGauges();
}
function updateGauges(){
const rpmNeedle = document.getElementById('rpmNeedle');
const boostNeedle = document.getElementById('boostNeedle');
const speedFrac = Math.max(0, Math.min(1, (car.v - MAX_REVERSE)/(MAX_SPEED-MAX_REVERSE)));
const rpmDeg = -60 + speedFrac*120; // -60 to 60
rpmNeedle.style.transform = `translate(-50%,-100%) rotate(${rpmDeg}deg)`;
const boostDeg = -60 + (boostFuel/100)*120;
boostNeedle.style.transform = `translate(-50%,-100%) rotate(${boostDeg}deg)`;
}
// ---------- Rendering ----------
function worldToScreenX(xMeters){
// camera centered so the car sits ~32% from left
const camOffsetM = car.xM - (W*0.30)/SCALE_PX_PER_M;
return (xMeters - camOffsetM) * SCALE_PX_PER_M;
}
function groundBaseY(){ return H*groundSplit; }
function drawSky(){
const grad = ctx.createLinearGradient(0,0,0,H*groundSplit);
grad.addColorStop(0, '#57c9f0');
grad.addColorStop(1, '#8fe0f5');
ctx.fillStyle = grad;
ctx.fillRect(0,0,W,H*groundSplit);
// sun
const sunX = W*0.22, sunY = H*0.16, sunR = Math.min(W,H)*0.09;
const sg = ctx.createRadialGradient(sunX,sunY,0,sunX,sunY,sunR*2.2);
sg.addColorStop(0,'rgba(255,235,130,0.9)');
sg.addColorStop(1,'rgba(255,235,130,0)');
ctx.fillStyle = sg;
ctx.beginPath(); ctx.arc(sunX,sunY,sunR*2.2,0,Math.PI*2); ctx.fill();
ctx.fillStyle = '#ffd93d';
ctx.beginPath(); ctx.arc(sunX,sunY,sunR,0,Math.PI*2); ctx.fill();
}
function drawMountains(){
const layers = [
{parallax:0.15, color:'#9fd6e0', baseH:H*groundSplit*0.55, amp:38, freq:0.006},
{parallax:0.28, color:'#7fcfd8', baseH:H*groundSplit*0.62, amp:46, freq:0.009},
];
layers.forEach((layer, li)=>{
ctx.fillStyle = layer.color;
ctx.beginPath();
ctx.moveTo(0, H*groundSplit);
const camPx = car.xM*SCALE_PX_PER_M*layer.parallax;
for (let sx=0; sx<=W; sx+=16){
const worldX = sx + camPx;
const y = layer.baseH - (Math.sin(worldX*layer.freq + li*2)*layer.amp + Math.sin(worldX*layer.freq*2.3+1)*layer.amp*0.4);
ctx.lineTo(sx, y);
}
ctx.lineTo(W, H*groundSplit);
ctx.closePath();
ctx.fill();
});
}
function drawTree(px, py, scale, type){
ctx.save();
ctx.translate(px,py);
ctx.scale(scale,scale);
if (type==='tree'){
ctx.fillStyle = '#8a5a34';
ctx.fillRect(-4,-10,8,26);
ctx.fillStyle = '#3d9a4f';
ctx.beginPath(); ctx.moveTo(0,-70); ctx.lineTo(-26,-14); ctx.lineTo(26,-14); ctx.closePath(); ctx.fill();
ctx.beginPath(); ctx.moveTo(0,-52); ctx.lineTo(-22,-4); ctx.lineTo(22,-4); ctx.closePath(); ctx.fill();
} else if (type==='palm'){
ctx.strokeStyle = '#8a6a3a'; ctx.lineWidth=6; ctx.lineCap='round';
ctx.beginPath(); ctx.moveTo(0,16); ctx.quadraticCurveTo(6,-20,2,-50); ctx.stroke();
ctx.fillStyle = '#4caf62';
for (let i=0;i<5;i++){
ctx.save(); ctx.translate(2,-50); ctx.rotate(i*(Math.PI*2/5));
ctx.beginPath(); ctx.ellipse(14,0,18,7,0,0,Math.PI*2); ctx.fill();
ctx.restore();
}
} else {
ctx.fillStyle = '#9aa5ad';
ctx.beginPath(); ctx.ellipse(0,0,16,10,0,0,Math.PI*2); ctx.fill();
}
ctx.restore();
}
function drawGround(){
// dirt road path (top edge follows terrain), grass strip on top edge, red dirt fill below
const baseY = groundBaseY();
ctx.beginPath();
ctx.moveTo(0, H);
const pts = [];
for (let sx=-10; sx<=W+10; sx+=6){
const worldM = (sx + car.xM*SCALE_PX_PER_M - W*0.30)/SCALE_PX_PER_M;
const h = terrainHeight(worldM);
const y = baseY - h;
pts.push([sx,y]);
}
ctx.moveTo(0,H);
ctx.lineTo(pts[0][0], pts[0][1]);
pts.forEach(p=> ctx.lineTo(p[0],p[1]));
ctx.lineTo(W,H);
ctx.closePath();
const dg = ctx.createLinearGradient(0, baseY-100, 0, H);
dg.addColorStop(0,'#c85a3c');
dg.addColorStop(1,'#a8432a');
ctx.fillStyle = dg;
ctx.fill();
// grass cap
ctx.beginPath();
ctx.moveTo(pts[0][0], pts[0][1]);
pts.forEach(p=> ctx.lineTo(p[0],p[1]));
for (let i=pts.length-1;i>=0;i--){
ctx.lineTo(pts[i][0], pts[i][1]+14);
}
ctx.closePath();
ctx.fillStyle = '#6fce55';
ctx.fill();
// speckles in dirt
ctx.fillStyle = 'rgba(0,0,0,0.08)';
for (let i=0;i<40;i++){
const sx = (i*53 + (car.xM*40)%53*0) % (W+40) - 20;
const sy = baseY + 40 + (i*29)%(H-baseY-40);
ctx.beginPath(); ctx.arc(sx,sy,3,0,Math.PI*2); ctx.fill();
}
}
function drawDecorations(){
decorations.forEach(d=>{
const sx = worldToScreenX(d.m);
if (sx < -60 || sx > W+60) return;
const y = groundBaseY() - terrainHeight(d.m);
drawTree(sx, y, 1, d.type);
});
}
function drawCoins(){
coinMarkers.forEach(c=>{
if (c.taken) return;
const sx = worldToScreenX(c.m);
if (sx < -30 || sx > W+30) return;
const y = groundBaseY() - terrainHeight(c.m) - 55;
const bob = Math.sin(time*3 + c.m)*4;
ctx.save();
ctx.translate(sx, y+bob);
const g = ctx.createRadialGradient(-3,-3,1,0,0,12);
g.addColorStop(0,'#fff3c4'); g.addColorStop(0.6,'#ffcf3f'); g.addColorStop(1,'#e8a415');
ctx.fillStyle = g;
ctx.beginPath(); ctx.arc(0,0,11,0,Math.PI*2); ctx.fill();
ctx.strokeStyle = '#c9860a'; ctx.lineWidth=2; ctx.stroke();
ctx.restore();
});
}
function drawBestFlag(){
if (best <= 0) return;
const sx = worldToScreenX(best);
if (sx < -20 || sx > W+20) return;
const y = groundBaseY() - terrainHeight(best);
ctx.save();
ctx.strokeStyle = 'rgba(255,255,255,0.8)';
ctx.setLineDash([6,6]);
ctx.lineWidth = 2;
ctx.beginPath(); ctx.moveTo(sx, 0); ctx.lineTo(sx, H*groundSplit); ctx.stroke();
ctx.setLineDash([]);
// flag pole
ctx.strokeStyle = '#555'; ctx.lineWidth=3;
ctx.beginPath(); ctx.moveTo(sx, y); ctx.lineTo(sx, y-46); ctx.stroke();
ctx.fillStyle = '#e8433a';
ctx.beginPath(); ctx.moveTo(sx,y-46); ctx.lineTo(sx+22,y-38); ctx.lineTo(sx,y-30); ctx.closePath(); ctx.fill();
ctx.restore();
}
function drawBarrier(sx, y){
ctx.save();
ctx.translate(sx,y);
ctx.fillStyle = '#8a8f96';
ctx.fillRect(-22,-46,4,46);
ctx.fillRect(18,-46,4,46);
for (let i=0;i<2;i++){
ctx.save();
ctx.translate(0,-38+i*20);
ctx.rotate(-0.05);
ctx.fillStyle = '#f5a623';
ctx.fillRect(-24,-6,48,12);
ctx.fillStyle = '#2b2b2b';
for (let s=-20;s<20;s+=12){ ctx.fillRect(s,-6,6,12); }
ctx.restore();
}
ctx.restore();
}
function drawCar(){
const sx = worldToScreenX(car.xM);
const sy = groundBaseY() - terrainHeight(car.xM) - 8;
ctx.save();
ctx.translate(sx, sy);
ctx.rotate(car.angle);
// shadow
ctx.save();
ctx.globalAlpha = 0.2;
ctx.fillStyle = '#000';
ctx.beginPath(); ctx.ellipse(0, 34, 46, 8, 0, 0, Math.PI*2); ctx.fill();
ctx.restore();
// wheels
function wheel(wx){
ctx.save();
ctx.translate(wx, 26);
ctx.fillStyle = '#2b2b2b';
ctx.beginPath(); ctx.arc(0,0,17,0,Math.PI*2); ctx.fill();
ctx.fillStyle = '#c9ccd1';
ctx.beginPath(); ctx.arc(0,0,7,0,Math.PI*2); ctx.fill();
ctx.strokeStyle = '#555'; ctx.lineWidth=2;
const spin = (car.xM*SCALE_PX_PER_M*0.3) % (Math.PI*2);
for (let i=0;i<3;i++){
ctx.save(); ctx.rotate(spin + i*(Math.PI*2/3));
ctx.beginPath(); ctx.moveTo(0,0); ctx.lineTo(0,7); ctx.stroke();
ctx.restore();
}
ctx.restore();
}
wheel(-24); wheel(24);
// body
ctx.fillStyle = '#e0392f';
roundRectPath(-40,-2,80,26,8);
ctx.fill();
ctx.fillStyle = '#c72e25';
roundRectPath(-40,14,80,10,4);
ctx.fill();
// seat back
ctx.fillStyle = '#2b2b2b';
roundRectPath(-8,-30,10,30,3);
ctx.fill();
// windshield hint / headlight
ctx.fillStyle = '#ffd35c';
ctx.beginPath(); ctx.arc(38,10,4,0,Math.PI*2); ctx.fill();
// egg driver
ctx.save();
ctx.translate(-3,-30);
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.ellipse(0,0,15,19,0,0,Math.PI*2);
ctx.fill();
ctx.strokeStyle = '#e5e5e5'; ctx.lineWidth=1.2; ctx.stroke();
// face
ctx.fillStyle = '#2b2b2b';
ctx.beginPath(); ctx.arc(-5,-2,1.8,0,Math.PI*2); ctx.fill();
ctx.beginPath(); ctx.arc(5,-2,1.8,0,Math.PI*2); ctx.fill();
ctx.strokeStyle = '#2b2b2b'; ctx.lineWidth=2; ctx.lineCap='round';
ctx.beginPath(); ctx.arc(0,3,7,0.15*Math.PI,0.85*Math.PI); ctx.stroke();
ctx.restore();
ctx.restore();
}
function roundRectPath(x,y,w,h,r){
ctx.beginPath();
ctx.moveTo(x+r,y);
ctx.arcTo(x+w,y,x+w,y+h,r);
ctx.arcTo(x+w,y+h,x,y+h,r);
ctx.arcTo(x,y+h,x,y,r);
ctx.arcTo(x,y,x+w,y,r);
ctx.closePath();
}
function draw(){
ctx.clearRect(0,0,W,H);
drawSky();
drawMountains();
drawGround();
drawDecorations();
drawBestFlag();
drawCoins();
// start barrier decoration near x=3m
const bx = worldToScreenX(3);
if (bx > -60 && bx < W+60) drawBarrier(bx, groundBaseY()-terrainHeight(3));
drawCar();
}
// ---------- Main loop ----------
let last = performance.now();
function loop(now){
const dt = Math.min(0.033, (now-last)/1000);
last = now;
update(dt);
draw();
requestAnimationFrame(loop);
}
updateHUD();
document.getElementById('startHint').style.display = 'block';
setTimeout(()=>{ document.getElementById('startHint').style.display='none'; }, 2600);
requestAnimationFrame(loop);
})();
</script>
</body>
</html>