تخطَّ إلى المحتوى
ARAB CODEمجتمع المطورين العرب

Car-arab-code-BMG

htmlv1MITنُشر ٢٧ سبتمبر ٢٠٢٦· ♥ 0 · ⬇ 1 · 💬 0

لعبه سيارات بعبصه اخر اليل - BMG

<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover" />
<meta name="theme-color" content="#050510" />
<title>3D ROAD RUSH</title>
<style>
  * { margin: 0; padding: 0; box-sizing: border-box; }
  html, body {
    width: 100%; height: 100%;
    overflow: hidden;
    background: #050510;
    color: #fff;
    font-family: 'Segoe UI', system-ui, -apple-system, 'Helvetica Neue', Arial, sans-serif;
    user-select: none;
    -webkit-user-select: none;
    -webkit-tap-highlight-color: transparent;
    touch-action: none;
    overscroll-behavior: none;
  }
  #game { position: fixed; inset: 0; z-index: 0; }
  #game canvas { display: block; width: 100% !important; height: 100% !important; }

  /* ===== Speed vignette ===== */
  #speedOverlay {
    position: fixed; inset: 0;
    pointer-events: none;
    z-index: 5;
    background: radial-gradient(ellipse at 50% 45%, rgba(0,0,0,0) 30%, rgba(20,10,50,0.55) 75%, rgba(0,0,0,0.85) 100%);
    opacity: 0.15;
    transition: opacity 0.25s linear;
  }

  /* ===== HUD ===== */
  #hud {
    position: fixed;
    top: 0; left: 0; right: 0;
    padding: max(12px, env(safe-area-inset-top)) 12px 0;
    display: flex;
    justify-content: center;
    gap: 8px;
    z-index: 10;
    pointer-events: none;
    flex-wrap: wrap;
  }
  .hud-panel {
    background: rgba(10, 14, 32, 0.55);
    backdrop-filter: blur(12px);
    -webkit-backdrop-filter: blur(12px);
    border: 1px solid rgba(120, 180, 255, 0.18);
    border-radius: 12px;
    padding: 7px 14px;
    min-width: 84px;
    text-align: center;
    box-shadow: 0 4px 20px rgba(0, 0, 0, 0.45), inset 0 1px 0 rgba(255,255,255,0.06);
  }
  .hud-label {
    font-size: 9px;
    letter-spacing: 2px;
    color: #7aa8ff;
    font-weight: 700;
    margin-bottom: 1px;
  }
  .hud-value {
    font-size: 19px;
    font-weight: 800;
    color: #fff;
    font-variant-numeric: tabular-nums;
    text-shadow: 0 0 14px rgba(100, 180, 255, 0.55);
    line-height: 1.1;
  }
  .hud-value small {
    font-size: 9px;
    color: #8899bb;
    margin-inline-start: 3px;
    font-weight: 700;
    letter-spacing: 1px;
  }

  /* ===== Touch controls ===== */
  #controls {
    position: fixed;
    bottom: 0; left: 0; right: 0;
    padding: 0 18px max(20px, env(safe-area-inset-bottom));
    display: flex;
    justify-content: space-between;
    align-items: flex-end;
    z-index: 10;
    pointer-events: none;
  }
  .ctrl-btn {
    width: 88px; height: 88px;
    border-radius: 50%;
    background: rgba(24, 34, 68, 0.42);
    border: 2px solid rgba(120, 180, 255, 0.32);
    color: #cfe4ff;
    font-size: 34px;
    line-height: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    pointer-events: auto;
    cursor: pointer;
    transition: transform 0.08s ease, background 0.15s ease, border-color 0.15s ease;
    box-shadow: 0 6px 24px rgba(0,0,0,0.45), inset 0 1px 0 rgba(255,255,255,0.1);
    user-select: none;
    -webkit-user-select: none;
    touch-action: none;
    -webkit-appearance: none;
    appearance: none;
    outline: none;
    font-family: inherit;
    padding-bottom: 4px;
  }
  .ctrl-btn:active, .ctrl-btn.pressed {
    background: rgba(80, 140, 255, 0.55);
    transform: scale(0.93);
    border-color: rgba(170, 215, 255, 0.9);
    color: #fff;
  }
  @media (max-height: 520px) {
    .ctrl-btn { width: 72px; height: 72px; font-size: 28px; }
  }

  /* ===== Overlays ===== */
  .overlay {
    position: fixed;
    inset: 0;
    z-index: 20;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    background: radial-gradient(ellipse at center, rgba(14,20,50,0.88) 0%, rgba(2,3,12,0.98) 100%);
    backdrop-filter: blur(7px);
    -webkit-backdrop-filter: blur(7px);
    gap: 18px;
    padding: 24px;
    text-align: center;
    transition: opacity 0.3s ease;
  }
  .overlay.hidden { display: none; }

  .game-title {
    font-size: clamp(34px, 10vw, 76px);
    font-weight: 900;
    letter-spacing: 5px;
    line-height: 1;
    background: linear-gradient(180deg, #8fd4ff 0%, #3a7bff 48%, #b44bff 100%);
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
    filter: drop-shadow(0 0 30px rgba(80, 140, 255, 0.55));
    margin-bottom: 4px;
  }
  .subtitle {
    font-size: 12px;
    letter-spacing: 5px;
    color: #7d92bb;
    font-weight: 600;
    text-transform: uppercase;
    margin-bottom: 6px;
  }

  .go-title {
    font-size: clamp(38px, 11vw, 78px);
    font-weight: 900;
    letter-spacing: 4px;
    color: #ff4d6d;
    text-shadow: 0 0 30px rgba(255, 60, 100, 0.6), 0 0 60px rgba(255, 60, 100, 0.3);
    margin-bottom: 4px;
  }

  .stat-card {
    display: flex;
    flex-direction: column;
    gap: 12px;
    background: rgba(14, 20, 44, 0.6);
    border: 1px solid rgba(120, 180, 255, 0.2);
    border-radius: 16px;
    padding: 20px 34px;
    box-shadow: inset 0 1px 0 rgba(255,255,255,0.06), 0 10px 40px rgba(0,0,0,0.5);
  }
  .stat-row {
    display: flex;
    justify-content: space-between;
    gap: 26px;
    align-items: baseline;
  }
  .stat-key {
    font-size: 12px;
    letter-spacing: 2px;
    color: #7aa8ff;
    font-weight: 700;
  }
  .stat-val {
    font-size: 24px;
    font-weight: 900;
    color: #fff;
    font-variant-numeric: tabular-nums;
    text-shadow: 0 0 14px rgba(120, 190, 255, 0.5);
  }
  .stat-val.gold { color: #ffd24a; text-shadow: 0 0 14px rgba(255, 210, 74, 0.6); }

  .primary-btn {
    padding: 16px 46px;
    font-size: 18px;
    font-weight: 800;
    letter-spacing: 2.5px;
    color: #fff;
    background: linear-gradient(135deg, #3a7bff 0%, #8b3aff 100%);
    border: none;
    border-radius: 14px;
    cursor: pointer;
    box-shadow: 0 10px 34px rgba(80, 100, 255, 0.5), inset 0 1px 0 rgba(255,255,255,0.28);
    transition: transform 0.12s ease, box-shadow 0.2s ease, filter 0.2s;
    font-family: inherit;
    text-transform: uppercase;
    -webkit-appearance: none;
    appearance: none;
    outline: none;
    touch-action: manipulation;
  }
  .primary-btn:hover { filter: brightness(1.1); }
  .primary-btn:active {
    transform: scale(0.95);
    box-shadow: 0 5px 18px rgba(80, 100, 255, 0.55), inset 0 1px 0 rgba(255,255,255,0.28);
  }

  .hint {
    font-size: 12px;
    color: #6b7fa5;
    letter-spacing: 1px;
    line-height: 1.9;
    margin-top: 6px;
    max-width: 420px;
  }
  .hint b { color: #9ab8e8; font-weight: 700; }

  #errorBox {
    position: fixed; inset: 0;
    z-index: 99;
    display: none;
    align-items: center;
    justify-content: center;
    background: #05050f;
    color: #ff6b6b;
    font-size: 16px;
    padding: 30px;
    text-align: center;
    line-height: 1.8;
    font-weight: 600;
  }
</style>
</head>
<body>

<div id="game"></div>
<div id="speedOverlay"></div>

<!-- HUD -->
<div id="hud">
  <div class="hud-panel">
    <div class="hud-label">SPEED</div>
    <div class="hud-value"><span id="speedVal">0</span><small>KM/H</small></div>
  </div>
  <div class="hud-panel">
    <div class="hud-label">DISTANCE</div>
    <div class="hud-value"><span id="distVal">0</span><small>M</small></div>
  </div>
  <div class="hud-panel">
    <div class="hud-label">BEST</div>
    <div class="hud-value"><span id="bestVal">0</span><small>M</small></div>
  </div>
</div>

<!-- Touch controls -->
<div id="controls">
  <button class="ctrl-btn" id="btnLeft" aria-label="يسار">◀</button>
  <button class="ctrl-btn" id="btnRight" aria-label="يمين">▶</button>
</div>

<!-- Start screen -->
<div id="startScreen" class="overlay">
  <div class="game-title">3D ROAD RUSH</div>
  <div class="subtitle">Endless Highway Racing</div>
  <button id="startBtn" class="primary-btn">START GAME</button>
  <div class="hint">
    <b>A / D</b> أو <b>← →</b> لتغيير المسار &nbsp;·&nbsp; <b>Space</b> للفرامل<br>
    على الهاتف استخدم أزرار <b>◀ ▶</b> أسفل الشاشة
  </div>
</div>

<!-- Game over screen -->
<div id="gameOverScreen" class="overlay hidden">
  <div class="go-title">GAME OVER</div>
  <div class="stat-card">
    <div class="stat-row">
      <span class="stat-key">DISTANCE</span>
      <span class="stat-val"><span id="goDist">0</span> M</span>
    </div>
    <div class="stat-row">
      <span class="stat-key">BEST</span>
      <span class="stat-val gold"><span id="goBest">0</span> M</span>
    </div>
  </div>
  <button id="restartBtn" class="primary-btn">PLAY AGAIN</button>
</div>

<div id="errorBox">
  ⚠️ تعذّر تحميل مكتبة Three.js.<br>
  تأكد من اتصالك بالإنترنت ثم أعد تحميل الصفحة.
</div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
(function () {
  'use strict';

  /* =========================================================
     التحقق من تحميل Three.js
     ========================================================= */
  if (typeof THREE === 'undefined') {
    document.getElementById('errorBox').style.display = 'flex';
    return;
  }

  /* =========================================================
     الثوابت
     ========================================================= */
  const LANES          = [-3, 0, 3];   // مواضع المسارات على المحور X
  const BASE_SPEED     = 11;           // م/ث  ≈ 40 كم/س
  const MAX_SPEED      = 60;           // م/ث  ≈ 216 كم/س
  const SPEED_GROWTH   = 0.018;        // زيادة السرعة لكل متر
  const ROAD_LENGTH    = 420;
  const ROAD_WIDTH     = 11.4;
  const DASH_SPACING   = 8;
  const DASH_PER_LANE  = 46;
  const DASH_TOTAL     = DASH_SPACING * DASH_PER_LANE;
  const PILLAR_SPACING = 24;
  const PILLAR_PAIRS   = 16;
  const PILLAR_TOTAL   = PILLAR_SPACING * PILLAR_PAIRS;
  const SPAWN_Z        = -175;
  const DESPAWN_Z      = 16;
  const FOG_COLOR      = 0x1a0f33;

  const NPC_COLORS = [
    0xff3b30, 0xffcc00, 0x34c759, 0x007aff,
    0xff9500, 0xaf52de, 0xff2d55, 0x5ac8fa,
    0x00d9a3, 0xff7a00
  ];

  /* =========================================================
     حالة اللعبة
     ========================================================= */
  const state = {
    running: false,
    started: false,
    playerLane: 1,
    playerX: 0,
    speed: BASE_SPEED,
    displaySpeed: 0,
    distance: 0,
    best: 0,
    lastSpawnTimer: 1.4,
    elapsed: 0,
    braking: false,
    shakeX: 0,
    shakeY: 0
  };

  try {
    const stored = parseInt(localStorage.getItem('roadRushBest'), 10);
    if (!isNaN(stored) && stored > 0) state.best = stored;
  } catch (e) { /* ignore */ }

  /* =========================================================
     المشهد والكاميرا والعارض
     ========================================================= */
  const container = document.getElementById('game');

  const scene = new THREE.Scene();
  scene.fog = new THREE.Fog(FOG_COLOR, 55, 190);

  const camera = new THREE.PerspectiveCamera(68, window.innerWidth / window.innerHeight, 0.1, 600);
  camera.position.set(0, 4.1, 8.2);
  camera.lookAt(0, 0.9, -14);

  let renderer;
  try {
    renderer = new THREE.WebGLRenderer({
      antialias: window.devicePixelRatio < 2,
      powerPreference: 'high-performance',
      alpha: false
    });
  } catch (e) {
    document.getElementById('errorBox').style.display = 'flex';
    document.getElementById('errorBox').innerHTML = '⚠️ متصفحك لا يدعم WebGL.';
    return;
  }
  renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.outputEncoding = THREE.sRGBEncoding;
  renderer.toneMapping = THREE.ACESFilmicToneMapping;
  renderer.toneMappingExposure = 1.15;
  container.appendChild(renderer.domElement);

  /* =========================================================
     الإضاءة
     ========================================================= */
  scene.add(new THREE.AmbientLight(0x6a7ba8, 0.85));

  const hemi = new THREE.HemisphereLight(0x8099ff, 0x221133, 0.7);
  scene.add(hemi);

  const dirLight = new THREE.DirectionalLight(0xffe0b0, 1.25);
  dirLight.position.set(-14, 26, -10);
  dirLight.target.position.set(0, 0, -30);
  scene.add(dirLight);
  scene.add(dirLight.target);

  const rimLight = new THREE.DirectionalLight(0x7a5cff, 0.55);
  rimLight.position.set(12, 8, 20);
  scene.add(rimLight);

  /* =========================================================
     السماء (تدرّج لوني عبر Shader)
     ========================================================= */
  (function createSky() {
    const geo = new THREE.SphereGeometry(420, 24, 16);
    const mat = new THREE.ShaderMaterial({
      side: THREE.BackSide,
      depthWrite: false,
      uniforms: {
        topColor:    { value: new THREE.Color(0x04040f) },
        midColor:    { value: new THREE.Color(0x1a0f33) },
        bottomColor: { value: new THREE.Color(0x2e1a52) }
      },
      vertexShader: `
        varying vec3 vWorldPos;
        void main() {
          vec4 wp = modelMatrix * vec4(position, 1.0);
          vWorldPos = wp.xyz;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
        }
      `,
      fragmentShader: `
        uniform vec3 topColor;
        uniform vec3 midColor;
        uniform vec3 bottomColor;
        varying vec3 vWorldPos;
        void main() {
          float h = normalize(vWorldPos).y;
          vec3 col;
          if (h > 0.0) {
            col = mix(midColor, topColor, clamp(h * 1.6, 0.0, 1.0));
          } else {
            col = mix(midColor, bottomColor, clamp(-h * 2.2, 0.0, 1.0));
          }
          gl_FragColor = vec4(col, 1.0);
        }
      `
    });
    const sky = new THREE.Mesh(geo, mat);
    sky.frustumCulled = false;
    scene.add(sky);
  })();

  /* =========================================================
     الأرضية
     ========================================================= */
  (function createGround() {
    const geo = new THREE.PlaneGeometry(520, 620);
    const mat = new THREE.MeshStandardMaterial({
      color: 0x160b28,
      roughness: 1.0,
      metalness: 0.0
    });
    const ground = new THREE.Mesh(geo, mat);
    ground.rotation.x = -Math.PI / 2;
    ground.position.set(0, -0.08, -200);
    scene.add(ground);
  })();

  /* =========================================================
     الطريق
     ========================================================= */
  (function createRoad() {
    const geo = new THREE.PlaneGeometry(ROAD_WIDTH, ROAD_LENGTH);
    const mat = new THREE.MeshStandardMaterial({
      color: 0x181822,
      roughness: 0.85,
      metalness: 0.15
    });
    const road = new THREE.Mesh(geo, mat);
    road.rotation.x = -Math.PI / 2;
    road.position.set(0, 0, -ROAD_LENGTH / 2 + 30);
    scene.add(road);

    // حواف الطريق المتوهجة
    const edgeMat = new THREE.MeshBasicMaterial({ color: 0x2f6fff });
    const edgeGeo = new THREE.PlaneGeometry(0.14, ROAD_LENGTH);
    [-ROAD_WIDTH / 2 + 0.1, ROAD_WIDTH / 2 - 0.1].forEach(x => {
      const edge = new THREE.Mesh(edgeGeo, edgeMat);
      edge.rotation.x = -Math.PI / 2;
      edge.position.set(x, 0.012, -ROAD_LENGTH / 2 + 30);
      scene.add(edge);
    });
  })();

  /* =========================================================
     خطوط الطريق (Dashes) — مع إعادة تدوير
     ========================================================= */
  const dashGeo = new THREE.PlaneGeometry(0.22, 3.4);
  const dashMat = new THREE.MeshBasicMaterial({ color: 0xffffff });
  const dashes = [];

  for (let i = 0; i < DASH_PER_LANE; i++) {
    for (let l = 0; l < 2; l++) {
      const dash = new THREE.Mesh(dashGeo, dashMat);
      dash.rotation.x = -Math.PI / 2;
      const x = l === 0 ? -1.5 : 1.5;
      const z = -i * DASH_SPACING;
      dash.position.set(x, 0.015, z);
      dash.userData.initialZ = z;
      scene.add(dash);
      dashes.push(dash);
    }
  }

  /* =========================================================
     أعمدة الإضاءة الجانبية
     ========================================================= */
  const pillars = [];
  (function createPillars() {
    const geo = new THREE.BoxGeometry(0.18, 2.6, 0.18);
    const capGeo = new THREE.BoxGeometry(0.34, 0.34, 0.34);

    for (let i = 0; i < PILLAR_PAIRS; i++) {
      const z = -i * PILLAR_SPACING;
      for (let s = 0; s < 2; s++) {
        const side = s === 0 ? -1 : 1;
        const x = side * (ROAD_WIDTH / 2 + 0.85);

        const pillar = new THREE.Mesh(geo, new THREE.MeshStandardMaterial({
          color: 0x22263a, roughness: 0.6, metalness: 0.5
        }));
        pillar.position.set(x, 1.3, z);
        pillar.userData.initialZ = z;

        const capMat = new THREE.MeshBasicMaterial({
          color: s === 0 ? 0x00e5ff : 0xff2fb9
        });
        const cap = new THREE.Mesh(capGeo, capMat);
        cap.position.set(x, 2.75, z);
        cap.userData.isCap = true;

        scene.add(pillar);
        scene.add(cap);
        pillars.push({ pillar, cap, initialZ: z });
      }
    }
  })();

  /* =========================================================
     بناء السيارة
     ========================================================= */
  const SHARED = {
    bodyGeo:     new THREE.BoxGeometry(1.78, 0.52, 3.9),
    chassisGeo:  new THREE.BoxGeometry(1.9, 0.26, 4.05),
    cabinGeo:    new THREE.BoxGeometry(1.42, 0.58, 1.95),
    roofGeo:     new THREE.BoxGeometry(1.36, 0.14, 1.85),
    wheelGeo:    new THREE.CylinderGeometry(0.42, 0.42, 0.34, 14),
    rimGeo:      new THREE.CylinderGeometry(0.2, 0.2, 0.36, 10),
    headGeo:     new THREE.BoxGeometry(0.34, 0.16, 0.1),
    tailGeo:     new THREE.BoxGeometry(0.42, 0.14, 0.1),
    chassisMat:  new THREE.MeshStandardMaterial({ color: 0x141418, roughness: 0.95, metalness: 0.15 }),
    glassMat:    new THREE.MeshStandardMaterial({ color: 0x0a0d18, roughness: 0.12, metalness: 0.85 }),
    wheelMat:    new THREE.MeshStandardMaterial({ color: 0x0c0c0e, roughness: 1.0 }),
    rimMat:      new THREE.MeshStandardMaterial({ color: 0x9aa4bb, roughness: 0.28, metalness: 0.95 }),
    headMat:     new THREE.MeshBasicMaterial({ color: 0xfff6cc }),
    tailMat:     new THREE.MeshBasicMaterial({ color: 0xff1f2e })
  };

  const shadowGeo = new THREE.CircleGeometry(1.35, 14);
  const shadowMat = new THREE.MeshBasicMaterial({
    color: 0x000000, transparent: true, opacity: 0.42, depthWrite: false
  });

  /**
   * ينشئ مجسّم سيارة.
   * @param {number} color اللون الأساسي
   * @param {boolean} detailed هل نضيف تفاصيل إضافية (للسيارة اللاعبة)
   */
  function createCarMesh(color, detailed) {
    const car = new THREE.Group();

    const bodyMat = new THREE.MeshStandardMaterial({
      color: color, roughness: 0.32, metalness: 0.72
    });

    // الهيكل السفلي
    const chassis = new THREE.Mesh(SHARED.chassisGeo, SHARED.chassisMat);
    chassis.position.y = 0.34;
    car.add(chassis);

    // الجسم الرئيسي
    const body = new THREE.Mesh(SHARED.bodyGeo, bodyMat);
    body.position.y = 0.66;
    car.add(body);

    // الكابينة
    const cabin = new THREE.Mesh(SHARED.cabinGeo, SHARED.glassMat);
    cabin.position.set(0, 1.18, 0.12);
    car.add(cabin);

    // السقف
    const roofMat = new THREE.MeshStandardMaterial({
      color: color, roughness: 0.32, metalness: 0.72
    });
    const roof = new THREE.Mesh(SHARED.roofGeo, roofMat);
    roof.position.set(0, 1.48, 0.12);
    car.add(roof);

    // العجلات
    const wheelPositions = [
      [-0.93, 0.42, 1.32], [0.93, 0.42, 1.32],
      [-0.93, 0.42, -1.32], [0.93, 0.42, -1.32]
    ];
    for (let i = 0; i < wheelPositions.length; i++) {
      const p = wheelPositions[i];
      const wheel = new THREE.Mesh(SHARED.wheelGeo, SHARED.wheelMat);
      wheel.rotation.z = Math.PI / 2;
      wheel.position.set(p[0], p[1], p[2]);
      car.add(wheel);

      const rim = new THREE.Mesh(SHARED.rimGeo, SHARED.rimMat);
      rim.rotation.z = Math.PI / 2;
      rim.position.set(p[0], p[1], p[2]);
      car.add(rim);
    }

    // المصابيح الخلفية (تظهر دائماً لأننا نرى السيارة من الخلف)
    for (let i = 0; i < 2; i++) {
      const t = new THREE.Mesh(SHARED.tailGeo, SHARED.tailMat);
      t.position.set(i === 0 ? -0.58 : 0.58, 0.78, 1.98);
      car.add(t);
    }

    // المصابيح الأمامية للسيارة اللاعبة فقط
    if (detailed) {
      for (let i = 0; i < 2; i++) {
        const h = new THREE.Mesh(SHARED.headGeo, SHARED.headMat);
        h.position.set(i === 0 ? -0.55 : 0.55, 0.72, -1.98);
        car.add(h);
      }
      // شريط أمامي مضيء
      const stripMat = new THREE.MeshBasicMaterial({ color: 0x3affff });
      const strip = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.05, 0.06), stripMat);
      strip.position.set(0, 0.5, -1.99);
      car.add(strip);
    }

    // ظل وهمي
    const shadow = new THREE.Mesh(shadowGeo, shadowMat);
    shadow.rotation.x = -Math.PI / 2;
    shadow.position.y = 0.025;
    car.add(shadow);

    car.userData.bodyMat = bodyMat;
    car.userData.roofMat = roofMat;

    return car;
  }

  /* =========================================================
     السيارة اللاعبة
     ========================================================= */
  const playerCar = createCarMesh(0x2f7bff, true);
  playerCar.position.set(0, 0, 0);
  scene.add(playerCar);

  // هالة ضوئية أسفل السيارة اللاعبة
  const glowLight = new THREE.PointLight(0x3a7bff, 0.9, 10, 2);
  glowLight.position.set(0, 0.4, 0);
  playerCar.add(glowLight);

  /* =========================================================
     نظام تجميع السيارات (Object Pooling)
     ========================================================= */
  const npcPool = [];
  const activeNPCs = [];

  function acquireNPC() {
    let npc = npcPool.pop();
    if (!npc) {
      npc = createCarMesh(0xff3b30, false);
      npc.visible = false;
      scene.add(npc);
    }
    return npc;
  }

  function releaseNPC(npc) {
    npc.visible = false;
    npcPool.push(npc);
  }

  function spawnNPC(laneIndex, color) {
    const npc = acquireNPC();
    npc.position.set(LANES[laneIndex], 0, SPAWN_Z);
    npc.rotation.set(0, 0, 0);
    if (npc.userData.bodyMat) npc.userData.bodyMat.color.setHex(color);
    if (npc.userData.roofMat) npc.userData.roofMat.color.setHex(color);
    npc.visible = true;
    activeNPCs.push(npc);
  }

  /* =========================================================
     الصوت
     ========================================================= */
  let audioCtx = null;
  let engineNodes = null;

  function initAudio() {
    if (audioCtx) {
      if (audioCtx.state === 'suspended') audioCtx.resume();
      return;
    }
    try {
      const Ctx = window.AudioContext || window.webkitAudioContext;
      if (!Ctx) return;
      audioCtx = new Ctx();

      const osc1 = audioCtx.createOscillator();
      osc1.type = 'sawtooth';
      osc1.frequency.value = 70;

      const osc2 = audioCtx.createOscillator();
      osc2.type = 'square';
      osc2.frequency.value = 35;

      const filter = audioCtx.createBiquadFilter();
      filter.type = 'lowpass';
      filter.frequency.value = 320;
      filter.Q.value = 3;

      const gain = audioCtx.createGain();
      gain.gain.value = 0.0;

      osc1.connect(filter);
      osc2.connect(filter);
      filter.connect(gain);
      gain.connect(audioCtx.destination);

      osc1.start();
      osc2.start();

      engineNodes = { osc1, osc2, filter, gain };
    } catch (e) {
      audioCtx = null;
      engineNodes = null;
    }
  }

  function updateEngineSound() {
    if (!audioCtx || !engineNodes) return;
    const t = audioCtx.currentTime;
    const ratio = Math.min(state.displaySpeed / (MAX_SPEED * 3.6), 1);
    const base = 58 + ratio * 170;
    try {
      engineNodes.osc1.frequency.setTargetAtTime(base, t, 0.12);
      engineNodes.osc2.frequency.setTargetAtTime(base * 0.5, t, 0.12);
      engineNodes.filter.frequency.setTargetAtTime(260 + ratio * 780, t, 0.15);
      engineNodes.gain.gain.setTargetAtTime(
        state.running ? (0.025 + ratio * 0.055) : 0.0,
        t, 0.2
      );
    } catch (e) { /* ignore */ }
  }

  function stopEngineSound() {
    if (!audioCtx || !engineNodes) return;
    try {
      engineNodes.gain.gain.setTargetAtTime(0.0, audioCtx.currentTime, 0.12);
    } catch (e) { /* ignore */ }
  }

  function playCrashSound() {
    if (!audioCtx) return;
    try {
      const dur = 0.55;
      const len = Math.floor(audioCtx.sampleRate * dur);
      const buffer = audioCtx.createBuffer(1, len, audioCtx.sampleRate);
      const data = buffer.getChannelData(0);
      for (let i = 0; i < len; i++) {
        const env = Math.pow(1 - i / len, 2.2);
        data[i] = (Math.random() * 2 - 1) * env;
      }
      const src = audioCtx.createBufferSource();
      src.buffer = buffer;

      const filter = audioCtx.createBiquadFilter();
      filter.type = 'lowpass';
      filter.frequency.value = 1400;
      filter.Q.value = 1.2;

      const gain = audioCtx.createGain();
      gain.gain.value = 0.55;

      src.connect(filter);
      filter.connect(gain);
      gain.connect(audioCtx.destination);
      src.start();

      // نغمة هابطة
      const osc = audioCtx.createOscillator();
      osc.type = 'sawtooth';
      const g2 = audioCtx.createGain();
      osc.frequency.setValueAtTime(220, audioCtx.currentTime);
      osc.frequency.exponentialRampToValueAtTime(45, audioCtx.currentTime + 0.4);
      g2.gain.setValueAtTime(0.18, audioCtx.currentTime);
      g2.gain.exponentialRampToValueAtTime(0.001, audioCtx.currentTime + 0.45);
      osc.connect(g2);
      g2.connect(audioCtx.destination);
      osc.start();
      osc.stop(audioCtx.currentTime + 0.5);
    } catch (e) { /* ignore */ }
  }

  /* =========================================================
     عناصر واجهة المستخدم
     ========================================================= */
  const ui = {
    speedVal:      document.getElementById('speedVal'),
    distVal:       document.getElementById('distVal'),
    bestVal:       document.getElementById('bestVal'),
    startScreen:   document.getElementById('startScreen'),
    gameOver:      document.getElementById('gameOverScreen'),
    goDist:        document.getElementById('goDist'),
    goBest:        document.getElementById('goBest'),
    startBtn:      document.getElementById('startBtn'),
    restartBtn:    document.getElementById('restartBtn'),
    btnLeft:       document.getElementById('btnLeft'),
    btnRight:      document.getElementById('btnRight'),
    speedOverlay:  document.getElementById('speedOverlay')
  };

  ui.bestVal.textContent = Math.floor(state.best);

  /* =========================================================
     أدوات مساعدة
     ========================================================= */
  function clamp(v, a, b) { return v < a ? a : (v > b ? b : v); }

  function lerp(a, b, t) { return a + (b - a) * t; }

  function getLevel() {
    return Math.floor(state.distance / 380) + 1;
  }

  /* =========================================================
     التحكم بالمسارات
     ========================================================= */
  function moveLane(dir) {
    if (!state.running) return;
    const next = state.playerLane + dir;
    if (next < 0 || next > 2) return;
    state.playerLane = next;
  }

  /* أزرار اللمس مع الاستمرار */
  function bindHoldButton(el, onDown, onUp) {
    let active = false;

    function down(e) {
      if (e) e.preventDefault();
      if (active) return;
      active = true;
      el.classList.add('pressed');
      onDown();
    }
    function up(e) {
      if (e) e.preventDefault();
      if (!active) return;
      active = false;
      el.classList.remove('pressed');
      if (onUp) onUp();
    }

    el.addEventListener('pointerdown', function (e) {
      try { el.setPointerCapture(e.pointerId); } catch (err) { /* ignore */ }
      down(e);
    });
    el.addEventListener('pointerup', up);
    el.addEventListener('pointercancel', up);
    el.addEventListener('pointerleave', up);
    el.addEventListener('contextmenu', function (e) { e.preventDefault(); });
  }

  let leftRepeat = null;
  let rightRepeat = null;
  const REPEAT_DELAY = 260;

  bindHoldButton(
    ui.btnLeft,
    function () {
      moveLane(-1);
      if (leftRepeat) clearInterval(leftRepeat);
      leftRepeat = setInterval(function () { moveLane(-1); }, REPEAT_DELAY);
    },
    function () {
      if (leftRepeat) { clearInterval(leftRepeat); leftRepeat = null; }
    }
  );

  bindHoldButton(
    ui.btnRight,
    function () {
      moveLane(1);
      if (rightRepeat) clearInterval(rightRepeat);
      rightRepeat = setInterval(function () { moveLane(1); }, REPEAT_DELAY);
    },
    function () {
      if (rightRepeat) { clearInterval(rightRepeat); rightRepeat = null; }
    }
  );

  /* =========================================================
     لوحة المفاتيح
     ========================================================= */
  window.addEventListener('keydown', function (e) {
    const k = e.key;

    if (k === 'ArrowLeft' || k === 'a' || k === 'A') {
      e.preventDefault();
      if (!e.repeat) moveLane(-1);
    } else if (k === 'ArrowRight' || k === 'd' || k === 'D') {
      e.preventDefault();
      if (!e.repeat) moveLane(1);
    } else if (k === ' ' || k === 'Spacebar') {
      e.preventDefault();
      state.braking = true;
    } else if (k === 'Enter') {
      if (!state.running) {
        if (!ui.startScreen.classList.contains('hidden')) startGame();
        else if (!ui.gameOver.classList.contains('hidden')) startGame();
      }
    }
  }, { passive: false });

  window.addEventListener('keyup', function (e) {
    const k = e.key;
    if (k === ' ' || k === 'Spacebar') {
      state.braking = false;
    }
  });

  /* =========================================================
     توليد حركة المرور
     ========================================================= */
  function spawnTraffic() {
    const level = getLevel();

    // تحديد المسارات المشغولة في منطقة الظهور
    const blocked = [false, false, false];
    for (let i = 0; i < activeNPCs.length; i++) {
      const npc = activeNPCs[i];
      if (npc.position.z > SPAWN_Z - 42 && npc.position.z < SPAWN_Z + 48) {
        const lane = LANES.indexOf(npc.position.x);
        if (lane >= 0) blocked[lane] = true;
      }
    }

    const free = [];
    for (let i = 0; i < 3; i++) if (!blocked[i]) free.push(i);
    if (free.length <= 1) return; // لا تحجب كل المسارات

    // أقصى عدد سيارات في المرة الواحدة
    let maxSpawn = Math.min(free.length - 1, 1 + Math.floor(level / 3), 2);
    if (maxSpawn <= 0) return;

    let count = 1 + (Math.random() < 0.38 ? 1 : 0);
    count = Math.min(count, maxSpawn);

    // خلط المسارات الحرة
    for (let i = free.length - 1; i > 0; i--) {
      const j = Math.floor(Math.random() * (i + 1));
      const tmp = free[i]; free[i] = free[j]; free[j] = tmp;
    }

    for (let i = 0; i < count; i++) {
      const color = NPC_COLORS[(Math.random() * NPC_COLORS.length) | 0];
      spawnNPC(free[i], color);
    }
  }

  /* =========================================================
     كشف التصادم
     ========================================================= */
  const PLAYER_HALF_W = 0.78;
  const PLAYER_HALF_L = 1.75;
  const NPC_HALF_W    = 0.86;
  const NPC_HALF_L    = 1.9;

  function checkCollision() {
    const px = playerCar.position.x;
    const pz = playerCar.position.z;

    for (let i = 0; i < activeNPCs.length; i++) {
      const npc = activeNPCs[i];
      if (!npc.visible) continue;

      const dx = Math.abs(px - npc.position.x);
      const dz = Math.abs(pz - npc.position.z);

      if (dx < (PLAYER_HALF_W + NPC_HALF_W) && dz < (PLAYER_HALF_L + NPC_HALF_L)) {
        return true;
      }
    }
    return false;
  }

  /* =========================================================
     تحديث الكاميرا
     ========================================================= */
  function updateCamera(dt) {
    const ratio = clamp(state.displaySpeed / (MAX_SPEED * 3.6), 0, 1);

    // FOV يتغير مع السرعة
    const targetFov = 66 + ratio * 14;
    camera.fov += (targetFov - camera.fov) * Math.min(dt * 2.2, 1);
    camera.updateProjectionMatrix();

    // متابعة سلسة لمحور X
    const targetCamX = playerCar.position.x * 0.72;
    camera.position.x += (targetCamX - camera.position.x) * Math.min(dt * 4.2, 1);

    // ارتفاع وزاوية خلفية
    const targetY = 4.05 - ratio * 0.35;
    camera.position.y += (targetY - camera.position.y) * Math.min(dt * 2.4, 1);
    camera.position.z = 8.2 + ratio * 0.7;

    // اهتزاز الكاميرا
    const shake = ratio * ratio * 0.075;
    state.shakeX = (Math.random() - 0.5) * shake;
    state.shakeY = (Math.random() - 0.5) * shake;

    const lookX = playerCar.position.x * 0.5 + state.shakeX * 0.5;
    camera.position.x += state.shakeX * 0.35;
    camera.position.y += state.shakeY * 0.35;

    camera.lookAt(lookX, 0.85 - ratio * 0.1, -14);
  }

  /* =========================================================
     تحديث واجهة HUD
     ========================================================= */
  let hudAccum = 0;
  function updateHUD(dt) {
    hudAccum += dt;
    if (hudAccum < 0.06) return;
    hudAccum = 0;

    ui.speedVal.textContent = Math.round(state.displaySpeed);
    ui.distVal.textContent = Math.floor(state.distance);
    ui.bestVal.textContent = Math.floor(state.best);

    // تأثير Vignette مع السرعة
    const ratio = clamp(state.displaySpeed / (MAX_SPEED * 3.6), 0, 1);
    ui.speedOverlay.style.opacity = (0.12 + ratio * 0.5).toFixed(3);
  }

  /* =========================================================
     التحديث الرئيسي للعبة
     ========================================================= */
  function update(dt) {
    state.elapsed += dt;

    /* --- السرعة --- */
    if (state.braking) {
      state.speed = Math.max(7, state.speed - 34 * dt);
    } else {
      const targetSpeed = Math.min(MAX_SPEED, BASE_SPEED + state.distance * SPEED_GROWTH);
      state.speed += (targetSpeed - state.speed) * Math.min(dt * 0.9, 1);
    }

    /* --- المسافة --- */
    state.distance += state.speed * dt;

    /* --- عرض السرعة بالكم/س --- */
    const targetDisplay = state.speed * 3.6;
    state.displaySpeed += (targetDisplay - state.displaySpeed) * Math.min(dt * 6, 1);

    /* --- حركة السيارة بين المسارات --- */
    const targetX = LANES[state.playerLane];
    const diffX = targetX - state.playerX;

    state.playerX += diffX * Math.min(dt * 7.5, 1);
    playerCar.position.x = state.playerX;

    // ميلان خفيف أثناء الانتقال
    const roll = clamp(-diffX * 0.11, -0.16, 0.16);
    const yaw  = clamp(diffX * 0.055, -0.09, 0.09);
    playerCar.rotation.z += (roll - playerCar.rotation.z) * Math.min(dt * 9, 1);
    playerCar.rotation.y += (yaw - playerCar.rotation.y) * Math.min(dt * 9, 1);

    // اهتزاز بسيط للسيارة عند السرعات العالية
    const vib = clamp(state.displaySpeed / (MAX_SPEED * 3.6), 0, 1) * 0.012;
    playerCar.position.y = Math.sin(state.elapsed * 42) * vib;

    /* --- تحريك خطوط الطريق --- */
    const moveZ = state.speed * dt;
    for (let i = 0; i < dashes.length; i++) {
      const d = dashes[i];
      d.position.z += moveZ;
      if (d.position.z > DESPAWN_Z) {
        d.position.z -= DASH_TOTAL;
      }
    }

    /* --- تحريك الأعمدة الجانبية --- */
    for (let i = 0; i < pillars.length; i++) {
      const p = pillars[i];
      p.pillar.position.z += moveZ;
      p.cap.position.z += moveZ;
      if (p.pillar.position.z > DESPAWN_Z) {
        p.pillar.position.z -= PILLAR_TOTAL;
        p.cap.position.z -= PILLAR_TOTAL;
      }
    }

    /* --- تحريك سيارات المرور --- */
    for (let i = activeNPCs.length - 1; i >= 0; i--) {
      const npc = activeNPCs[i];
      npc.position.z += moveZ;
      if (npc.position.z > DESPAWN_Z) {
        releaseNPC(npc);
        activeNPCs.splice(i, 1);
      }
    }

    /* --- توليد سيارات جديدة --- */
    state.lastSpawnTimer -= dt;
    if (state.lastSpawnTimer <= 0) {
      spawnTraffic();
      const level = getLevel();
      const base = Math.max(0.42, 1.55 - level * 0.11);
      state.lastSpawnTimer = base + Math.random() * 0.45;
    }

    /* --- التصادم --- */
    if (checkCollision()) {
      triggerGameOver();
      return;
    }

    /* --- الكاميرا والصوت والواجهة --- */
    updateCamera(dt);
    updateHUD(dt);
    updateEngineSound();
  }

  /* =========================================================
     بدء / إعادة تشغيل / نهاية اللعبة
     ========================================================= */
  function resetWorld() {
    // إعادة كل سيارات المرور
    for (let i = activeNPCs.length - 1; i >= 0; i--) {
      releaseNPC(activeNPCs[i]);
    }
    activeNPCs.length = 0;

    // إعادة خطوط الطريق
    for (let i = 0; i < dashes.length; i++) {
      dashes[i].position.z = dashes[i].userData.initialZ;
    }

    // إعادة الأعمدة
    for (let i = 0; i < pillars.length; i++) {
      pillars[i].pillar.position.z = pillars[i].initialZ;
      pillars[i].cap.position.z = pillars[i].initialZ;
    }

    // إعادة السيارة اللاعبة
    state.playerLane = 1;
    state.playerX = 0;
    state.speed = BASE_SPEED;
    state.displaySpeed = 0;
    state.distance = 0;
    state.lastSpawnTimer = 1.2;
    state.elapsed = 0;
    state.braking = false;

    playerCar.position.set(0, 0, 0);
    playerCar.rotation.set(0, 0, 0);

    camera.position.set(0, 4.1, 8.2);
    camera.fov = 68;
    camera.updateProjectionMatrix();
    camera.lookAt(0, 0.9, -14);

    ui.speedOverlay.style.opacity = '0.12';
    ui.speedVal.textContent = '0';
    ui.distVal.textContent = '0';
    ui.bestVal.textContent = Math.floor(state.best);
  }

  function startGame() {
    initAudio();

    ui.startScreen.classList.add('hidden');
    ui.gameOver.classList.add('hidden');

    resetWorld();
    state.running = true;
    state.started = true;
  }

  function triggerGameOver() {
    state.running = false;

    if (state.distance > state.best) {
      state.best = Math.floor(state.distance);
      try { localStorage.setItem('roadRushBest', String(state.best)); } catch (e) { /* ignore */ }
    }

    ui.goDist.textContent = Math.floor(state.distance);
    ui.goBest.textContent = Math.floor(state.best);
    ui.bestVal.textContent = Math.floor(state.best);

    ui.gameOver.classList.remove('hidden');

    playCrashSound();
    stopEngineSound();

    // تأثير اهتزاز سريع للكاميرا
    let shakeCount = 0;
    const shakeInterval = setInterval(function () {
      camera.position.x += (Math.random() - 0.5) * 0.5;
      camera.position.y += (Math.random() - 0.5) * 0.4;
      shakeCount++;
      if (shakeCount > 10) clearInterval(shakeInterval);
    }, 28);
  }

  /* =========================================================
     ربط الأزرار
     ========================================================= */
  ui.startBtn.addEventListener('click', function (e) {
    e.preventDefault();
    startGame();
  });

  ui.restartBtn.addEventListener('click', function (e) {
    e.preventDefault();
    startGame();
  });

  // منع السلوك الافتراضي للمس على الأزرار
  [ui.startBtn, ui.restartBtn].forEach(function (btn) {
    btn.addEventListener('touchstart', function (e) {
      e.preventDefault();
      btn.click();
    }, { passive: false });
  });

  /* =========================================================
     الاستجابة لتغيير حجم الشاشة
     ========================================================= */
  function onResize() {
    const w = window.innerWidth;
    const h = window.innerHeight;
    camera.aspect = w / h;
    camera.updateProjectionMatrix();
    renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 2));
    renderer.setSize(w, h);
  }
  window.addEventListener('resize', onResize);
  window.addEventListener('orientationchange', function () {
    setTimeout(onResize, 200);
  });

  // إيقاف الفرامل عند فقدان التركيز
  window.addEventListener('blur', function () {
    state.braking = false;
    if (leftRepeat) { clearInterval(leftRepeat); leftRepeat = null; }
    if (rightRepeat) { clearInterval(rightRepeat); rightRepeat = null; }
    ui.btnLeft.classList.remove('pressed');
    ui.btnRight.classList.remove('pressed');
  });

  /* =========================================================
     حلقة اللعبة
     ========================================================= */
  let lastTime = 0;
  let loopHandle = null;

  function loop(now) {
    loopHandle = requestAnimationFrame(loop);

    if (!lastTime) lastTime = now;
    let dt = (now - lastTime) / 1000;
    lastTime = now;

    // حماية من القفزات الكبيرة (تبديل التبويب)
    if (dt > 0.1) dt = 0.1;
    if (dt < 0) dt = 0;

    if (state.running) {
      update(dt);
    } else {
      // مشهد ساكن خلف شاشة البداية
      if (!state.started) {
        state.elapsed += dt;
        for (let i = 0; i < dashes.length; i++) {
          const d = dashes[i];
          d.position.z += 6 * dt;
          if (d.position.z > DESPAWN_Z) d.position.z -= DASH_TOTAL;
        }
        for (let i = 0; i < pillars.length; i++) {
          const p = pillars[i];
          p.pillar.position.z += 6 * dt;
          p.cap.position.z += 6 * dt;
          if (p.pillar.position.z > DESPAWN_Z) {
            p.pillar.position.z -= PILLAR_TOTAL;
            p.cap.position.z -= PILLAR_TOTAL;
          }
        }
        // دوران بطيء للسيارة في شاشة البداية
        playerCar.rotation.y = Math.sin(state.elapsed * 0.5) * 0.12;
        playerCar.position.y = Math.sin(state.elapsed * 1.6) * 0.04;
      }
    }

    renderer.render(scene, camera);
  }

  /* =========================================================
     الإقلاع
     ========================================================= */
  resetWorld();
  loopHandle = requestAnimationFrame(loop);

  // تنظيف عند إغلاق الصفحة
  window.addEventListener('beforeunload', function () {
    if (loopHandle) cancelAnimationFrame(loopHandle);
    if (leftRepeat) clearInterval(leftRepeat);
    if (rightRepeat) clearInterval(rightRepeat);
    try {
      if (renderer) {
        renderer.dispose();
        if (renderer.domElement && renderer.domElement.parentNode) {
          renderer.domElement.parentNode.removeChild(renderer.domElement);
        }
      }
    } catch (e) { /* ignore */ }
  });

})();
</script>
</body>
</html>

التعليقات (1)

Khalid Hisham · قبل 24 يومًا

الكود جميل ما شاء الله لكن في نقطة مهمه الازرار معكوسه ممكن تعدلها عند #controls في الستايل وتضيف السطر ده في الاخر direction: ltr; وبس كده وهتبقي افضل لو عملت دانا بيز تتحفظ فيها السكور بس ده هيحتاج منك api بس عاااااش

سجّل الدخول للتعليق.