🎮ArcadeLab

العدّاء اللانهائي

by FrostLegend54
175 lines7.0 KB
▶ Play
<!doctype html>
<html lang="ar" dir="rtl">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>العدّاء اللانهائي</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Tajawal:wght@400;700;800&display=swap" rel="stylesheet">
<style>
:root{
  --bg:#dde7ea; --panel:#f3f8f9; --fg:#0f2a35; --muted:#5d7882; --accent:#e8336d; --ground:#9db2ba; --line:#c3d3d8;
  color-scheme:light;
  box-sizing:border-box;
  padding-top:env(safe-area-inset-top,0px);
  padding-bottom:env(safe-area-inset-bottom,0px);
}
@media (prefers-color-scheme: dark){
  :root:not([data-theme="light"]){
    --bg:#0a1418; --panel:#0f1d23; --fg:#e6f1f4; --muted:#8aa5ae; --accent:#ff5c8a; --ground:#2c4650; --line:#1e3640;
    color-scheme:dark;
  }
}
:root[data-theme="dark"]{
  --bg:#0a1418; --panel:#0f1d23; --fg:#e6f1f4; --muted:#8aa5ae; --accent:#ff5c8a; --ground:#2c4650; --line:#1e3640;
  color-scheme:dark;
}
html{scroll-padding-top:env(safe-area-inset-top,0px)}
html,body{height:100%;margin:0;touch-action:none;-webkit-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent}
body{
  background:var(--bg);color:var(--fg);
  font-family:"Tajawal","Segoe UI",Tahoma,"Noto Sans Arabic",system-ui,sans-serif;
  display:flex;align-items:center;justify-content:center;
}
#game{width:min(100%,760px);padding:16px;box-sizing:border-box}
.hud{display:flex;justify-content:space-between;align-items:baseline;margin:0 4px 10px;color:var(--muted);font-size:15px}
.hud b{color:var(--fg);font-size:26px;font-weight:800;font-variant-numeric:tabular-nums;margin-inline-start:6px}
.stage{position:relative;width:100%;aspect-ratio:2/1}
canvas{display:block;width:100%;height:100%;border-radius:18px;border:1px solid var(--line);background:var(--panel)}
.ov{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;text-align:center;pointer-events:none;padding:16px}
.ov.hide{display:none}
#ovT{font-size:clamp(26px,6vw,40px);font-weight:800}
#ovS{font-size:clamp(14px,3.4vw,17px);color:var(--muted);max-width:30ch;line-height:1.6}
.hint{margin:12px 4px 0;color:var(--muted);font-size:14px;text-align:center}
</style>
</head>
<body>
<main id="game">
  <div class="hud">
    <div>النتيجة<b id="score">00000</b></div>
    <div>الأفضل<b id="best">00000</b></div>
  </div>
  <div class="stage">
    <canvas id="c"></canvas>
    <div id="ov" class="ov"><div id="ovT"></div><div id="ovS"></div></div>
  </div>
  <p class="hint">اضغط على الشاشة أو Space للقفز. كل ما تطوّل الضغطة، تعلى القفزة.</p>
