Skip to content

Instantly share code, notes, and snippets.

@EncodeTheCode
Created May 25, 2026 01:15
Show Gist options
  • Select an option

  • Save EncodeTheCode/9967e70a66b525a93e5bd0f208391d0b to your computer and use it in GitHub Desktop.

Select an option

Save EncodeTheCode/9967e70a66b525a93e5bd0f208391d0b to your computer and use it in GitHub Desktop.
<!doctype html>
<html lang="en">
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
<title>2D Zone Platformer</title>
<style>
html,body{margin:0;height:100%;overflow:hidden;background:#87cfff;touch-action:none}
canvas{display:block;width:100vw;height:100vh;image-rendering:pixelated}
#hud{position:fixed;left:8px;top:8px;color:#fff;font:14px monospace;text-shadow:0 1px 2px #0008;user-select:none;white-space:pre}
#gameover{position:fixed;inset:0;display:none;align-items:center;justify-content:center;color:#fff;font:700 54px monospace;text-shadow:0 4px 0 #000c;background:rgba(0,0,0,.18);pointer-events:none}
</style>
<canvas id=c></canvas><div id=hud></div><div id=gameover>GAME OVER</div>
<script>
(()=>{
const c=document.getElementById('c'),x=c.getContext('2d'),hud=document.getElementById('hud'),go=document.getElementById('gameover');
let W,H,S=24,ZW=18,ZH=12,ZWpx=ZW*S,zone=0,px=48,py=36,vx=0,vy=0,on=0,keys={},last=0,cam=0,state='play',stateT=0;
const spawn={x:48,y:36};
const T={0:0,1:1,2:2,3:3,4:4};
const Z=[
['000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','111111111111111111'],
['000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','111111111111111111','111111111111111111'],
['000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','000000000000000000','111111111111111111','111111111111111111','111111111111111111']
].map(z=>z.map(r=>r.split('').map(n=>+n)));
function ground(z,row){Z[z][row]='111111111111111111'.split('').map(n=>+n)}
function fillRow(z,row,v){Z[z][row]=Array.from({length:ZW},()=>v)}
fillRow(0,11,1);fillRow(1,11,1);fillRow(1,10,1);fillRow(2,11,1);fillRow(2,10,1);fillRow(2,9,1);
Z[0][10]='000000000033300000'.split('').map(n=>+n);Z[0][9]='000000000033300000'.split('').map(n=>+n);Z[0][7]='000040000000000000'.split('').map(n=>+n);Z[0][6]='000000022200000000'.split('').map(n=>+n);Z[0][5]='000000022200000000'.split('').map(n=>+n);Z[0][4]='000003000000000000'.split('').map(n=>+n);Z[0][3]='000003000000000000'.split('').map(n=>+n);
Z[1][9]='000000000033300000'.split('').map(n=>+n);Z[1][8]='003330000000000000'.split('').map(n=>+n);Z[1][7]='000000000000400000'.split('').map(n=>+n);Z[1][6]='000022200000000000'.split('').map(n=>+n);Z[1][5]='000022200003330000'.split('').map(n=>+n);Z[1][3]='000003000000000000'.split('').map(n=>+n);
Z[2][8]='000000000033300000'.split('').map(n=>+n);Z[2][7]='000400000000000000'.split('').map(n=>+n);Z[2][6]='000000022200000000'.split('').map(n=>+n);Z[2][5]='000003022200003330'.split('').map(n=>+n);
function resize(){W=innerWidth;H=innerHeight;c.width=W;c.height=H}addEventListener('resize',resize);resize();
addEventListener('keydown',e=>keys[e.code]=1);addEventListener('keyup',e=>keys[e.code]=0);
function tile(z,c,r){return Z[z]?.[r]?.[c]??0}
function solid(t){return t===1||t===3}
function at(xp,yp,z=zone){return tile(z,(xp/S)|0,(yp/S)|0)}
function hitSolid(xp,yp,w,h,z=zone){return solid(at(xp,yp,z))||solid(at(xp+w-1,yp,z))||solid(at(xp,yp+h-1,z))||solid(at(xp+w-1,yp+h-1,z))}
let ents=[];
function load(z,keepPos){zone=z;if(!keepPos){px=spawn.x;py=spawn.y}vx=vy=0;on=0;ents=[];for(let r=0;r<ZH;r++)for(let c=0;c<ZW;c++)if(tile(zone,c,r)===4)ents.push({x:c*S,y:r*S,vx:0,vy:0,d:1,dead:0,a:1})}
function reset(){zone=0;load(0,false);cam=0}
function beginGameOver(){if(state!=='play')return;state='over';stateT=.8;go.style.display='flex'}
function enemyHit(e){return px<e.x+16&&px+14>e.x&&py<e.y+20&&py+20>e.y}
function stomp(e){return vy>0&&py+19<=e.y+8&&px+7>e.x-2&&px+7<e.x+18}
function update(dt){if(state==='over'){stateT-=dt;if(stateT<=0){go.style.display='none';state='play';reset()}return}
const ACC=900,FR=780,MAX=180,G=1200,J=420;
let ax=(keys['KeyD']||keys['ArrowRight']?1:0)-(keys['KeyA']||keys['ArrowLeft']?1:0);
vx+=ax*ACC*dt;if(!ax)vx+=(vx>0?-1:1)*Math.min(Math.abs(vx),FR*dt);vx=Math.max(-MAX,Math.min(MAX,vx));vy+=G*dt;
if(on&&(keys['Space']||keys['KeyW']||keys['ArrowUp'])){vy=-J;on=0}
let nx=px+vx*dt;if(!hitSolid(nx,py,14,20))px=nx;else{while(!hitSolid(px+(vx>0?1:-1),py,14,20))px+=(vx>0?1:-1);vx=0}
let ny=py+vy*dt;on=0;if(!hitSolid(px,ny,14,20))py=ny;else{while(!hitSolid(px,py+(vy>0?1:-1),14,20))py+=(vy>0?1:-1);if(vy>0)on=1;vy=0}
for(const e of ents){if(e.dead){e.x+=e.vx*dt;e.y+=e.vy*dt;e.vy+=G*dt*.8;e.a=Math.max(0,e.a-dt*1.7);continue}
let belowL=solid(at(e.x+2,e.y+24)),belowR=solid(at(e.x+13,e.y+24));if(!belowL||!belowR)e.d*=-1;
let ex=e.x+e.d*55*dt;if(hitSolid(ex,e.y,16,20)||hitSolid(ex+e.d*2,e.y+2,16,18))e.d*=-1;else e.x=ex;
if(!solid(at(e.x+2,e.y+24))&&!solid(at(e.x+13,e.y+24))){e.vy=Math.min(240,e.vy+G*dt*.5);e.y+=e.vy*dt}else e.vy=0;
if(enemyHit(e)){
if(stomp(e)){e.dead=1;e.vx=(Math.random()-.5)*80;e.vy=-260;e.a=1;vy=-220;on=0;py=e.y-21}
else{beginGameOver();return}
}}
let edge=(zone+1)*ZWpx-30;if(px>edge&&zone<Z.length-1)load(zone+1,false);
if(py>H+220)beginGameOver();cam=Math.max(0,Math.min((zone+1)*ZWpx-W,zone*ZWpx+px-W/2))}
function drawTile(t,x0,y0){if(!t)return;if(t===1){x.fillStyle='#6b4f2a';x.fillRect(x0,y0,S,S);x.fillStyle='#3c8d2f';x.fillRect(x0,y0,S,5)}else if(t===2){x.fillStyle='#1e86d9';x.fillRect(x0,y0,S,S)}else if(t===3){x.fillStyle='#777';x.fillRect(x0,y0,S,S)}else if(t===4){x.fillStyle='#e53935';x.fillRect(x0+4,y0+4,S-8,S-8)}}
function render(){x.clearRect(0,0,W,H);x.fillStyle='#87cfff';x.fillRect(0,0,W,H);x.save();x.translate(-cam,0);for(let z=zone;z<=Math.min(Z.length-1,zone+1);z++){let ox=z*ZWpx;for(let r=0;r<ZH;r++)for(let c=0;c<ZW;c++)drawTile(tile(z,c,r),ox+c*S,r*S)}x.fillStyle='#ffd34d';x.fillRect(px,py,14,20);x.fillStyle='#222';x.fillRect(px+3,py+5,3,3);x.fillRect(px+8,py+5,3,3);for(const e of ents){if(!e.dead){x.fillStyle='#e53935';x.fillRect(e.x,e.y,16,20)}else{x.save();x.globalAlpha=e.a;x.fillStyle='#e53935';x.fillRect(e.x,e.y,16,20);x.restore()}}x.restore();hud.textContent=`Zone ${zone+1}/${Z.length}\nWASD/Arrows move | Space/W/Up jump | 30 FPS cap`}
function loop(t){let dt=Math.min(.033,(t-last)/1000||0);last=t;update(dt);render();requestAnimationFrame(loop)}
reset();requestAnimationFrame(loop)
})();
</script>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment