Hill Climb Adventure
by BlazeGalaxy19484 lines12.0 KB
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Hill Climb Adventure</title>
<style>
*{box-sizing:border-box}
body{margin:0;background:#172b3d;color:white;font-family:Arial;text-align:center}
#hud{display:flex;justify-content:space-around;gap:8px;padding:12px;background:#102030;flex-wrap:wrap}
canvas{display:block;width:100%;height:62vh;background:#87ceeb}
button{border:0;border-radius:10px;padding:12px;font-weight:bold;cursor:pointer}
#controls{display:flex;justify-content:space-around;padding:12px;background:#172b3d}
.pedal{width:42%;height:65px;font-size:21px;color:white;touch-action:none}
#gas{background:#159447}#brake{background:#bd3838}
#menu{padding:10px}
#shop{display:none;padding:12px;background:#203b54}
#shop button{margin:5px}
#message{min-height:25px;color:#ffdf69;font-weight:bold;padding:5px}
</style>
</head>
<body>
<div id="hud">
<span>🪙 <b id="coins">0</b></span>
<span>⛽ <b id="fuel">100</b>%</span>
<span>📏 <b id="distance">0</b> m</span>
<span>🏆 <b id="best">0</b> m</span>
</div>
<canvas id="game"></canvas>
<div id="message">HILL CLIMB ADVENTURE</div>
<div id="menu">
<button onclick="startGame()">▶ PLAY</button>
<button onclick="showVehicles()">🚗 VEHICLES</button>
<button onclick="showMaps()">🌎 MAPS</button>
</div>
<div id="shop">
<h3 id="shopTitle">SHOP</h3>
<div id="shopItems"></div>
<button onclick="closeShop()">CLOSE</button>
</div>
<div id="controls">
<button class="pedal" id="brake">◀ BRAKE</button>
<button class="pedal" id="gas">GAS ▶</button>
</div>
<script>
const canvas=document.getElementById("game");
const ctx=canvas.getContext("2d");
function resize(){
canvas.width=canvas.clientWidth;
canvas.height=canvas.clientHeight;
}
resize();
window.addEventListener("resize",resize);
const vehicles=[
{name:"Mini Car",price:0,color:"#ffd52e",power:1},
{name:"Bike",price:500,color:"#ff4545",power:1.15},
{name:"Truck",price:1200,color:"#e58b32",power:.9},
{name:"Tractor",price:2000,color:"#38bd4c",power:.85},
{name:"JCB",price:3000,color:"#ffb52b",power:.8},
{name:"Cycle",price:4000,color:"#45c8ff",power:.75},
{name:"Skateboard",price:5000,color:"#d75aff",power:1.3},
{name:"Jet Ski",price:6500,color:"#30e5d4",power:1.2},
{name:"Monster Truck",price:8000,color:"#a7d83b",power:1.1},
{name:"Super Car",price:10000,color:"#eeeeee",power:1.5}
];
const maps=[
{name:"Green Hills",price:0,sky:"#87ceeb",ground:"#479b42"},
{name:"Desert",price:1500,sky:"#f4c477",ground:"#c78a43"},
{name:"Snow Land",price:3000,sky:"#bce8ff",ground:"#e9f5fa"},
{name:"Forest",price:5000,sky:"#667d79",ground:"#315a3c"},
{name:"Moon",price:8000,sky:"#20233f",ground:"#777b8a"}
];
let coins=+(localStorage.hcCoins||0);
let best=+(localStorage.hcBest||0);
let owned=JSON.parse(localStorage.hcOwned||"[0]");
let unlocked=JSON.parse(localStorage.hcMaps||"[0]");
let vehicle=0,map=0;
let playing=false;
let fuel=100,distance=0,speed=0;
let camera=0,carY=0,vy=0,angle=0;
let grounded=true,boost=100;
let gas=false,brake=false;
let keys={};
let lastTime=0;
let messageTime=0;
let bridgeUsed=new Set();
const $=id=>document.getElementById(id);
function save(){
localStorage.hcCoins=coins;
localStorage.hcBest=best;
localStorage.hcOwned=JSON.stringify(owned);
localStorage.hcMaps=JSON.stringify(unlocked);
updateHUD();
}
function updateHUD(){
$("coins").textContent=coins;
$("fuel").textContent=Math.floor(fuel);
$("distance").textContent=Math.floor(distance);
$("best").textContent=best;
}
function say(text){
$("message").textContent=text;
messageTime=2.5;
}
function terrain(x){
const difficulty=Math.min(x/9000,2);
return canvas.height*.70
+Math.sin(x*.0035)*35
+Math.sin(x*.008)*15
+Math.sin(x*.0015)*45
+Math.sin(x*.014)*8
-difficulty*12*Math.sin(x*.002);
}
function startGame(){
closeShop();
playing=true;
fuel=100;
distance=0;
speed=0;
camera=0;
vy=0;
angle=0;
boost=100;
bridgeUsed.clear();
carY=terrain(0)-35;
grounded=true;
lastTime=performance.now();
say("GO! DRIVE AS FAR AS YOU CAN!");
requestAnimationFrame(loop);
}
function endGame(reason){
playing=false;
if(distance>best)best=Math.floor(distance);
save();
say(reason+" — DISTANCE: "+Math.floor(distance)+" m");
$("menu").style.display="block";
}
function showVehicles(){
playing=false;
$("menu").style.display="none";
$("controls").style.display="none";
$("shop").style.display="block";
$("shopTitle").textContent="🚗 VEHICLE SHOP";
let html="";
vehicles.forEach((v,i)=>{
html+=`<button onclick="selectVehicle(${i})">
${v.name}<br>${owned.includes(i)?"SELECT":"BUY "+v.price+" 🪙"}
</button>`;
});
$("shopItems").innerHTML=html;
}
function selectVehicle(i){
if(!owned.includes(i)){
if(coins<vehicles[i].price){say("NOT ENOUGH COINS!");return}
coins-=vehicles[i].price;
owned.push(i);
}
vehicle=i;
save();
showVehicles();
say(vehicles[i].name+" SELECTED!");
}
function showMaps(){
playing=false;
$("menu").style.display="none";
$("controls").style.display="none";
$("shop").style.display="block";
$("shopTitle").textContent="🌎 MAP SHOP";
let html="";
maps.forEach((m,i)=>{
html+=`<button onclick="selectMap(${i})">
${m.name}<br>${unlocked.includes(i)?"SELECT":"BUY "+m.price+" 🪙"}
</button>`;
});
$("shopItems").innerHTML=html;
}
function selectMap(i){
if(!unlocked.includes(i)){
if(coins<maps[i].price){say("NOT ENOUGH COINS!");return}
coins-=maps[i].price;
unlocked.push(i);
}
map=i;
save();
closeShop();
say(maps[i].name+" SELECTED!");
}
function closeShop(){
$("shop").style.display="none";
$("menu").style.display="block";
$("controls").style.display="flex";
}
function drawBackground(){
ctx.fillStyle=maps[map].sky;
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle="rgba(50,90,90,.20)";
ctx.beginPath();
ctx.moveTo(0,canvas.height);
for(let x=0;x<=canvas.width+20;x+=20){
ctx.lineTo(x,canvas.height*.52+Math.sin((x+camera*.2)*.005)*35);
}
ctx.lineTo(canvas.width,canvas.height);
ctx.fill();
}
function drawGround(){
ctx.fillStyle=maps[map].ground;
ctx.beginPath();
ctx.moveTo(0,canvas.height);
for(let x=0;x<=canvas.width+10;x+=6){
ctx.lineTo(x,terrain(camera+x));
}
ctx.lineTo(canvas.width,canvas.height);
ctx.fill();
ctx.strokeStyle="#294a2c";
ctx.lineWidth=4;
ctx.stroke();
}
function drawBridge(worldX){
const sx=worldX-camera;
if(sx<-160||sx>canvas.width+160)return;
const y=terrain(worldX)-10;
ctx.fillStyle="#77543a";
ctx.fillRect(sx,y,150,17);
ctx.fillStyle="#c69a60";
for(let i=0;i<7;i++)ctx.fillRect(sx+i*22,y-6,12,6);
}
function drawFuel(worldX){
const sx=worldX-camera;
if(sx<-40||sx>canvas.width+40)return;
const y=terrain(worldX)-48;
ctx.fillStyle="white";
ctx.fillRect(sx-12,y-20,24,27);
ctx.fillStyle="#e53232";
ctx.fillRect(sx-8,y-15,16,18);
ctx.fillStyle="white";
ctx.font="bold 13px Arial";
ctx.fillText("F",sx-4,y-2);
}
function drawCoin(worldX){
const sx=worldX-camera;
if(sx<-30||sx>canvas.width+30)return;
const y=terrain(worldX)-65;
ctx.fillStyle="#ffdf25";
ctx.beginPath();
ctx.arc(sx,y,10,0,Math.PI*2);
ctx.fill();
ctx.strokeStyle="#a87900";
ctx.lineWidth=2;
ctx.stroke();
}
function drawCar(){
const v=vehicles[vehicle];
const x=canvas.width*.28;
ctx.save();
ctx.translate(x,carY);
ctx.rotate(angle);
ctx.fillStyle="#202020";
ctx.beginPath();ctx.arc(-25,18,13,0,Math.PI*2);ctx.fill();
ctx.beginPath();ctx.arc(25,18,13,0,Math.PI*2);ctx.fill();
ctx.fillStyle="#aaa";
ctx.beginPath();ctx.arc(-25,18,5,0,Math.PI*2);ctx.fill();
ctx.beginPath();ctx.arc(25,18,5,0,Math.PI*2);ctx.fill();
ctx.fillStyle=v.color;
ctx.fillRect(-43,-10,86,25);
ctx.fillStyle="#b9eaff";
ctx.fillRect(-18,-25,30,17);
ctx.fillStyle="#222";
ctx.beginPath();ctx.arc(-3,-29,8,0,Math.PI*2);ctx.fill();
ctx.restore();
}
function drawMeters(){
ctx.fillStyle="#172b3b";
ctx.fillRect(12,12,145,14);
ctx.fillStyle="#e83b3b";
ctx.fillRect(12,12,145*fuel/100,14);
ctx.strokeStyle="white";
ctx.strokeRect(12,12,145,14);
ctx.fillStyle="white";
ctx.font="12px Arial";
ctx.fillText("FUEL",12,40);
ctx.fillStyle="#172b3b";
ctx.fillRect(12,48,145,10);
ctx.fillStyle="#30d7ed";
ctx.fillRect(12,48,145*boost/100,10);
ctx.strokeStyle="white";
ctx.strokeRect(12,48,145,10);
ctx.fillStyle="white";
ctx.fillText("BOOST (SPACE)",12,73);
}
function draw(){
drawBackground();
drawGround();
// Draw upcoming bridges
for(let i=Math.floor(camera/1800);i<=Math.floor(camera/1800)+2;i++){
drawBridge(i*1800+900);
}
// Fuel cans: 2250 world units apart.
for(let i=Math.floor(camera/2250);i<=Math.floor(camera/2250)+2;i++){
if(i>=0)drawFuel((i+1)*2250);
}
// Rare coins: one small group every 1200 units.
for(let i=Math.floor(camera/1200);i<=Math.floor(camera/1200)+2;i++){
if(i>=0&&i%2===0)drawCoin((i+1)*1200);
}
drawCar();
drawMeters();
ctx.fillStyle="white";
ctx.font="bold 15px Arial";
ctx.fillText(maps[map].name,canvas.width-130,25);
}
function loop(now){
if(!playing)return;
const dt=Math.min((now-lastTime)/1000,.04);
lastTime=now;
const v=vehicles[vehicle];
const right=gas||keys.ArrowRight||keys.d;
const left=brake||keys.ArrowLeft||keys.a;
const boosting=keys[" "]&&boost>0&&fuel>0;
// Fuel drains at 2 percent every second.
fuel=Math.max(0,fuel-2*dt);
// Accelerator and brake/reverse.
if(right&&fuel>0)speed+=95*v.power*dt;
if(left)speed-=150*dt;
if(!right&&!left)speed*=Math.pow(.985,dt*60);
if(boosting)speed+=170*dt;
speed=Math.max(-45,Math.min(speed,125*v.power));
if(boosting)boost=Math.max(0,boost-30*dt);
else boost=Math.min(100,boost+7*dt);
// Forward distance and camera.
const dx=speed*dt;
distance=Math.max(0,distance+Math.max(0,dx));
camera=Math.max(0,camera+dx);
const worldX=camera+canvas.width*.28;
const groundY=terrain(worldX)-35;
// Gravity and wheel contact.
vy+=650*dt;
carY+=vy*dt;
grounded=false;
if(carY>=groundY){
carY=groundY;
vy=0;
grounded=true;
const slope=(terrain(worldX+10)-terrain(worldX-10))/20;
const target=Math.atan(slope);
angle+=(target-angle)*Math.min(1,dt*7);
}else{
if(right)angle-=1.4*dt;
if(left)angle+=1.4*dt;
}
// Bridge launches the car upward once per bridge.
const bridgeIndex=Math.floor(worldX/1800);
const bridgeStart=bridgeIndex*1800+900;
if(worldX>=bridgeStart&&worldX<=bridgeStart+150&&!bridgeUsed.has(bridgeIndex)){
vy=-390;
grounded=false;
bridgeUsed.add(bridgeIndex);
say("BRIDGE JUMP!");
}
// Collect fuel only when close to the actual can.
const fuelIndex=Math.round(worldX/2250);
const fuelX=fuelIndex*2250;
if(fuelIndex>0&&Math.abs(worldX-fuelX)<45&&
Math.abs(carY-(terrain(fuelX)-48))<100){
if(!window.collectedFuel)window.collectedFuel=new Set();
if(!window.collectedFuel.has(fuelIndex)){
window.collectedFuel.add(fuelIndex);
fuel=Math.min(100,fuel+45);
say("FUEL +45%!");
}
}
// Collect rare coins.
const coinIndex=Math.round(worldX/1200);
const coinX=coinIndex*1200;
if(coinIndex>0&&coinIndex%2===0&&Math.abs(worldX-coinX)<40&&
Math.abs(carY-(terrain(coinX)-65))<110){
if(!window.collectedCoins)window.collectedCoins=new Set();
if(!window.collectedCoins.has(coinIndex)){
window.collectedCoins.add(coinIndex);
coins+=25;
save();
say("+25 COINS!");
}
}
// Crash if upside down while on the ground.
if(grounded&&Math.abs(angle)>2.5){
endGame("CRASH!");
return;
}
if(fuel<=0&&speed<5){
endGame("OUT OF FUEL!");
return;
}
if(distance>best)best=Math.floor(distance);
if(messageTime>0)messageTime-=dt;
else $("message").textContent="Keep driving! Beat your best distance!";
updateHUD();
draw();
requestAnimationFrame(loop);
}
// Keyboard controls
window.addEventListener("keydown",e=>{
if(["ArrowRight","ArrowLeft"," "].includes(e.key))e.preventDefault();
keys[e.key===" "?" ":e.key]=true;
});
window.addEventListener("keyup",e=>{
keys[e.key===" "?" ":e.key]=false;
});
// Touch controls
function hold(id,action){
const el=$(id);
el.addEventListener("pointerdown",e=>{
e.preventDefault();action(true);
});
["pointerup","pointerleave","pointercancel"].forEach(event=>{
el.addEventListener(event,e=>{
e.preventDefault();action(false);
});
});
}
hold("gas",v=>gas=v);
hold("brake",v=>brake=v);
// Initial screen
draw();
updateHUD();
</script>
</body>
</html>
Game Source: Hill Climb Adventure
Creator: BlazeGalaxy19
Libraries: none
Complexity: complex (484 lines, 12.0 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-mwaumpze" to link back to the original. Then publish at arcadelab.ai/publish.