🎮ArcadeLab

Hill Climb Adventure

by BlazeGalaxy19
343 lines11.5 KB
▶ Play

<!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.