🎮ArcadeLab

Jungle Battle Ground

by WildDragon12
390 lines14.7 KB
▶ Play

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<title>Jungle Battle Ground</title>
<style>
*{box-sizing:border-box}
body{margin:0;background:#101922;color:white;font-family:Arial,sans-serif;overflow:hidden;touch-action:none}
#hud{height:48px;background:#593c27;display:flex;align-items:center;justify-content:space-around;gap:5px;border-bottom:3px solid #b78a49;font-weight:bold;font-size:14px}
.hudbox{background:#80552f;border:2px solid #d5a65b;border-radius:20px;padding:6px 10px}
#game{display:block;width:100%;height:calc(100dvh - 140px);background:#19b5b6;touch-action:none}
#controls{height:92px;background:#18242c;display:flex;align-items:center;justify-content:space-around;gap:8px;padding:5px}
.ctrl{width:64px;height:64px;border:2px solid #ffffffaa;border-radius:50%;background:#ffffff30;color:white;font-size:27px;font-weight:bold;touch-action:none;user-select:none}
.ctrl:active{background:#ffffff70}
#jump{background:#64b94b55}
#attack{background:#ff9d3755}
#menu{position:fixed;inset:0;background:#071b20d9;z-index:5;display:flex;align-items:center;justify-content:center;padding:14px}
.panel{width:min(430px,95vw);max-height:95dvh;overflow:auto;background:#533a28;border:4px solid #d4a35a;border-radius:20px;text-align:center;padding:20px;box-shadow:0 8px 0 #241b14}
h1{color:#ffe18a;font-size:30px;margin:5px}
p{line-height:1.45;font-size:14px}
button,select{font-size:16px;font-weight:bold;border:2px solid #f6d58b;border-radius:10px;background:#79cb4b;color:#172412;padding:12px;margin:5px}
#message{position:fixed;top:60px;left:50%;transform:translateX(-50%);background:#142c26dd;padding:8px 15px;border-radius:12px;display:none;z-index:2}
.small{font-size:11px;color:#e9d5b5}
</style>
</head>
<body>
<div id="hud">
 <div class="hudbox">LV. <span id="lv">1</span></div>
 <div class="hudbox">⭐ <span id="score">0</span></div>
 <div class="hudbox">❤️ <span id="health">3</span></div>
 <div class="hudbox">🪙 <span id="coins">0</span></div>
 <button style="padding:5px 12px;margin:0;border-radius:50%" onclick="pauseGame()">Ⅱ</button>
</div>
<canvas id="game" width="960" height="540"></canvas>
<div id="message"></div>
<div id="controls">
 <button class="ctrl" data-key="left">◀</button>
 <button class="ctrl" data-key="right">▶</button>
 <button class="ctrl" id="attack" data-key="attack">✦</button>
 <button class="ctrl" id="jump" data-key="jump">⬆</button>
</div>

<div id="menu">
<div class="panel">
<h1 id="title">🌴 JUNGLE BATTLE</h1>
<p id="desc">Run, jump, collect coins and defeat jungle monsters across 100 levels!</p>
<p>Choose difficulty</p>
<select id="difficulty">
<option value="0.75">Easy</option>
<option value="1" selected>Normal</option>
<option value="1.4">Hard</option>
<option value="1.8">Very Hard</option>
<option value="2.2">Crazy</option>
</select>
<br>
<button onclick="startGame()">▶ START GAME</button>
<p class="small">Mobile: use the buttons below the game. Computer: A/D or arrows, W/Up to jump, Space to attack.</p>
</div>
</div>

<script>
const canvas=document.getElementById('game');
const ctx=canvas.getContext('2d');
const W=960,H=540;
let keys={},player,platforms=[],coins=[],enemies=[],decor=[];
let level=1,score=0,coinCount=0,health=3;
let camera=0,worldWidth=3000,gravity=1400;
let running=false,paused=false,gameOver=false;
let difficulty=1,last=0,attackTime=0,invincible=0;
let jumpPressed=false,attackPressed=false,levelBanner=0;

function platform(x,y,w,h,type='grass'){
 platforms.push({x,y,w,h,type});
}
function buildLevel(){
 platforms=[];coins=[];enemies=[];decor=[];
 worldWidth=2600+level*80;
 platform(0,470,worldWidth,130);
 // Raised platforms with gaps between sections.
 for(let x=300;x<worldWidth-180;x+=330){
  const y=360-Math.random()*100;
  platform(x,y,130,22);
  platform(x+185,y-65,100,22);
  platform(x+235,y+65,70,22);
 }
 // Coins arranged along the route.
 for(let x=180;x<worldWidth-100;x+=115){
  let y=rand(260,390);
  coins.push({x,y,r:11,taken:false});
 }
 // Trees, plants and decorative jungle details.
 for(let x=0;x<worldWidth;x+=rand(100,180)){
  decor.push({x,y:rand(100,310),type:Math.random()});
 }
 // Enemies start on the main floor.
 for(let i=0;i<Math.min(5+level,24);i++){
  enemies.push({
   x:rand(450,worldWidth-100),y:420,w:34,h:42,
   speed:rand(45,80)*difficulty*(1+level*.015),
   dir:Math.random()<.5?-1:1,alive:true,stun:0
  });
 }
 player.x=45;player.y=350;player.vx=0;player.vy=0;
 player.grounded=false;
 camera=0;
 levelBanner=2.3;
}
function rand(a,b){return a+Math.random()*(b-a)}
function startGame(){
 difficulty=+document.getElementById('difficulty').value;
 level=1;score=0;coinCount=0;health=3;
 player={x:45,y:350,w:32,h:52,vx:0,vy:0,grounded:false,face:1};
 buildLevel();
 running=true;paused=false;gameOver=false;
 document.getElementById('menu').style.display='none';
 updateHUD();last=performance.now();
 requestAnimationFrame(loop);
}
function showMessage(text){
 const el=document.getElementById('message');
 el.textContent=text;el.style.display='block';
 clearTimeout(showMessage.timer);
 showMessage.timer=setTimeout(()=>el.style.display='none',1300);
}
function pauseGame(){
 if(!running||gameOver)return;
 paused=!paused;
 if(paused){
  document.getElementById('title').textContent='⏸ PAUSED';
  document.getElementById('desc').textContent='Game paused. Press Resume to continue.';
  const btn=document.querySelector('#menu button');
  btn.textContent='▶ RESUME';
  btn.onclick=()=>{
   document.getElementById('menu').style.display='none';
   paused=false;last=performance.now();requestAnimationFrame(loop);
  };
  document.getElementById('menu').style.display='flex';
 }else{
  document.getElementById('menu').style.display='none';
  last=performance.now();requestAnimationFrame(loop);
 }
}
function restartMenu(win){
 running=false;gameOver=true;
 document.getElementById('menu').style.display='flex';
 document.getElementById('title').textContent=win?'🏆 YOU WIN!':'💥 GAME OVER';
 document.getElementById('desc').textContent=win?
  'You completed all 100 levels! Final score: '+score:
  'Level '+level+' reached. Score: '+score;
 const btn=document.querySelector('#menu button');
 btn.textContent='↻ PLAY AGAIN';btn.onclick=startGame;
}
function updateHUD(){
 document.getElementById('lv').textContent=level;
 document.getElementById('score').textContent=score;
 document.getElementById('health').textContent=health;
 document.getElementById('coins').textContent=coinCount;
}
function pressJump(){
 if(!running||paused||gameOver)return;
 if(player.grounded){player.vy=-570;player.grounded=false}
}
function doAttack(){
 if(!running||paused||gameOver)return;
 attackTime=.25;
 for(const e of enemies){
  if(e.alive&&Math.abs(e.x-player.x)<85&&Math.abs(e.y-player.y)<70&&
    Math.sign(e.x-player.x)===player.face){
   e.stun=1.6;e.x+=player.face*20;
   score+=20;
   showMessage('Monster stunned! +20');
  }
 }
}
document.querySelectorAll('[data-key]').forEach(btn=>{
 const k=btn.dataset.key;
 btn.addEventListener('pointerdown',e=>{
  e.preventDefault();keys[k]=true;
  if(k==='jump')pressJump();
  if(k==='attack')doAttack();
 });
 for(const ev of ['pointerup','pointercancel','pointerleave'])
  btn.addEventListener(ev,()=>keys[k]=false);
});
window.addEventListener('keydown',e=>{
 const k=e.key.toLowerCase();
 if(['arrowleft','arrowright','arrowup',' ','arrowdown'].includes(k))e.preventDefault();
 if(k==='a'||k==='arrowleft')keys.left=true;
 if(k==='d'||k==='arrowright')keys.right=true;
 if((k==='w'||k==='arrowup')&&!e.repeat)pressJump();
 if(k===' '&&!e.repeat)doAttack();
 if(k==='p'&&!e.repeat)pauseGame();
});
window.addEventListener('keyup',e=>{
 const k=e.key.toLowerCase();
 if(k==='a'||k==='arrowleft')keys.left=false;
 if(k==='d'||k==='arrowright')keys.right=false;
});

function movePlayer(dt){
 const speed=250;
 player.vx=0;
 if(keys.left){player.vx=-speed;player.face=-1}
 if(keys.right){player.vx=speed;player.face=1}
 player.x+=player.vx*dt;
 player.x=Math.max(0,Math.min(worldWidth-player.w,player.x));
 const oldBottom=player.y+player.h;
 player.vy+=gravity*dt;
 player.y+=player.vy*dt;
 player.grounded=false;
 for(const p of platforms){
  if(player.x+player.w>p.x&&player.x<p.x+p.w&&
     oldBottom<=p.y+12&&player.y+player.h>=p.y&&player.vy>=0){
   player.y=p.y-player.h;player.vy=0;player.grounded=true;
  }
 }
 if(player.y>H+350)takeDamage(true);
}
function takeDamage(fall=false){
 if(invincible>0||!running)return;
 health--;invincible=1.5;
 if(health<=0){updateHUD();restartMenu(false);return}
 if(fall){player.x=Math.max(0,camera+70);player.y=260;player.vy=0}
 showMessage(fall?'You fell!':'Ouch! Health lost');
 updateHUD();
}
function update(dt){
 if(!running||paused)return;
 attackTime=Math.max(0,attackTime-dt);
 invincible=Math.max(0,invincible-dt);
 levelBanner=Math.max(0,levelBanner-dt);
 movePlayer(dt);
 for(const e of enemies){
  if(!e.alive)continue;
  if(e.stun>0){e.stun-=dt;continue}
  e.x+=e.dir*e.speed*dt;
  if(e.x<camera-80||e.x>Math.min(worldWidth,e.x+800))e.dir*=-1;
  if(Math.abs(e.x-player.x)<29&&Math.abs(e.y-player.y)<43){
   takeDamage();e.dir=player.x<e.x?1:-1;
  }
 }
 for(const c of coins){
  if(!c.taken&&Math.abs(c.x-(player.x+16))<25&&Math.abs(c.y-(player.y+25))<36){
   c.taken=true;coinCount++;score+=10;
  }
 }
 camera=Math.max(0,Math.min(worldWidth-W,player.x-W*.35));
 if(player.x>worldWidth-120){
  if(level>=100){restartMenu(true);return}
  level++;health=Math.min(3,health+1);buildLevel();
  showMessage('LEVEL '+level+'!');
 }
 updateHUD();
}
function drawBackground(){
 const g=ctx.createLinearGradient(0,0,0,H);
 g.addColorStop(0,'#087d89');g.addColorStop(.6,'#36c1ac');g.addColorStop(1,'#d2d879');
 ctx.fillStyle=g;ctx.fillRect(0,0,W,H);
 // distant jungle silhouettes
 for(let i=0;i<11;i++){
  let x=((i*143-camera*.12)%1100+1100)%1100-60;
  ctx.fillStyle=i%2?'#075c4e':'#087365';
  ctx.beginPath();ctx.moveTo(x,0);ctx.lineTo(x+45,80);ctx.lineTo(x+15,110);
  ctx.lineTo(x+85,150);ctx.lineTo(x+40,145);ctx.lineTo(x+80,220);
  ctx.lineTo(x+10,170);ctx.lineTo(x-20,90);ctx.closePath();ctx.fill();
  ctx.fillStyle='#174e38';ctx.fillRect(x+25,120,18,240);
 }
 // water / forest haze
 ctx.fillStyle='#179eae';ctx.fillRect(0,360,W,180);
 ctx.fillStyle='#4cc2a0';
 for(let i=0;i<9;i++){
  ctx.beginPath();ctx.ellipse(i*130-camera*.05,390,100,20,0,0,Math.PI*2);ctx.fill();
 }
}
function drawPlatform(p){
 let x=p.x-camera;
 if(x+p.w<0||x>W)return;
 ctx.fillStyle='#56331f';ctx.fillRect(x,p.y,p.w,p.h);
 ctx.fillStyle='#78462a';ctx.fillRect(x,p.y+10,p.w,p.h-10);
 // earth strata
 for(let yy=p.y+20;yy<p.y+p.h;yy+=25){
  ctx.strokeStyle='#4b2d1c';ctx.lineWidth=2;
  ctx.beginPath();ctx.moveTo(x,yy);ctx.quadraticCurveTo(x+p.w*.5,yy+8,x+p.w,yy);ctx.stroke();
 }
 ctx.fillStyle='#317a28';ctx.fillRect(x,p.y-2,p.w,13);
 ctx.fillStyle='#57b83b';ctx.fillRect(x,p.y-7,p.w,10);
 for(let xx=x;xx<x+p.w;xx+=13){
  ctx.fillStyle=xx%2?'#368b2d':'#6ac64b';
  ctx.beginPath();ctx.moveTo(xx,p.y);ctx.lineTo(xx+7,p.y+12);ctx.lineTo(xx+13,p.y);ctx.fill();
 }
}
function drawCoin(c){
 const x=c.x-camera,y=c.y;
 ctx.fillStyle='#c27b12';ctx.beginPath();ctx.ellipse(x,y,10,15,0,0,Math.PI*2);ctx.fill();
 ctx.fillStyle='#ffdf44';ctx.beginPath();ctx.ellipse(x-2,y,6,12,0,0,Math.PI*2);ctx.fill();
 ctx.strokeStyle='#b47a13';ctx.lineWidth=2;ctx.stroke();
 ctx.fillStyle='#b47a13';ctx.font='bold 13px Arial';ctx.textAlign='center';ctx.fillText('$',x,y+5);
}
function drawEnemy(e){
 const x=e.x-camera,y=e.y;
 if(x<-60||x>W+60)return;
 ctx.fillStyle=e.stun>0?'#a7f5ff':'#c43b65';
 ctx.fillRect(x,y+8,34,30);
 ctx.fillStyle='#f0b88b';ctx.fillRect(x+3,y-12,28,25);
 // expressive face
 ctx.fillStyle='#fff';ctx.fillRect(x+5,y-6,9,8);ctx.fillRect(x+20,y-6,9,8);
 ctx.fillStyle='#1c2330';ctx.fillRect(x+9,y-4,4,5);ctx.fillRect(x+23,y-4,4,5);
 ctx.fillStyle='#8a243e';ctx.fillRect(x+10,y+7,15,4);
 ctx.fillStyle='#583621';ctx.fillRect(x+2,y-15,30,7);
 ctx.fillStyle='#37281f';ctx.fillRect(x+3,y+36,10,8);ctx.fillRect(x+22,y+36,10,8);
 if(e.stun>0){ctx.fillStyle='#fff';ctx.font='20px Arial';ctx.fillText('✦',x+8,y-22)}
}
function drawPlayer(){
 const x=player.x-camera,y=player.y;
 if(invincible>0&&Math.floor(invincible*12)%2===0)return;
 ctx.save();
 if(player.face<0){ctx.translate(x+player.w,y);ctx.scale(-1,1);ctx.translate(-x,-y)}
 // shadow
 ctx.fillStyle='#0b3d3b66';ctx.beginPath();ctx.ellipse(x+16,y+53,19,5,0,0,7);ctx.fill();
 // legs and boots
 ctx.fillStyle='#694128';ctx.fillRect(x+4,y+35,10,14);ctx.fillRect(x+19,y+35,10,14);
 ctx.fillStyle='#392d28';ctx.fillRect(x+2,y+46,13,6);ctx.fillRect(x+18,y+46,13,6);
 // body and backpack
 ctx.fillStyle='#714322';ctx.fillRect(x+5,y+14,23,25);
 ctx.fillStyle='#a86a31';ctx.fillRect(x+8,y+17,17,18);
 ctx.fillStyle='#d2a253';ctx.fillRect(x+11,y+23,10,4);
 // arms
 ctx.fillStyle='#dba36d';ctx.fillRect(x-1,y+19,7,18);ctx.fillRect(x+27,y+19,7,18);
 ctx.fillStyle='#f0c89a';ctx.fillRect(x-2,y+33,9,7);ctx.fillRect(x+27,y+33,9,7);
 // hair and head
 ctx.fillStyle='#e8bd91';ctx.fillRect(x+5,y-8,24,25);
 ctx.fillStyle='#4b2d1c';ctx.fillRect(x+3,y-11,27,10);
 ctx.fillRect(x+2,y-7,7,9);ctx.fillRect(x+23,y-6,8,7);
 // eyebrows, eyes, pupils
 ctx.fillStyle='#34231b';ctx.fillRect(x+8,y-2,7,3);ctx.fillRect(x+20,y-2,6,3);
 ctx.fillStyle='#fff';ctx.fillRect(x+8,y+1,7,7);ctx.fillRect(x+20,y+1,6,7);
 ctx.fillStyle='#201b17';ctx.fillRect(x+11,y+3,3,5);ctx.fillRect(x+22,y+3,3,5);
 // nose and mouth
 ctx.fillStyle='#bd815c';ctx.fillRect(x+16,y+6,4,4);
 ctx.fillStyle='#712c29';ctx.fillRect(x+12,y+11,11,3);
 ctx.fillStyle='#fff1dc';ctx.fillRect(x+14,y+11,7,1);
 ctx.restore();
 if(attackTime>0){
  ctx.strokeStyle='#fff5a2';ctx.lineWidth=5;
  ctx.beginPath();ctx.arc(x+16+(player.face*22),y+20,20,-1.2,1.2);ctx.stroke();
 }
}
function draw(){
 drawBackground();
 ctx.save();
 for(const p of platforms)drawPlatform(p);
 // small flower pots / jungle plants
 for(const d of decor){
  let x=d.x-camera;
  if(x<-40||x>W+40)continue;
  ctx.fillStyle='#194e37';ctx.fillRect(x,360,8,100);
  ctx.fillStyle=d.type>.5?'#e84c8c':'#64ba47';
  ctx.beginPath();ctx.arc(x+5,355,12,0,Math.PI*2);ctx.fill();
  ctx.fillStyle='#ffdb63';ctx.beginPath();ctx.arc(x+5,355,4,0,Math.PI*2);ctx.fill();
 }
 for(const c of coins)if(!c.taken)drawCoin(c);
 for(const e of enemies)if(e.alive)drawEnemy(e);
 drawPlayer();
 // goal sign
 const goal=worldWidth-95-camera;
 if(goal>-40&&goal<W+40){
  ctx.fillStyle='#744727';ctx.fillRect(goal,320,9,150);
  ctx.fillStyle='#ffe276';ctx.fillRect(goal+9,324,70,40);
  ctx.fillStyle='#3d3820';ctx.font='bold 15px Arial';ctx.fillText('GOAL',goal+17,350);
 }
 ctx.restore();
 if(levelBanner>0){
  ctx.fillStyle='#142a25bb';ctx.fillRect(W/2-110,65,220,48);
  ctx.fillStyle='#ffdf7a';ctx.font='bold 25px Arial';ctx.textAlign='center';
  ctx.fillText('LEVEL '+level,W/2,97);
 }
}
function loop(t){
 if(!running||paused)return;
 const dt=Math.min(.033,Math.max(0,(t-last)/1000));last=t;
 update(dt);draw();
 if(running&&!paused)requestAnimationFrame(loop);
}
draw();
</script>
</body>
</html>

Game Source: Jungle Battle Ground

Creator: WildDragon12

Libraries: none

Complexity: complex (390 lines, 14.7 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: block-survival-royale-wilddragon12" to link back to the original. Then publish at arcadelab.ai/publish.