Jungle Battle Ground
by WildDragon12390 lines14.7 KB
<!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.