العدّاء اللانهائي
by FrostLegend54175 lines7.0 KB
<!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.