body {
background: #5C94FC;
font-family: "Segoe UI", Tahoma, Arial, sans-serif;
color: #fff;
display: flex;
flex-direction: column;
align-items: center;
min-height: 100vh;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
h1 {
margin: 10px 0 8px;
text-shadow: 3px 3px 0 #000;
font-weight: 900;
}
.sub {
opacity: .9;
margin-bottom: 8px
}
.game-wrap {
position: relative;
width: 95vw;
max-width: 1000px;
aspect-ratio: 16/9;
border: 4px solid #8B4513;
border-radius: 12px;
overflow: hidden;
background: #63adff;
box-shadow: 0 12px 24px rgba(0, 0, 0, .35);
}
canvas {
width: 100%;
height: 100%;
display: block;
image-rendering: pixelated;
background: linear-gradient(#76b8ff, #8cd1ff 55%, #63adff);
}
.hud {
position: absolute;
inset: 10px 10px auto 10px;
display: flex;
gap: 10px;
justify-content: space-between;
pointer-events: none;
font-weight: 800;
text-shadow: 1px 1px 2px #000;
}
.hud .left,
.hud .right {
display: flex;
gap: 14px
}
.badge {
background: rgba(0, 0, 0, .45);
padding: 6px 10px;
border-radius: 8px;
font-size: 16px
}
.msg {
position: absolute;
inset: 0;
display: none;
align-items: center;
justify-content: center;
flex-direction: column;
gap: 14px;
background: rgba(0, 0, 0, .4);
text-align: center;
font-size: 32px;
font-weight: 900;
text-shadow: 3px 3px 0 #000;
padding: 20px;
}
.msg small {
font-size: 16px;
opacity: .9
}
.mobile {
position: fixed;
left: 0;
right: 0;
bottom: 12px;
display: flex;
justify-content: space-between;
align-items: center;
padding: 0 18px;
z-index: 5;
gap: 10px;
opacity: .92;
}
.pad {
display: flex;
gap: 10px
}
.btn {
width: 64px;
height: 64px;
border: none;
border-radius: 50%;
background: rgba(255, 255, 255, .55);
color: #222;
font-size: 22px;
font-weight: 800;
box-shadow: 0 4px 0 rgba(0, 0, 0, .2);
}
.btn.jump {
background: rgba(255, 90, 90, .8);
color: #fff
}
.btn.fire {
background: rgba(255, 200, 0, .9);
color: #7a3
}
.btn.down {
background: rgba(180, 255, 180, .9);
color: #063
}
@media(min-width:900px) {
.mobile {
display: none
}
}
/* زر المشاركة */
.sharebar {
margin: 8px 0 0;
display: flex;
gap: 8px;
align-items: center;
}
.sharebtn {
border: none;
background: #ffe08a;
color: #5b3f00;
font-weight: 800;
padding: 8px 12px;
border-radius: 8px;
box-shadow: 0 3px 0 rgba(0, 0, 0, .2);
cursor: pointer;
}
.overlay {
position: fixed;
inset: 0;
display: none;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, .5);
z-index: 9999;
padding: 20px;
}
.card {
background: #fff;
color: #222;
max-width: 92vw;
border-radius: 12px;
padding: 16px;
box-shadow: 0 12px 24px rgba(0, 0, 0, .35);
}
.card h3 {
margin-bottom: 8px
}
.row {
display: flex;
gap: 10px;
align-items: center;
flex-wrap: wrap
}
.urlbox {
width: 320px;
max-width: 80vw;
padding: 6px 8px;
border: 1px solid #ccc;
border-radius: 6px
}
.qr {
width: 260px;
height: 260px;
border: 1px solid #ddd
}
.close {
margin-top: 10px;
background: #444;
color: #fff
}
</style>
مراحل طويلة، أنابيب انتقال، نقاط حفظ، صناديق مكافآت، قفز متدرّج، ورصاص بمفتاح Alt
<div class="sharebar">
<button id="shareOpen" class="sharebtn">🔗 مشاركة / QR</button>
</div>
<div class="game-wrap">
<canvas id="game"></canvas>
<div class="hud">
<div class="left">
<div class="badge">النقاط: <span id="score">0</span></div>
<div class="badge">الأرواح: <span id="lives">3</span></div>
</div>
<div class="right">
<div class="badge">المستوى: <span id="level">1</span></div>
</div>
</div>
<div id="msg" class="msg"></div>
</div>
<div class="mobile">
<div class="pad">
<button class="btn" id="left">◀</button>
<button class="btn down" id="down">▼</button>
<button class="btn" id="right">▶</button>
</div>
<div class="pad">
<button class="btn jump" id="jump">⤒</button>
<button class="btn fire" id="fire">✶</button>
</div>
</div>
<!-- Overlay لمشاركة الرابط و QR -->
<div id="overlay" class="overlay">
<div class="card">
<h3>مشاركة اللعبة</h3>
<div class="row" style="margin-bottom:8px">
<input id="pageUrl" class="urlbox" readonly />
<button id="copyLink" class="sharebtn">نسخ الرابط</button>
<button id="nativeShare" class="sharebtn">مشاركة</button>
</div>
<div class="row">
<img id="qrImg" class="qr" alt="QR" />
<div style="max-width:320px; font-size:14px; line-height:1.5">
ملاحظة: يعمل QR عند استضافة الملف على الإنترنت (مثل GitHub Pages).
إن كان الرابط يبدأ بـ file:// فلن يفتح على الجوال. ارفع الملف ثم افتح هذه النافذة مجدداً ليتولّد QR
صالح.
</div>
</div>
<button id="closeOverlay" class="sharebtn close">إغلاق</button>
</div>
</div>
<script>
// إعدادات أساسية
const canvas = document.getElementById('game');
const ctx = canvas.getContext('2d');
canvas.width = 960;
canvas.height = 540;
// HUD وعناصر الرسائل
const elScore = document.getElementById('score');
const elLives = document.getElementById('lives');
const elLevel = document.getElementById('level');
const elMsg = document.getElementById('msg');
// مشاركة/QR
const overlay = document.getElementById('overlay');
const shareOpen = document.getElementById('shareOpen');
const copyLink = document.getElementById('copyLink');
const nativeShare = document.getElementById('nativeShare');
const pageUrl = document.getElementById('pageUrl');
const qrImg = document.getElementById('qrImg');
const closeOverlay = document.getElementById('closeOverlay');
function openShare() {
const url = location.href;
pageUrl.value = url;
qrImg.src = 'https://chart.googleapis.com/chart?cht=qr&chs=300x300&chld=M|0&chl=' + encodeURIComponent(url);
overlay.style.display = 'flex';
}
shareOpen.onclick = openShare;
closeOverlay.onclick = () => overlay.style.display = 'none';
copyLink.onclick = async () => {
try { await navigator.clipboard.writeText(pageUrl.value); copyLink.textContent = 'تم النسخ ✓'; setTimeout(() => copyLink.textContent = 'نسخ الرابط', 1200); } catch { }
};
nativeShare.onclick = async () => {
try {
if (navigator.share) await navigator.share({ title: document.title, url: location.href });
else alert('ميزة المشاركة غير مدعومة على هذا المتصفح');
} catch { }
};
// الأصوات (WebAudio بسيط)
const sound = {
enabled: true,
ctx: null,
init() {
try { this.ctx = new (window.AudioContext || window.webkitAudioContext)(); }
catch (e) { this.enabled = false; }
},
tone(freq = 440, dur = .1, type = 'square', vol = .08) {
if (!this.enabled || !this.ctx) return;
const o = this.ctx.createOscillator();
const g = this.ctx.createGain();
o.type = type;
o.frequency.setValueAtTime(freq, this.ctx.currentTime);
g.gain.setValueAtTime(vol, this.ctx.currentTime);
g.gain.exponentialRampToValueAtTime(0.0001, this.ctx.currentTime + dur);
o.connect(g); g.connect(this.ctx.destination);
o.start(); o.stop(this.ctx.currentTime + dur);
},
jump() { this.tone(520, .09, 'sine', .06) },
coin() { this.tone(900, .08, 'triangle', .07) },
stomp() { this.tone(200, .12, 'square', .09) },
hit() { this.tone(160, .25, 'sawtooth', .09) },
power() { this.tone(660, .08); setTimeout(() => this.tone(880, .12), 80) },
win() { [523, 659, 783, 1046].forEach((f, i) => setTimeout(() => this.tone(f, .12, 'square', .07), 150 * i)); },
shoot() { this.tone(980, .08, 'triangle', .07) },
break() { this.tone(120, .06, 'square', .1) },
life() { this.tone(880, .08, 'square', .08); setTimeout(() => this.tone(1175, .12, 'square', .08), 90) }
};
// ثوابت فيزيائية
const GRAVITY = 0.6;
const MOVE_SPEED = 4.0;
const JUMP_VELOCITY = -12.5;
const COYOTE_TIME_FRAMES = 8;
const JUMP_BUFFER_FRAMES = 8;
// حالة اللعبة
let state = {
score: 0,
lives: 3,
levelIndex: 0,
cameraX: 0,
win: false,
gameOver: false,
respawn: { x: 0, y: 0 } // نقطة الحفظ
};
// اللاعب جاسم
const player = {
x: 100, y: 100, w: 28, h: 48,
vx: 0, vy: 0,
facing: 1,
onGround: false,
coyote: 0, jumpBuffer: 0,
big: false, invul: 0,
shootCooldown: 0
};
// كائنات العالم
let level = null;
let platforms = [];
let coins = [];
let enemies = [];
let bullets = [];
let eBullets = [];
let blocks = [];
let powerups = [];
let pipes = [];
let checkpoints = [];
let flag = { x: 2000, y: 0, h: 140, reached: false };
let houses = [];
// مُدخلات
const keys = { left: false, right: false, jump: false, shoot: false, down: false, up: false };
const justPressed = { jump: false, shoot: false };
// تحكّم لوحة مفاتيح
addEventListener('keydown', e => {
if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'Space', 'ArrowDown'].includes(e.code)) e.preventDefault();
if (state.gameOver || state.win) { startGame(); return; }
if (e.code === 'ArrowLeft' || e.code === 'KeyA') keys.left = true;
if (e.code === 'ArrowRight' || e.code === 'KeyD') keys.right = true;
if (e.code === 'ArrowUp' || e.code === 'Space' || e.code === 'KeyW') { keys.jump = true; keys.up = true; justPressed.jump = true; }
if (e.code === 'ArrowDown' || e.code === 'KeyS') { keys.down = true; }
// إطلاق بالـ Alt (مع بديل Z في حال منع النظام Alt)
if (e.code === 'AltLeft' || e.code === 'AltRight' || e.key === 'Alt' || e.code === 'KeyZ') {
e.preventDefault();
keys.shoot = true; justPressed.shoot = true;
}
}, { passive: false });
addEventListener('keyup', e => {
if (e.code === 'ArrowLeft' || e.code === 'KeyA') keys.left = false;
if (e.code === 'ArrowRight' || e.code === 'KeyD') keys.right = false;
if (e.code === 'ArrowUp' || e.code === 'Space' || e.code === 'KeyW') { keys.jump = false; keys.up = false; }
if (e.code === 'ArrowDown' || e.code === 'KeyS') { keys.down = false; }
if (e.code === 'AltLeft' || e.code === 'AltRight' || e.key === 'Alt' || e.code === 'KeyZ') { keys.shoot = false; }
});
// أزرار موبايل
const btnL = document.getElementById('left');
const btnR = document.getElementById('right');
const btnJ = document.getElementById('jump');
const btnF = document.getElementById('fire');
const btnD = document.getElementById('down');
function bindBtn(btn, prop) {
const down = e => {
e.preventDefault(); if (state.gameOver || state.win) { startGame(); return; }
keys[prop] = true; if (prop === 'jump') justPressed.jump = true; if (prop === 'shoot') justPressed.shoot = true;
};
const up = e => { e.preventDefault(); keys[prop] = false; };
btn.addEventListener('touchstart', down, { passive: false });
btn.addEventListener('touchend', up, { passive: false });
btn.addEventListener('mousedown', down);
btn.addEventListener('mouseup', up);
btn.addEventListener('mouseleave', up);
}
bindBtn(btnL, 'left'); bindBtn(btnR, 'right'); bindBtn(btnJ, 'jump'); bindBtn(btnF, 'shoot'); bindBtn(btnD, 'down');
// أدوات
const rectsCollide = (a, b) => a.x < b.x + b.w && a.x + a.w > b.x && a.y < b.y + b.h && a.y + a.h > b.y;
const clamp = (v, a, b) => Math.max(a, Math.min(b, v));
// تعريف المستويات
const LEVELS = [
{
name: "السهول", theme: "overworld",
width: 4400,
start: { x: 120, y: 360 },
flagX: 4200,
houses: [{ x: 40 }, { x: 4220 }],
platforms: [
{ x: 500, y: 420, w: 120, h: 20 },
{ x: 720, y: 380, w: 120, h: 20 },
{ x: 940, y: 340, w: 120, h: 20 },
{ x: 1280, y: 420, w: 140, h: 20 },
{ x: 1680, y: 420, w: 120, h: 20 },
{ x: 2100, y: 380, w: 120, h: 20 },
{ x: 2400, y: 340, w: 160, h: 20 },
{ x: 2800, y: 420, w: 140, h: 20 },
{ x: 3180, y: 380, w: 120, h: 20 },
{ x: 3500, y: 340, w: 140, h: 20 }
],
coins: [
...Array.from({ length: 8 }, (_, i) => ({ x: 560 + i * 26, y: 380 })),
...Array.from({ length: 8 }, (_, i) => ({ x: 1360 + i * 26, y: 380 })),
...Array.from({ length: 8 }, (_, i) => ({ x: 2460 + i * 26, y: 300 })),
...Array.from({ length: 8 }, (_, i) => ({ x: 3520 + i * 26, y: 300 }))
],
blocks: [
{ x: 820, y: 300, w: 40, h: 40, type: 'q', contains: 'coin', hit: false },
{ x: 980, y: 260, w: 40, h: 40, type: 'q', contains: 'grow', hit: false },
{ x: 1320, y: 300, w: 40, h: 40, type: 'q', contains: 'life', hit: false },
{ x: 1700, y: 300, w: 40, h: 40, type: 'brick', breakable: true, hit: false },
{ x: 1740, y: 300, w: 40, h: 40, type: 'brick', breakable: true, hit: false }
],
enemies: [
...Array.from({ length: 8 }, (_, i) => ({ type: 'walker', x: 600 + i * 350, y: 432, w: 30, h: 26, dir: i % 2 ? 1 : -1, speed: 1.1, alive: true })),
{ type: 'shooter', x: 2300, y: 432, w: 34, h: 34, cooldown: 120, alive: true },
{ type: 'walker', x: 3100, y: 432, w: 30, h: 26, dir: -1, speed: 1.2, alive: true }
],
pipes: [
{ x: 260, y: 472, w: 64, h: 64, enterDir: 'down', target: { name: 'الهضاب', spawn: { x: 80, y: 360 } } },
{ x: 1800, y: 472, w: 64, h: 64, enterDir: 'down', target: { name: 'تحت الأرض 1', spawn: { x: 120, y: 360 } } }
],
checkpoints: [{ x: 2200 }]
},
{
name: "الهضاب", theme: "overworld",
width: 5200,
start: { x: 80, y: 360 },
flagX: 5000,
houses: [{ x: 40 }, { x: 5020 }],
platforms: [
{ x: 380, y: 420, w: 160, h: 20 },
{ x: 620, y: 360, w: 140, h: 20 },
{ x: 900, y: 300, w: 160, h: 20 },
{ x: 1280, y: 360, w: 160, h: 20 },
{ x: 1600, y: 420, w: 160, h: 20 },
{ x: 2000, y: 380, w: 160, h: 20 },
{ x: 2400, y: 340, w: 160, h: 20 },
{ x: 2800, y: 300, w: 200, h: 20 },
{ x: 3400, y: 360, w: 160, h: 20 },
{ x: 3800, y: 420, w: 160, h: 20 },
{ x: 4200, y: 380, w: 160, h: 20 }
],
coins: [
...Array.from({ length: 10 }, (_, i) => ({ x: 980 + i * 26, y: 260 })),
...Array.from({ length: 10 }, (_, i) => ({ x: 2860 + i * 26, y: 260 }))
],
blocks: [
{ x: 620, y: 320, w: 40, h: 40, type: 'q', contains: 'grow', hit: false },
{ x: 1260, y: 320, w: 40, h: 40, type: 'q', contains: 'coin', hit: false },
{ x: 2000, y: 340, w: 40, h: 40, type: 'q', contains: 'life', hit: false },
{ x: 2400, y: 300, w: 40, h: 40, type: 'brick', breakable: true, hit: false },
{ x: 2440, y: 300, w: 40, h: 40, type: 'brick', breakable: true, hit: false },
{ x: 2480, y: 300, w: 40, h: 40, type: 'brick', breakable: true, hit: false }
],
enemies: [
...Array.from({ length: 10 }, (_, i) => ({ type: 'walker', x: 500 + i * 320, y: 432, w: 30, h: 26, dir: i % 2 ? 1 : -1, speed: 1.3, alive: true })),
{ type: 'shooter', x: 2600, y: 432, w: 34, h: 34, cooldown: 100, alive: true },
{ type: 'shooter', x: 4200, y: 432, w: 34, h: 34, cooldown: 120, alive: true }
],
pipes: [
{ x: 600, y: 472, w: 64, h: 64, enterDir: 'up', target: { name: 'السهول', spawn: { x: 320, y: 360 } } },
{ x: 2200, y: 472, w: 64, h: 64, enterDir: 'down', target: { name: 'تحت الأرض 1', spawn: { x: 420, y: 360 } } }
],
checkpoints: [{ x: 2600 }]
},
{
name: "تحت الأرض 1", theme: "underground",
width: 3000,
start: { x: 120, y: 360 },
flagX: 2800,
houses: [],
platforms: [
{ x: 300, y: 420, w: 160, h: 20 },
{ x: 560, y: 380, w: 140, h: 20 },
{ x: 820, y: 340, w: 160, h: 20 },
{ x: 1100, y: 380, w: 160, h: 20 },
{ x: 1400, y: 420, w: 160, h: 20 },
{ x: 1800, y: 380, w: 160, h: 20 },
{ x: 2200, y: 340, w: 160, h: 20 }
],
coins: [
...Array.from({ length: 12 }, (_, i) => ({ x: 360 + i * 26, y: 380 })),
...Array.from({ length: 12 }, (_, i) => ({ x: 1860 + i * 26, y: 300 }))
],
blocks: [
{ x: 560, y: 340, w: 40, h: 40, type: 'q', contains: 'coin', hit: false },
{ x: 820, y: 300, w: 40, h: 40, type: 'q', contains: 'grow', hit: false }
],
enemies: [
...Array.from({ length: 6 }, (_, i) => ({ type: 'walker', x: 400 + i * 260, y: 432, w: 30, h: 26, dir: i % 2 ? 1 : -1, speed: 1.2, alive: true })),
{ type: 'shooter', x: 2000, y: 432, w: 34, h: 34, cooldown: 100, alive: true }
],
pipes: [
{ x: 2600, y: 472, w: 64, h: 64, enterDir: 'up', target: { name: 'الهضاب', spawn: { x: 2400, y: 360 } } }
],
checkpoints: [{ x: 1500 }]
},
{
name: "الصحراء", theme: "desert",
width: 5200,
start: { x: 120, y: 360 },
flagX: 5000,
houses: [{ x: 40 }, { x: 5020 }],
platforms: [
{ x: 460, y: 420, w: 200, h: 20 },
{ x: 820, y: 380, w: 180, h: 20 },
{ x: 1200, y: 340, w: 160, h: 20 },
{ x: 1600, y: 300, w: 200, h: 20 },
{ x: 2100, y: 340, w: 160, h: 20 },
{ x: 2500, y: 380, w: 180, h: 20 },
{ x: 2900, y: 420, w: 160, h: 20 },
{ x: 3400, y: 380, w: 180, h: 20 },
{ x: 3800, y: 340, w: 160, h: 20 },
{ x: 4200, y: 300, w: 200, h: 20 }
],
coins: [
...Array.from({ length: 10 }, (_, i) => ({ x: 900 + i * 30, y: 340 })),
...Array.from({ length: 10 }, (_, i) => ({ x: 3200 + i * 30, y: 300 }))
],
blocks: [
{ x: 820, y: 340, w: 40, h: 40, type: 'q', contains: 'grow', hit: false },
{ x: 1600, y: 260, w: 40, h: 40, type: 'q', contains: 'coin', hit: false },
{ x: 2500, y: 340, w: 40, h: 40, type: 'brick', breakable: true, hit: false }
],
enemies: [
...Array.from({ length: 10 }, (_, i) => ({ type: 'walker', x: 600 + i * 340, y: 432, w: 30, h: 26, dir: i % 2 ? 1 : -1, speed: 1.35, alive: true })),
{ type: 'shooter', x: 2800, y: 432, w: 34, h: 34, cooldown: 100, alive: true },
{ type: 'shooter', x: 4200, y: 432, w: 34, h: 34, cooldown: 90, alive: true }
],
pipes: [
{ x: 4800, y: 472, w: 64, h: 64, enterDir: 'down', target: { name: 'الثلوج', spawn: { x: 120, y: 360 } } }
],
checkpoints: [{ x: 2600 }]
},
{
name: "الثلوج", theme: "snow",
width: 5000,
start: { x: 120, y: 360 },
flagX: 4800,
houses: [{ x: 40 }, { x: 4820 }],
platforms: [
{ x: 520, y: 420, w: 140, h: 20 },
{ x: 760, y: 380, w: 140, h: 20 },
{ x: 1000, y: 340, w: 140, h: 20 },
{ x: 1240, y: 300, w: 140, h: 20 },
{ x: 1600, y: 340, w: 160, h: 20 },
{ x: 2000, y: 380, w: 160, h: 20 },
{ x: 2400, y: 340, w: 160, h: 20 },
{ x: 2800, y: 300, w: 160, h: 20 },
{ x: 3200, y: 340, w: 160, h: 20 },
{ x: 3600, y: 380, w: 160, h: 20 }
],
coins: [
...Array.from({ length: 10 }, (_, i) => ({ x: 1200 + i * 28, y: 260 })),
...Array.from({ length: 10 }, (_, i) => ({ x: 3000 + i * 28, y: 260 }))
],
blocks: [
{ x: 760, y: 340, w: 40, h: 40, type: 'q', contains: 'grow', hit: false },
{ x: 1240, y: 260, w: 40, h: 40, type: 'q', contains: 'life', hit: false }
],
enemies: [
...Array.from({ length: 8 }, (_, i) => ({ type: 'walker', x: 600 + i * 360, y: 432, w: 30, h: 26, dir: i % 2 ? 1 : -1, speed: 1.25, alive: true })),
{ type: 'shooter', x: 2600, y: 432, w: 34, h: 34, cooldown: 90, alive: true }
],
pipes: [
{ x: 4200, y: 472, w: 64, h: 64, enterDir: 'down', target: { name: 'الجزر السماوية', spawn: { x: 120, y: 240 } } }
],
checkpoints: [{ x: 2500 }]
},
{
name: "الجزر السماوية", theme: "sky",
width: 5400,
start: { x: 120, y: 240 },
flagX: 5200,
houses: [],
platforms: [
{ x: 300, y: 280, w: 160, h: 20 },
{ x: 600, y: 220, w: 160, h: 20 },
{ x: 900, y: 260, w: 160, h: 20 },
{ x: 1200, y: 200, w: 160, h: 20 },
{ x: 1500, y: 240, w: 160, h: 20 },
{ x: 1900, y: 280, w: 160, h: 20 },
{ x: 2300, y: 240, w: 160, h: 20 },
{ x: 2700, y: 200, w: 160, h: 20 },
{ x: 3100, y: 240, w: 160, h: 20 },
{ x: 3500, y: 200, w: 160, h: 20 },
{ x: 3900, y: 240, w: 160, h: 20 }
],
coins: [...Array.from({ length: 10 }, (_, i) => ({ x: 1000 + i * 36, y: 180 }))],
blocks: [{ x: 900, y: 220, w: 40, h: 40, type: 'q', contains: 'grow', hit: false }],
enemies: [
...Array.from({ length: 10 }, (_, i) => ({ type: 'walker', x: 600 + i * 320, y: 292, w: 30, h: 26, dir: i % 2 ? 1 : -1, speed: 1.2, alive: true })),
{ type: 'shooter', x: 2600, y: 292, w: 34, h: 34, cooldown: 100, alive: true }
],
pipes: [
{ x: 5000, y: 472, w: 64, h: 64, enterDir: 'down', target: { name: 'القلعة', spawn: { x: 80, y: 360 } } }
],
checkpoints: [{ x: 2700 }]
},
{
name: "القلعة", theme: "castle",
width: 5600,
start: { x: 80, y: 360 },
flagX: 5400,
houses: [{ x: 40 }, { x: 5420 }],
platforms: [
{ x: 500, y: 420, w: 160, h: 20 },
{ x: 720, y: 360, w: 160, h: 20 },
{ x: 940, y: 300, w: 160, h: 20 },
{ x: 1280, y: 300, w: 160, h: 20 },
{ x: 1600, y: 360, w: 160, h: 20 },
{ x: 1920, y: 420, w: 160, h: 20 },
{ x: 2400, y: 380, w: 160, h: 20 },
{ x: 2800, y: 340, w: 160, h: 20 },
{ x: 3200, y: 300, w: 160, h: 20 },
{ x: 3600, y: 340, w: 160, h: 20 },
{ x: 4000, y: 380, w: 160, h: 20 },
{ x: 4400, y: 420, w: 160, h: 20 }
],
coins: [
...Array.from({ length: 12 }, (_, i) => ({ x: 1360 + i * 26, y: 260 })),
...Array.from({ length: 12 }, (_, i) => ({ x: 3240 + i * 26, y: 260 }))
],
blocks: [
{ x: 720, y: 320, w: 40, h: 40, type: 'q', contains: 'grow', hit: false },
{ x: 940, y: 260, w: 40, h: 40, type: 'q', contains: 'life', hit: false },
{ x: 1600, y: 320, w: 40, h: 40, type: 'brick', breakable: true, hit: false },
{ x: 1640, y: 320, w: 40, h: 40, type: 'brick', breakable: true, hit: false },
{ x: 1680, y: 320, w: 40, h: 40, type: 'brick', breakable: true, hit: false }
],
enemies: [
...Array.from({ length: 12 }, (_, i) => ({ type: 'walker', x: 600 + i * 320, y: 432, w: 30, h: 26, dir: i % 2 ? 1 : -1, speed: 1.35, alive: true })),
{ type: 'shooter', x: 2500, y: 432, w: 34, h: 34, cooldown: 90, alive: true },
{ type: 'shooter', x: 3800, y: 432, w: 34, h: 34, cooldown: 90, alive: true },
{ type: 'shooter', x: 5000, y: 432, w: 34, h: 34, cooldown: 80, alive: true }
],
pipes: [
{ x: 5200, y: 472, w: 64, h: 64, enterDir: 'down', target: { name: 'السهول', spawn: { x: 260, y: 360 } } }
],
checkpoints: [{ x: 2800 }]
}
];
// تهيئة مستوى
function loadLevel(i) {
const src = LEVELS[i];
level = JSON.parse(JSON.stringify(src));
platforms = [{ x: 0, y: 472, w: level.width, h: 68 }, ...level.platforms];
coins = level.coins.map(c => ({ x: c.x, y: c.y, w: 18, h: 18, taken: false }));
blocks = level.blocks.map(b => ({ ...b }));
enemies = level.enemies.map(e => ({ ...e }));
bullets = []; eBullets = []; powerups = [];
pipes = (level.pipes || []).map(p => ({ ...p }));
checkpoints = (level.checkpoints || []).map(c => ({ ...c, reached: false }));
flag = { x: level.flagX, y: 472 - 140, h: 140, reached: false };
houses = level.houses || [];
player.x = level.start.x; player.y = level.start.y;
player.vx = 0; player.vy = 0; player.onGround = false; player.coyote = 0; player.jumpBuffer = 0; player.invul = 0; player.shootCooldown = 0;
state.cameraX = 0;
state.respawn = { x: level.start.x, y: Math.min(level.start.y, 472 - player.h) }; // نقطة الحفظ الأولية
elLevel.textContent = (state.levelIndex + 1);
updateHUD();
hideMsg();
}
function updateHUD() {
elScore.textContent = state.score;
elLives.textContent = state.lives;
elLevel.textContent = (state.levelIndex + 1);
}
function showMsg(txt, small) {
elMsg.innerHTML = txt + (small ? `<br><small>${small}</small>` : '');
elMsg.style.display = 'flex';
}
function hideMsg() { elMsg.style.display = 'none'; }
function startGame() {
sound.init();
state.score = 0; state.lives = 3; state.levelIndex = 0;
state.gameOver = false; state.win = false;
player.big = false;
loadLevel(state.levelIndex);
}
// فيزياء اللاعب
function updatePlayer() {
// حركة أفقية
let ax = 0;
if (keys.left) ax = -MOVE_SPEED;
if (keys.right) ax = MOVE_SPEED;
player.vx = ax;
if (player.vx !== 0) player.facing = Math.sign(player.vx);
// جاذبية
player.vy += GRAVITY;
// قفزة متغيرة الطول (ضغط مطوّل أعلى)
if (player.vy < 0) {
if (!keys.jump) player.vy += 0.6; // تقصير القفزة عند ترك الزر
else player.vy += -0.12; // دعم خفيف لقفزة أعلى
}
// coyote/jump buffer
if (player.onGround) player.coyote = COYOTE_TIME_FRAMES;
else if (player.coyote > 0) player.coyote--;
if (justPressed.jump) player.jumpBuffer = JUMP_BUFFER_FRAMES;
else if (player.jumpBuffer > 0) player.jumpBuffer--;
// بدء القفز
if (player.jumpBuffer > 0 && player.coyote > 0) {
player.vy = JUMP_VELOCITY;
player.onGround = false;
player.coyote = 0;
player.jumpBuffer = 0;
sound.jump();
}
// إطلاق
if (player.shootCooldown > 0) player.shootCooldown--;
if (justPressed.shoot && player.shootCooldown === 0) {
bullets.push({
x: player.x + player.w / 2 + player.facing * 18,
y: player.y + player.h * 0.45,
w: 10, h: 10,
vx: player.facing * 9.5
});
player.shootCooldown = 12;
sound.shoot();
}
// الحركة مع التصادم
moveWithCollisions(player, player.vx, player.vy);
player.onGround = checkOnGround(player);
// حصانة قصيرة
if (player.invul > 0) player.invul--;
// سقطة خارج العالم
if (player.y > canvas.height + 200) {
takeHit(true);
}
// تحريك الكاميرا
state.cameraX = clamp(player.x - canvas.width * 0.4, 0, level.width - canvas.width);
justPressed.jump = false;
justPressed.shoot = false;
}
// حركة كائن مع تصادم منصات (X ثم Y)
function moveWithCollisions(o, dx, dy) {
// X
o.x += dx;
const nearby = platformsInRange(o);
for (const p of nearby) {
if (rectsCollide(o, p)) {
if (dx > 0) o.x = p.x - o.w;
else if (dx < 0) o.x = p.x + p.w;
}
}
// Y
o.y += dy;
for (const p of nearby) {
if (rectsCollide(o, p)) {
if (dy > 0) { o.y = p.y - o.h; o.vy = 0; }
else if (dy < 0) {
o.y = p.y + p.h; o.vy = 0;
hitBlockAt(o.x + o.w * 0.5, p.y + p.h + 1);
}
}
}
}
function platformsInRange(o) {
const r = 80;
const minX = o.x - r, maxX = o.x + o.w + r;
return platforms.filter(pl => pl.x < maxX && pl.x + pl.w > minX);
}
function checkOnGround(o) {
const feet = { x: o.x, y: o.y + o.h, w: o.w, h: 2 };
return platforms.some(p => rectsCollide(feet, p));
}
// صناديق
function hitBlockAt(cx, yTouch) {
for (let i = 0; i < blocks.length; i++) {
const b = blocks[i];
if (Math.abs((b.x + 20) - cx) <= 22 && Math.abs((b.y + b.h) - yTouch) < 8) {
if (b.type === 'q' && !b.hit) {
b.hit = true;
if (b.contains === 'coin') {
state.score += 10; updateHUD(); sound.coin();
} else if (b.contains === 'grow') {
powerups.push({ type: 'grow', x: b.x, y: b.y - 22, w: 22, h: 22, vx: 1.0 });
sound.power();
} else if (b.contains === 'life') {
state.lives += 1; updateHUD(); sound.life();
}
} else if (b.type === 'brick') {
if (b.breakable && player.big) {
blocks.splice(i, 1);
sound.break();
}
}
break;
}
}
}
function updateCoins() {
for (const c of coins) {
if (!c.taken && rectsCollide(player, c)) {
c.taken = true;
state.score += 5; updateHUD(); sound.coin();
}
}
}
function updatePowerups() {
for (let i = powerups.length - 1; i >= 0; i--) {
const p = powerups[i];
p.x += p.vx;
p.vy = (p.vy || 0) + GRAVITY * 0.7;
moveItemWithPlatforms(p);
if (rectsCollide(player, p)) {
powerups.splice(i, 1);
if (p.type === 'grow') {
if (!player.big) {
player.big = true;
const oldH = player.h;
player.h = 64;
player.y -= (player.h - oldH);
}
state.score += 20; updateHUD(); sound.power();
}
}
}
}
function moveItemWithPlatforms(o) {
const dx = o.vx || 0, dy = o.vy || 0;
o.x += dx;
for (const p of platforms) {
if (rectsCollide(o, p)) {
if (dx > 0) o.x = p.x - o.w;
else o.x = p.x + p.w;
o.vx = -(o.vx || 0);
}
}
o.y += dy;
for (const p of platforms) {
if (rectsCollide(o, p)) {
if (dy > 0) { o.y = p.y - o.h; o.vy = 0; }
else { o.y = p.y + p.h; o.vy = 0; }
}
}
}
function updateEnemies() {
for (const e of enemies) {
if (!e.alive) continue;
if (e.type === 'walker') {
e.vx = e.dir * e.speed;
e.vy = (e.vy || 0) + GRAVITY * 0.9;
moveEntityWithPlatforms(e, e.vx, e.vy);
if (onEdge(e)) e.dir *= -1;
if (rectsCollide(player, e)) {
if (player.vy > 0 && player.y + player.h - 6 < e.y + 10) {
e.alive = false; state.score += 20; updateHUD();
player.vy = -9; sound.stomp();
} else {
takeHit(false);
}
}
} else if (e.type === 'shooter') {
e.vy = (e.vy || 0) + GRAVITY * 0.9;
moveEntityWithPlatforms(e, 0, e.vy);
if (e.cooldown > 0) e.cooldown--;
const dist = Math.abs((e.x + e.w / 2) - (player.x + player.w / 2));
if (dist < 520 && e.cooldown === 0) {
const dir = Math.sign((player.x + player.w / 2) - (e.x + e.w / 2)) || 1;
eBullets.push({ x: e.x + e.w / 2, y: e.y + e.h * 0.5, w: 10, h: 10, vx: dir * 6.5, vy: 0 });
e.cooldown = 90 + (Math.random() * 60 | 0);
}
if (rectsCollide(player, e)) takeHit(false);
}
}
}
function moveEntityWithPlatforms(o, dx, dy) {
o.x += dx;
const near = platformsInRange(o);
for (const p of near) {
if (rectsCollide(o, p)) {
if (dx > 0) o.x = p.x - o.w;
else o.x = p.x + p.w;
o.vx = 0; if (o.dir) o.dir *= -1;
}
}
o.y += dy;
for (const p of near) {
if (rectsCollide(o, p)) {
if (dy > 0) { o.y = p.y - o.h; o.vy = 0; }
else { o.y = p.y + p.h; o.vy = 0; }
}
}
}
function onEdge(e) {
const foot = { x: e.x + (e.dir > 0 ? e.w + 1 : -1), y: e.y + e.h + 1, w: 2, h: 2 };
return !platforms.some(p => rectsCollide(foot, p));
}
function updateBullets() {
// رصاص اللاعب
for (let i = bullets.length - 1; i >= 0; i--) {
const b = bullets[i];
b.x += b.vx;
const hitPlatform = platforms.some(p => rectsCollide(b, p));
if (hitPlatform || b.x < 0 || b.x > level.width) { bullets.splice(i, 1); continue; }
for (const e of enemies) {
if (e.alive && rectsCollide(b, e)) {
e.alive = false; bullets.splice(i, 1);
state.score += 30; updateHUD(); sound.stomp();
break;
}
}
}
// رصاص الأعداء
for (let i = eBullets.length - 1; i >= 0; i--) {
const b = eBullets[i];
b.x += b.vx; b.y += b.vy;
const hitPlatform = platforms.some(p => rectsCollide(b, p));
if (hitPlatform || b.x < 0 || b.x > level.width) { eBullets.splice(i, 1); continue; }
if (rectsCollide(player, b)) { eBullets.splice(i, 1); takeHit(false); }
}
}
function takeHit(fell) {
if (player.invul > 0) return;
if (player.big) {
player.big = false;
const oldH = player.h;
player.h = 48;
player.y += (oldH - player.h);
player.invul = 120;
sound.hit();
} else {
state.lives--; updateHUD(); sound.hit();
if (state.lives <= 0) {
state.gameOver = true;
showMsg('انتهت اللعبة', 'اضغط أي زر أو المس الشاشة للبدء من جديد');
return;
} else {
// العودة لأقرب نقطة حفظ بدلاً من البداية
player.x = state.respawn.x; player.y = Math.min(state.respawn.y, 472 - player.h);
player.vx = 0; player.vy = 0; player.invul = 120;
}
}
}
// نقاط حفظ
function updateCheckpoints() {
for (const cp of checkpoints) {
if (!cp.reached && player.x + player.w / 2 >= cp.x) {
cp.reached = true;
state.respawn = { x: cp.x + 20, y: 472 - player.h };
showMsg('نقطة حفظ ✓'); setTimeout(hideMsg, 700);
}
}
}
// أنابيب: انتقال بين المراحل
function findLevelByName(name) { return LEVELS.findIndex(l => l.name === name); }
function warpTo(target) {
const idx = (typeof target.index === 'number') ? target.index : findLevelByName(target.name);
if (idx < 0) return;
state.levelIndex = idx;
loadLevel(state.levelIndex);
if (target.spawn) {
player.x = target.spawn.x;
player.y = target.spawn.y;
player.vx = 0; player.vy = 0;
}
state.respawn = { x: player.x, y: player.y };
sound.power();
showMsg('انتقال عبر الأنبوب...', ''); setTimeout(hideMsg, 500);
}
function checkPipes() {
for (const p of pipes) {
const topArea = { x: p.x, y: p.y - 4, w: p.w, h: 8 };
const onTop = rectsCollide(
{ x: player.x + 4, y: player.y + player.h - 2, w: player.w - 8, h: 4 },
topArea
);
if (onTop && p.enterDir === 'down' && keys.down && player.onGround) { warpTo(p.target); return; }
if (onTop && p.enterDir === 'up' && keys.up && player.onGround) { warpTo(p.target); return; }
}
}
function checkFlag() {
if (!flag.reached && player.x + player.w > flag.x) {
flag.reached = true;
sound.win();
state.score += 100; updateHUD();
if (state.levelIndex < LEVELS.length - 1) {
showMsg(`أحسنت! الانتقال إلى المستوى ${state.levelIndex + 2}`, '...جاري التحميل');
setTimeout(() => { state.levelIndex++; loadLevel(state.levelIndex); }, 1400);
} else {
state.win = true;
showMsg('فوز! لقد أنهيت اللعبة', 'اضغط أي زر لإعادة اللعب');
}
}
}
// خلفية حسب الثيم
function draw() {
ctx.clearRect(0, 0, canvas.width, canvas.height);
drawBackground();
drawWorld();
}
function drawBackground() {
const cam = state.cameraX;
const th = (level.theme || 'overworld');
let skyTop = '#76b8ff', skyMid = '#8cd1ff', hill1 = '#3c9444', hill2 = '#2e7a36';
if (th === 'underground') { skyTop = skyMid = '#1a1a1a'; hill1 = '#222'; hill2 = '#111'; }
if (th === 'desert') { skyTop = '#f7c56b'; skyMid = '#ffd98a'; hill1 = '#d6a14a'; hill2 = '#c1872e'; }
if (th === 'snow') { skyTop = '#bfe9ff'; skyMid = '#e8f6ff'; hill1 = '#a6d5f2'; hill2 = '#8fc3e6'; }
if (th === 'sky') { skyTop = '#b8e2ff'; skyMid = '#dff3ff'; hill1 = '#cfeaff'; hill2 = '#b6dcff'; }
if (th === 'castle') { skyTop = '#3e3e3e'; skyMid = '#5a5a5a'; hill1 = '#444'; hill2 = '#383838'; }
const g = ctx.createLinearGradient(0, 0, 0, canvas.height);
g.addColorStop(0, skyTop); g.addColorStop(0.6, skyMid); g.addColorStop(1, skyMid);
ctx.fillStyle = g; ctx.fillRect(0, 0, canvas.width, canvas.height);
if (th !== 'underground' && th !== 'castle') {
ctx.fillStyle = 'rgba(255,255,255,0.85)';
for (let i = 0; i < 8; i++) {
const x = ((i * 500 - (cam * 0.4)) % 1200 + 1200) % 1200 - 200;
const y = 80 + (i % 3) * 30;
drawCloud(x, y, 120, 30);
}
}
for (let i = 0; i < 10; i++) {
const x = ((i * 700 - (cam * 0.2)) % 1800 + 1800) % 1800 - 300;
drawHill(x, 540, 220, 120, hill1);
}
for (let i = 0; i < 10; i++) {
const x = ((i * 700 + 200 - (cam * 0.15)) % 1800 + 1800) % 1800 - 300;
drawHill(x, 540, 280, 150, hill2);
}
}
function drawCloud(x, y, w, h) {
ctx.beginPath();
ctx.ellipse(x, y, w * .32, h * .52, 0, 0, Math.PI * 2);
ctx.ellipse(x + w * 0.25, y - 10, w * .28, h * .55, 0, 0, Math.PI * 2);
ctx.ellipse(x + w * 0.5, y, w * .35, h * .6, 0, 0, Math.PI * 2);
ctx.fill();
}
function drawHill(x, baseY, w, h, color) {
ctx.fillStyle = color;
ctx.beginPath();
ctx.moveTo(x, baseY);
ctx.quadraticCurveTo(x + w * 0.5, baseY - h, x + w, baseY);
ctx.lineTo(x + w, baseY + 10);
ctx.lineTo(x, baseY + 10);
ctx.closePath();
ctx.fill();
}
function drawPipe(x, y, w, h, cam) {
ctx.fillStyle = '#0a9a2a';
ctx.fillRect(x - cam, y - h, w, h);
ctx.fillStyle = '#0fc642';
ctx.fillRect(x - cam - 6, y - h - 14, w + 12, 14);
ctx.fillStyle = 'rgba(0,0,0,.15)';
ctx.fillRect(x - cam, y - h + 6, w, 3);
}
function drawCheckpoint(x, cam, reached) {
ctx.fillStyle = reached ? '#33dd66' : '#cccccc';
ctx.fillRect(x - cam, 472 - 60, 8, 60);
ctx.fillStyle = reached ? '#33dd66' : '#ff4444';
ctx.beginPath();
ctx.moveTo(x - cam + 8, 472 - 60);
ctx.lineTo(x - cam + 48, 472 - 40);
ctx.lineTo(x - cam + 8, 472 - 20);
ctx.closePath(); ctx.fill();
}
function drawWorld() {
const cam = state.cameraX;
// أرضية
ctx.fillStyle = '#e8834a';
ctx.fillRect(-cam, 472, canvas.width, 68);
for (let x = Math.floor(cam / 40) * 40 - 40; x < cam + canvas.width + 40; x += 40) {
ctx.fillStyle = (Math.floor(x / 40) % 2 === 0) ? '#d4733e' : '#c76532';
ctx.fillRect(x - cam, 520, 40, 20);
}
// منصات
ctx.fillStyle = '#d4733e';
for (const p of platforms) {
if (p.y >= 472 && p.h >= 68) continue;
if (p.x + p.w < cam - 20 || p.x > cam + canvas.width + 20) continue;
ctx.fillRect(p.x - cam, p.y, p.w, p.h);
ctx.strokeStyle = 'rgba(0,0,0,.15)';
ctx.strokeRect(p.x - cam, p.y, p.w, p.h);
}
// صناديق
for (const b of blocks) {
if (b.x + b.w < cam - 20 || b.x > cam + canvas.width + 20) continue;
if (b.type === 'q') {
ctx.fillStyle = b.hit ? '#8c6b1a' : '#c88a1a';
ctx.fillRect(b.x - cam, b.y, b.w, b.h);
if (!b.hit) { ctx.fillStyle = '#fff'; ctx.fillRect(b.x - cam + 16, b.y + 8, 8, 8); }
} else {
ctx.fillStyle = '#b87333';
ctx.fillRect(b.x - cam, b.y, b.w, b.h);
ctx.strokeStyle = 'rgba(0,0,0,.25)';
ctx.strokeRect(b.x - cam, b.y, b.w, b.h);
}
}
// عملات
for (const c of coins) {
if (c.taken) continue;
if (c.x + c.w < cam - 20 || c.x > cam + canvas.width + 20) continue;
ctx.fillStyle = '#ffd700';
ctx.beginPath(); ctx.arc(c.x - cam, c.y, 9, 0, Math.PI * 2); ctx.fill();
ctx.fillStyle = 'rgba(255,255,255,.6)'; ctx.fillRect(c.x - cam - 2, c.y - 5, 3, 10);
}
// قوى
for (const p of powerups) {
if (p.x + p.w < cam - 20 || p.x > cam + canvas.width + 20) continue;
ctx.fillStyle = '#ff4444';
ctx.fillRect(p.x - cam, p.y, p.w, p.h);
ctx.fillStyle = '#fff'; ctx.fillRect(p.x - cam + 6, p.y + 6, 10, 4);
}
// أعداء
for (const e of enemies) {
if (!e.alive) continue;
if (e.x + e.w < cam - 20 || e.x > cam + canvas.width + 20) continue;
if (e.type === 'walker') {
ctx.fillStyle = '#8B4513';
ctx.fillRect(e.x - cam, e.y, e.w, e.h);
ctx.fillStyle = '#000';
ctx.fillRect(e.x - cam + 6, e.y + 6, 4, 4);
ctx.fillRect(e.x - cam + e.w - 10, e.y + 6, 4, 4);
} else {
ctx.fillStyle = '#000080';
ctx.fillRect(e.x - cam, e.y, e.w, e.h);
ctx.fillStyle = '#fff';
ctx.fillRect(e.x - cam + 8, e.y + 8, 6, 6);
ctx.fillRect(e.x - cam + e.w - 14, e.y + 8, 6, 6);
}
}
// رصاص
ctx.fillStyle = '#ff2e2e';
for (const b of bullets) {
if (b.x + b.w < cam - 20 || b.x > cam + canvas.width + 20) continue;
ctx.fillRect(b.x - cam, b.y, b.w, b.h);
}
ctx.fillStyle = '#ffcc00';
for (const b of eBullets) {
if (b.x + b.w < cam - 20 || b.x > cam + canvas.width + 20) continue;
ctx.fillRect(b.x - cam, b.y, b.w, b.h);
}
// بيوت
for (const h of houses) {
const x = h.x;
if (x < cam - 200 || x > cam + canvas.width + 200) continue;
drawHouse(x - cam, 472);
}
// أنابيب
for (const p of pipes) {
if (p.x + p.w < cam - 40 || p.x > cam + canvas.width + 40) continue;
drawPipe(p.x, p.y, p.w, p.h, cam);
}
// نقاط حفظ
for (const cp of checkpoints) {
drawCheckpoint(cp.x, cam, !!cp.reached);
}
// سارية العلم
ctx.fillStyle = '#c0c0c0';
ctx.fillRect(flag.x - cam, flag.y, 8, flag.h);
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.moveTo(flag.x - cam + 8, flag.y + 4);
ctx.lineTo(flag.x - cam + 52, flag.y + 24);
ctx.lineTo(flag.x - cam + 8, flag.y + 44);
ctx.closePath(); ctx.fill();
// اللاعب
if (player.invul % 10 < 7) {
drawPlayer(player.x - cam, player.y, player.w, player.h, player.facing, player.big);
}
}
function drawHouse(x, groundY) {
ctx.fillStyle = '#8B4513';
ctx.fillRect(x, groundY - 110, 100, 110);
ctx.fillStyle = '#b33';
ctx.beginPath();
ctx.moveTo(x - 10, groundY - 110);
ctx.lineTo(x + 50, groundY - 150);
ctx.lineTo(x + 110, groundY - 110);
ctx.closePath();
ctx.fill();
ctx.fillStyle = '#2b1a0d';
ctx.fillRect(x + 36, groundY - 50, 28, 50);
ctx.fillStyle = '#eee';
ctx.fillRect(x + 16, groundY - 85, 18, 18);
ctx.fillRect(x + 66, groundY - 85, 18, 18);
}
function drawPlayer(x, y, w, h, facing, big) {
ctx.fillStyle = '#ff4136';
ctx.fillRect(x, y, w, 10);
ctx.fillStyle = '#f6c6a6';
ctx.fillRect(x + 4, y + 10, w - 8, 12);
ctx.fillStyle = '#000';
const eyeX = facing > 0 ? x + w - 10 : x + 6;
ctx.fillRect(eyeX, y + 12, 3, 3);
ctx.fillStyle = big ? '#d11' : '#ff4136';
ctx.fillRect(x + 2, y + 22, w - 4, 16);
ctx.fillStyle = '#0074d9';
ctx.fillRect(x + 2, y + 38, w - 4, h - 38);
}
// حلقة اللعبة
function update() {
if (state.gameOver || state.win) return;
updatePlayer();
updateCoins();
updatePowerups();
updateEnemies();
updateBullets();
updateCheckpoints();
checkPipes();
checkFlag();
}
function loop() { update(); draw(); requestAnimationFrame(loop); }
// ابدأ
startGame();
loop();
</script>