</main>
<script>
(()=>{
const cv=document.getElementById('c'),ctx=cv.getContext('2d');
const W=720,H=360,GY=292,DPR=Math.min(window.devicePixelRatio||1,2);
cv.width=W*DPR;cv.height=H*DPR;ctx.scale(DPR,DPR);
const $=id=>document.getElementById(id);
const scoreEl=$('score'),bestEl=$('best'),ov=$('ov'),ovT=$('ovT'),ovS=$('ovS');
const pad=n=>String(n).padStart(5,'0');
const C={};
function readColors(){
  const s=getComputedStyle(document.documentElement);
  ['panel','fg','muted','accent','ground'].forEach(k=>C[k]=s.getPropertyValue('--'+k).trim());
}
readColors();

let st='ready',t=0,dist=0,speed=300,best=0,overAt=0,since=0,nextGap=500,frame=0,buf=0,shown=-1;
try{best=+localStorage.getItem('runner-best')||0}catch(e){}
bestEl.textContent=pad(best);

const P={x:80,y:GY,w:34,h:42,vy:0,hold:false};
let obs=[];
const clouds=Array.from({length:4},()=>({x:Math.random()*W,y:28+Math.random()*90,s:.15+Math.random()*.25}));

function showOv(a,b){ovT.textContent=a;ovS.textContent=b;ov.classList.remove('hide')}
showOv('العدّاء اللانهائي','اضغط للبدء، واقفز على العوائق.');

function reset(){obs=[];dist=0;speed=300;P.y=GY;P.vy=0;since=0;nextGap=500;buf=0;shown=-1}
function jump(){if(P.y>=GY){P.vy=-640;P.hold=true}else buf=.12}
function press(){
  if(st==='over'&&t-overAt<.45)return;
  if(st!=='play'){st='play';reset();ov.classList.add('hide')}
  jump();
}
function release(){P.hold=false}
function die(){
  st='over';overAt=t;
  const sc=Math.floor(dist/10);
  if(sc>best){best=sc;bestEl.textContent=pad(best);try{localStorage.setItem('runner-best',best)}catch(e){}}
  showOv('خسرت!','النتيجة '+sc+'. اضغط للعب من جديد.');
}
function spawn(){
  const k=Math.random();
  const o=k<.45?{w:22,h:38}:k<.8?{w:26,h:58}:{w:56,h:40};
  o.x=W+20;obs.push(o);
  since=0;nextGap=speed*.85+140+Math.random()*220;
}

function update(dt){
  clouds.forEach(c=>{c.x-=c.s*(st==='play'?speed:120)*dt;if(c.x<-80)c.x=W+40+Math.random()*60});
  if(st!=='play')return;
  speed=Math.min(720,300+dist*.03);
  const d=speed*dt;dist+=d;since+=d;
  if(since>=nextGap)spawn();
  obs.forEach(o=>o.x-=d);
  obs=obs.filter(o=>o.x+o.w>-10);
  if(P.y<GY||P.vy<0){
    if(!P.hold&&P.vy<-260)P.vy=-260;
    P.vy+=1900*dt;P.y+=P.vy*dt;
    if(P.y>=GY){P.y=GY;P.vy=0;if(buf>0){buf=0;jump()}}
  }
  if(buf>0)buf-=dt;
  const px=P.x+5,py=P.y-P.h+4,pw=P.w-10,ph=P.h-6;
  for(const o of obs){
    const ox=o.x+3,oy=GY-o.h+3,ow=o.w-6,oh=o.h-3;
    if(px<ox+ow&&px+pw>ox&&py<oy+oh&&py+ph>oy){die();break}
  }
  const sc=Math.floor(dist/10);
  if(sc!==shown){shown=sc;scoreEl.textContent=pad(sc)}
}

function rr(x,y,w,h,r){ctx.beginPath();ctx.roundRect?ctx.roundRect(x,y,w,h,r):ctx.rect(x,y,w,h);ctx.fill()}

function draw(){
  ctx.clearRect(0,0,W,H);
  ctx.fillStyle=C.panel;ctx.fillRect(0,0,W,H);
  ctx.fillStyle=C.ground;ctx.globalAlpha=.45;
  clouds.forEach(c=>{rr(c.x,c.y,64,14,7);rr(c.x+14,c.y-9,36,14,7)});
  ctx.globalAlpha=1;
  ctx.fillRect(0,GY,W,2);
  ctx.globalAlpha=.6;
  for(let x=-(dist%80);x<W;x+=80)ctx.fillRect(x,GY+12,24,2);
  ctx.globalAlpha=1;
  ctx.fillStyle=C.accent;
  obs.forEach(o=>{
    if(o.w>32){rr(o.x,GY-o.h,24,o.h,7);rr(o.x+32,GY-o.h*.75,24,o.h*.75,7)}
    else rr(o.x,GY-o.h,o.w,o.h,7);
  });
  const dead=st==='over',air=P.y<GY;
  ctx.fillStyle=C.fg;
  rr(P.x,P.y-P.h,P.w,P.h-10,9);
  const step=air||st!=='play'?0:Math.floor(dist/16)%2;
  rr(P.x+5,P.y-10-(step?4:0),9,10,3);
  rr(P.x+P.w-14,P.y-10-(step?0:4),9,10,3);
  ctx.fillStyle=C.panel;
  if(dead){ctx.fillRect(P.x+P.w-15,P.y-P.h+10,9,3)}
  else{ctx.beginPath();ctx.arc(P.x+P.w-11,P.y-P.h+13,5,0,7);ctx.fill();ctx.fillStyle=C.fg;ctx.beginPath();ctx.arc(P.x+P.w-9,P.y-P.h+13,2.2,0,7);ctx.fill()}
}

let last=performance.now();
function loop(now){
  const dt=Math.min(.05,(now-last)/1000);last=now;t+=dt;
  if(++frame%30===0)readColors();
  update(dt);draw();
  requestAnimationFrame(loop);
}
requestAnimationFrame(loop);

const KEYS=['Space','ArrowUp','KeyW'];
addEventListener('keydown',e=>{if(KEYS.includes(e.code)){e.preventDefault();if(!e.repeat)press()}});
addEventListener('keyup',e=>{if(KEYS.includes(e.code))release()});
addEventListener('pointerdown',e=>{e.preventDefault();press()});
addEventListener('pointerup',release);
addEventListener('pointercancel',release);
})();
</script>
</body>
</html>

Game Source: العدّاء اللانهائي

Creator: FrostLegend54

Libraries: none

Complexity: moderate (175 lines, 7.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: game-frostlegend54" to link back to the original. Then publish at arcadelab.ai/publish.