Hill Climb Adventure
by BlazeGalaxy19343 lines11.5 KB
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>Hill Climb Adventure</title>
<style>
*{box-sizing:border-box}
body{margin:0;background:#182b37;color:white;font-family:Arial;overflow:hidden}
canvas{display:block;width:100vw;height:100vh}
#hud{position:absolute;top:10px;left:12px;right:12px;display:flex;justify-content:space-between;pointer-events:none;font-weight:bold;text-shadow:1px 2px 3px black}
.panel{position:absolute;inset:0;background:#101c22ed;display:flex;align-items:center;justify-content:center;flex-direction:column;gap:12px;text-align:center;padding:15px}
h1{color:#ffce45;margin:4px}
button{border:0;border-radius:10px;padding:12px 17px;background:#ffbd35;color:#202020;font-weight:bold;font-size:15px;cursor:pointer}
button:active{transform:scale(.94)}
#controls{position:absolute;bottom:15px;left:12px;right:12px;display:flex;justify-content:space-between;align-items:end}
.pad{display:flex;gap:9px}
.control{width:65px;height:60px;background:#182b37cc;color:white;border:2px solid #ffffff80;font-size:23px}
#boost{background:#df7525}
.small{font-size:12px;color:#ddd}
#shop{max-width:520px;width:96%;max-height:85vh;overflow:auto}
.vehicle{display:flex;justify-content:space-between;align-items:center;background:#29414d;padding:9px;margin:5px;border-radius:9px;gap:8px}
.vehicle button{padding:8px}
.hidden{display:none!important}
</style>
</head>
<body>
<canvas id="game"></canvas>
<div id="hud" class="hidden">
<div>
<div>🪙 <span id="coins">0</span></div>
<div>⛽ <span id="fuel">100</span>%</div>
<div>📏 <span id="distance">0</span> m</div>
</div>
<div id="vehicleName">CAR</div>
<div>🏁 <span id="best">0</span> m</div>
</div>
<div id="menu" class="panel">
<h1>🚙 HILL CLIMB ADVENTURE</h1>
<p>Drive • Collect • Upgrade • Explore</p>
<button onclick="startGame()">PLAY GAME</button>
<button onclick="openShop()">VEHICLE GARAGE</button>
<button onclick="openMaps()">MAP SHOP</button>
<p class="small">WASD / Arrow keys • Space = Boost</p>
</div>
<div id="shop" class="panel hidden">
<h1>🚘 VEHICLE GARAGE</h1>
<div id="vehicles"></div>
<button onclick="closeShop()">BACK</button>
</div>
<div id="maps" class="panel hidden">
<h1>🌍 MAP SHOP</h1>
<div id="mapList"></div>
<button onclick="closeShop()">BACK</button>
</div>
<div id="over" class="panel hidden">
<h1>GAME OVER!</h1>
<h2 id="result">Distance: 0m</h2>
<button onclick="startGame()">TRY AGAIN</button>
<button onclick="goMenu()">MAIN MENU</button>
</div>
<div id="controls" class="hidden">
<div class="pad">
<button class="control" id="brake">◀</button>
<button class="control" id="gas">▶</button>
</div>
<button class="control" id="boost">⚡</button>
</div>
<script>
const canvas=document.getElementById("game");
const ctx=canvas.getContext("2d");
let W,H,playing=false,cam=0;
function resize(){
W=canvas.width=innerWidth*devicePixelRatio;
H=canvas.height=innerHeight*devicePixelRatio;
ctx.setTransform(devicePixelRatio,0,0,devicePixelRatio,0,0);
W=innerWidth;H=innerHeight;
}
addEventListener("resize",resize);resize();
const cars=[
["Bicycle",0,"🚲"],["Skateboard",0,"🛹"],
["Motorbike",120,"🏍️"],["Car",0,"🚗"],
["Tractor",250,"🚜"],["Truck",400,"🚚"],
["JCB",550,"🚧"],["Jet Ski",700,"🌊"],
["Buggy",900,"🏎️"],["Monster Truck",1300,"🚙"]
];
const maps=[
["Green Hills",0],["Desert Valley",500],["Snow Mountains",1000]
];
let owned=[0,3],selected=3,map=0,coins=0,best=0;
let fuel=100,dist=0,x=100,y=250,vx=0,vy=0,angle=0,av=0;
let gas=false,brake=false,boost=false;
let objects=[],lastFuel=0,lastCoin=0;
const terrain=(x)=>{
let a=H*.72+Math.sin(x*.003)*65+
Math.sin(x*.009)*24+Math.sin(x*.0012)*90;
if(map===1)a+=Math.sin(x*.002)*35;
if(map===2)a-=Math.sin(x*.001)*45;
return a;
};
function wheelData(){
let type=selected;
let size=type===5||type===9?32:type===0||type===1?12:23;
return size;
}
function makeObjects(){
objects=[];
for(let i=400;i<100000;i+=400){
let xx=i+Math.sin(i)*65;
objects.push({x:xx,type:"fuel",used:false});
for(let j=0;j<3;j++)
objects.push({x:xx+100+j*55,type:"coin",used:false});
}
}
function startGame(){
fuel=100;dist=0;x=100;y=terrain(x)-55;
vx=0;vy=0;angle=0;av=0;cam=0;
makeObjects();
["menu","shop","maps","over"].forEach(id=>document.getElementById(id).classList.add("hidden"));
document.getElementById("hud").classList.remove("hidden");
document.getElementById("controls").classList.remove("hidden");
document.getElementById("vehicleName").textContent=cars[selected][0];
playing=true;
}
function goMenu(){
playing=false;
document.getElementById("over").classList.add("hidden");
document.getElementById("hud").classList.add("hidden");
document.getElementById("controls").classList.add("hidden");
document.getElementById("menu").classList.remove("hidden");
}
function gameOver(){
playing=false;
document.getElementById("controls").classList.add("hidden");
document.getElementById("result").textContent="Distance: "+Math.floor(dist)+" m";
document.getElementById("over").classList.remove("hidden");
}
function openShop(){
playing=false;
document.getElementById("menu").classList.add("hidden");
document.getElementById("shop").classList.remove("hidden");
let html="";
cars.forEach((c,i)=>{
let has=owned.includes(i);
html+=`<div class="vehicle"><span>${c[2]} ${c[0]}</span>
<span>${has?(selected===i?"SELECTED":"OWNED"):"🪙 "+c[1]}</span>
<button onclick="buyCar(${i})">${has?"SELECT":"BUY"}</button></div>`;
});
document.getElementById("vehicles").innerHTML=html;
}
function buyCar(i){
if(owned.includes(i)){selected=i;openShop();return;}
if(coins>=cars[i][1]){
coins-=cars[i][1];owned.push(i);selected=i;openShop();
}else alert("Not enough coins! Collect more coins.");
}
function openMaps(){
playing=false;
document.getElementById("menu").classList.add("hidden");
document.getElementById("maps").classList.remove("hidden");
document.getElementById("mapList").innerHTML=maps.map((m,i)=>
`<div class="vehicle"><span>🌍 ${m[0]}</span>
<button onclick="buyMap(${i})">${map===i?"SELECTED":m[1]?"BUY "+m[1]:"FREE"}</button></div>`
).join("");
}
function buyMap(i){
if(i===map)return;
if(coins>=maps[i][1]){coins-=maps[i][1];map=i;openMaps();}
else alert("Not enough coins!");
}
function closeShop(){
document.getElementById("shop").classList.add("hidden");
document.getElementById("maps").classList.add("hidden");
document.getElementById("menu").classList.remove("hidden");
}
function setHold(id,fn){
let b=document.getElementById(id);
b.addEventListener("pointerdown",e=>{e.preventDefault();fn(true)});
["pointerup","pointerleave","pointercancel"].forEach(ev=>
b.addEventListener(ev,()=>fn(false)));
}
setHold("gas",v=>gas=v);
setHold("brake",v=>brake=v);
setHold("boost",v=>boost=v);
addEventListener("keydown",e=>{
if(["ArrowRight","ArrowLeft"," ","ArrowDown"].includes(e.key))e.preventDefault();
if(e.key==="ArrowRight"||e.key==="d")gas=true;
if(e.key==="ArrowLeft"||e.key==="a")brake=true;
if(e.key===" "||e.key==="ArrowUp")boost=true;
});
addEventListener("keyup",e=>{
if(e.key==="ArrowRight"||e.key==="d")gas=false;
if(e.key==="ArrowLeft"||e.key==="a")brake=false;
if(e.key===" "||e.key==="ArrowUp")boost=false;
});
function drawVehicle(){
let size=wheelData();
ctx.save();ctx.translate(W*.35,H*.45);ctx.rotate(angle);
let colors=["#aaa","#ddd","#e33","#278fe0","#b68b25","#da7731","#e7bd20","#26a9df","#38b75b","#d33"];
let col=colors[selected];
if(selected===0||selected===1){
ctx.strokeStyle="#e8e8e8";ctx.lineWidth=4;
ctx.beginPath();ctx.arc(-30,5,size,0,7);ctx.arc(30,5,size,0,7);ctx.stroke();
ctx.beginPath();ctx.moveTo(-30,5);ctx.lineTo(0,-20);ctx.lineTo(30,5);ctx.lineTo(-5,5);ctx.lineTo(-30,5);ctx.stroke();
}else{
ctx.fillStyle=col;
ctx.fillRect(-48,-23,96,27);
ctx.fillStyle="#222";
ctx.fillRect(-30,-43,42,22);
ctx.fillStyle="#9ce8ff";
ctx.fillRect(-25,-39,31,14);
if(selected===5||selected===9){
ctx.fillStyle=col;ctx.fillRect(30,-34,38,30);
}
if(selected===6){
ctx.strokeStyle="#eac342";ctx.lineWidth=6;
ctx.beginPath();ctx.moveTo(35,-15);ctx.lineTo(65,-65);ctx.lineTo(90,-25);ctx.stroke();
}
if(selected===4) {
ctx.fillStyle="#c69a30";ctx.fillRect(-55,-12,22,13);
}
if(selected===7){
ctx.fillStyle="#3de0e8";ctx.fillRect(-55,4,110,12);
}
ctx.fillStyle="#171717";
for(let xx of [-30,30]){
ctx.beginPath();ctx.arc(xx,8,size,0,7);ctx.fill();
ctx.strokeStyle="#aaa";ctx.lineWidth=3;
ctx.beginPath();ctx.arc(xx,8,size*.55,0,7);ctx.stroke();
}
}
// Driver dummy
ctx.fillStyle="#ffca91";
ctx.beginPath();ctx.arc(-2,-48,10,0,7);ctx.fill();
ctx.fillStyle="#274fbd";ctx.fillRect(-12,-38,22,19);
ctx.strokeStyle="#f2c39c";ctx.lineWidth=5;
ctx.beginPath();ctx.moveTo(-9,-30);ctx.lineTo(-23,-18);
ctx.moveTo(8,-30);ctx.lineTo(20,-17);ctx.stroke();
ctx.restore();
}
function draw(){
ctx.clearRect(0,0,W,H);
let sky=ctx.createLinearGradient(0,0,0,H);
sky.addColorStop(0,map===2?"#8caed4":map===1?"#f2b76e":"#65c6ee");
sky.addColorStop(1,"#e0f4c6");ctx.fillStyle=sky;ctx.fillRect(0,0,W,H);
// Sun
ctx.fillStyle="#fff3a2";ctx.beginPath();ctx.arc(W*.8,H*.16,35,0,7);ctx.fill();
// distant hills
ctx.fillStyle=map===1?"#b78345":map===2?"#a5c2d7":"#72b77a";
ctx.beginPath();ctx.moveTo(0,H);
for(let sx=0;sx<=W;sx+=8)
ctx.lineTo(sx,H*.65+Math.sin((sx+cam*.2)*.004)*40);
ctx.lineTo(W,H);ctx.fill();
// road
ctx.fillStyle=map===1?"#bb8b51":map===2?"#e2edf4":"#80613e";
ctx.beginPath();ctx.moveTo(0,H);
for(let sx=0;sx<=W;sx+=5)ctx.lineTo(sx,terrain(sx+cam));
ctx.lineTo(W,H);ctx.fill();
ctx.strokeStyle="#d4c18d";ctx.lineWidth=4;
ctx.beginPath();
for(let sx=0;sx<=W;sx+=5){
let yy=terrain(sx+cam);
if(!sx)ctx.moveTo(sx,yy);else ctx.lineTo(sx,yy);
}ctx.stroke();
objects.forEach(o=>{
if(o.used)return;
let sx=o.x-cam;
if(sx<-30||sx>W+30)return;
let sy=terrain(o.x);
if(o.type==="fuel"){
ctx.fillStyle="#ed3030";ctx.fillRect(sx-10,sy-55,20,27);
ctx.fillStyle="white";ctx.fillRect(sx-4,sy-49,8,15);
ctx.fillRect(sx-7,sy-44,14,5);
}else{
ctx.fillStyle="#ffda30";ctx.beginPath();ctx.arc(sx,sy-35,10,0,7);ctx.fill();
ctx.fillStyle="#a96b00";ctx.fillText("$",sx-4,sy-31);
}
});
drawVehicle();
if(playing){
document.getElementById("coins").textContent=coins;
document.getElementById("fuel").textContent=Math.ceil(fuel);
document.getElementById("distance").textContent=Math.floor(dist);
document.getElementById("best").textContent=best;
}
}
let last=0;
function update(dt){
if(!playing)return;
dt=Math.min(dt,.035);
let power=selected===0||selected===1?330: selected===5||selected===9?540:450;
let mass=selected===5||selected===9?1.5:1;
if(gas)vx+=power*dt/mass;
if(brake)vx-=power*.75*dt/mass;
if(boost&&fuel>0){vx+=power*1.3*dt/mass;fuel-=dt*7;}
vx*=Math.pow(.985,dt*60);
vx=Math.max(-100,Math.min(vx,selected===9?550:480));
vy+=750*dt;
x+=vx*dt;
y+=vy*dt;
let ground=terrain(x)-wheelData()-8;
if(y>ground){
y=ground;vy=-vy*.12;
let slope=(terrain(x+8)-terrain(x-8))/16;
let target=Math.atan(slope);
angle+=(target-angle)*Math.min(1,dt*7);
av*=.6;
}else{
angle+=av*dt;
av+=(gas?1.8:0)-(brake?1.8:0);
angle+=vx*.0008*dt;
}
if(y>H+200||Math.abs(angle)>2.7||fuel<=0){gameOver();return;}
dist=Math.max(0,(x-100)/10);
best=Math.max(best,Math.floor(dist));
cam=Math.max(0,x-W*.35);
fuel=Math.max(0,fuel-dt*(gas?1.0:.25));
objects.forEach(o=>{
if(!o.used&&Math.abs(o.x-x)<35&&Math.abs(y-terrain(x))<90){
o.used=true;
if(o.type==="fuel")fuel=Math.min(100,fuel+35);
else coins++;
}
});
}
function loop(t){
let dt=(t-last)/1000;last=t;
update(dt||0);draw();requestAnimationFrame(loop);
}
requestAnimationFrame(loop);
</script>
</body>
</html>
Game Source: Hill Climb Adventure
Creator: BlazeGalaxy19
Libraries: none
Complexity: complex (343 lines, 11.5 KB)
The full source code is displayed above on this page.
Remix Instructions
To remix this game, copy the source code above and modify it. Add a ARCADELAB header at the top with "remix_of: hill-climb-adventure-blazegalaxy19" to link back to the original. Then publish at arcadelab.ai/publish.