<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>JS Coding Game - Ultra Advanced Template</title>
  <!-- CodeMirror CSS & JS -->
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.css">
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/codemirror.min.js"></script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/codemirror/5.65.5/mode/javascript/javascript.min.js"></script>
  <!-- Matter.js Library -->
  <script src="https://cdnjs.cloudflare.com/ajax/libs/matter-js/0.18.0/matter.min.js"></script>
  <style>
    body { font-family: Arial, sans-serif; padding: 10px; }
    #editor { width: 100%; height: 200px; }
    #runBtn, #stopBtn { padding: 8px 15px; margin-top: 5px; margin-right: 5px; cursor: pointer; }
    #output { background: black; color: lime; padding: 10px; height: 100px; overflow-y: auto; font-family: monospace; }
    #gameCanvas { border: 1px solid black; background: lightblue; display: block; margin-top: 10px; }
    #guide { background: #f4f4f4; border: 1px solid #ccc; padding: 10px; margin-bottom: 15px; }
    #status { margin-top: 10px; font-family: monospace; }
    h3, h4 { margin: 5px 0; }
    .cooldown { color: gray; }
  </style>
</head>
<body>

  <!-- How To Play Guide -->
  <div id="guide">
    <h3>How to Play</h3>
    <p>
      Write JavaScript in the editor to control the hero in a dynamic world. Define an <code>update()</code> function that runs every 100ms when you click "Run Code." Use "Stop" to pause.
    </p>
    <h4>Commands (with Cooldowns)</h4>
    <ul>
      <li><code>console.log("move 5");</code> – Sets x-velocity (no cooldown).</li>
      <li><code>console.log("jump 0.05");</code> – Jumping force (0.5s cooldown).</li>
      <li><code>console.log("attack sword");</code> – Sword attack (1s cooldown).</li>
      <li><code>console.log("attack bow");</code> – Fires arrow (2s cooldown).</li>
      <li><code>console.log("attack bomb");</code> – Throws bomb (3s cooldown).</li>
      <li><code>console.log("heal 20");</code> – Heals hero (5s cooldown, requires potion).</li>
    </ul>
    <h4>Advanced Features</h4>
    <p>Create continuous logic:</p>
    <pre style="background:#eee; padding:10px;">
function update() {
  if (hero.position.x < portal.position.x) console.log("move 5");
  if (enemies.length > 0 && hero.inventory.potions > 0 && hero.health < 50) console.log("heal 20");
}
    </pre>
    <p>Key variables:</p>
    <ul>
      <li><code>hero</code> – Hero body (<code>.health</code>, <code>.inventory</code>, <code>.abilities</code>).</li>
      <li><code>enemies</code> – Array of enemies (incl. boss).</li>
      <li><code>items</code> – Array of collectibles.</li>
      <li><code>portal</code> – Exit portal body.</li>
      <li><code>obstacles</code> – Array of hazards.</li>
      <li><code>engine</code>, <code>world</code> – Matter.js instances.</li>
    </ul>
  </div>

  <!-- Task Display -->
  <h4>Task: <span id="taskText"></span></h4>

  <!-- Game Status -->
  <div id="status">
    <p>Health: <span id="health">100</span> | Score: <span id="score">0</span> | Potions: <span id="potions">0</span></p>
    <p>Cooldowns: Jump: <span id="cdJump">Ready</span> | Sword: <span id="cdSword">Ready</span> | Bow: <span id="cdBow">Ready</span> | Bomb: <span id="cdBomb">Ready</span> | Heal: <span id="cdHeal">Ready</span></p>
  </div>

  <!-- Code Editor -->
  <textarea id="editor">
// Continuous game logic to clear first level (Collect a potion)
function update() {
  // Target the first potion at x=200
  const targetX = 200;
  
  // Move toward the potion
  if (hero.position.x < targetX - 10) {
    console.log("move 5");
  } else if (hero.position.x > targetX + 10) {
    console.log("move -5");
  } else {
    console.log("move 0"); // Stop near the potion
  }

  // Jump over obstacles if close
  obstacles.forEach(obstacle => {
    const distanceX = Math.abs(hero.position.x - obstacle.position.x);
    const distanceY = hero.position.y - obstacle.position.y;
    if (distanceX < 50 && distanceY > 0) {
      console.log("jump 0.05");
    }
  });

  // Stop once potion is collected
  if (hero.inventory.potions > 0) {
    console.log("move 0");
  }
}
  </textarea>
  <br>
  <button id="runBtn">Run Code</button>
  <button id="stopBtn">Stop</button>
  
  <!-- Console Output -->
  <h4>Output:</h4>
  <div id="output"></div>
  
  <!-- Game Canvas -->
  <canvas id="gameCanvas"></canvas>

  <script>
    // --- Setup CodeMirror Editor ---
    const editor = CodeMirror.fromTextArea(document.getElementById("editor"), {
      mode: "javascript",
      lineNumbers: true
    });

    // --- Matter.js Game Setup ---
    const Engine = Matter.Engine,
          Render = Matter.Render,
          World = Matter.World,
          Bodies = Matter.Bodies,
          Body = Matter.Body,
          Events = Matter.Events;

    const engine = Engine.create();
    const render = Render.create({
      canvas: document.getElementById("gameCanvas"),
      engine: engine,
      options: { width: 800, height: 400, wireframes: false, background: '#87CEEB' }
    });

    // Collision Categories
    const categoryHero = 0x0001;
    const categoryEnemy = 0x0002;
    const categoryProjectile = 0x0004;
    const categoryWeapon = 0x0008;
    const categoryItem = 0x0010;
    const categoryStatic = 0x0020;
    const categoryHazard = 0x0040;

    // --- Procedural Level Generation ---
    const ground = Bodies.rectangle(400, 390, 800, 20, { 
      isStatic: true, 
      render: { fillStyle: 'brown' },
      collisionFilter: { category: categoryStatic }
    });

    const obstacles = [];
    for (let i = 0; i < 5; i++) {
      const x = 150 + i * 150;
      const y = 350 - Math.random() * 100;
      obstacles.push(Bodies.rectangle(x, y, 50, 20, { 
        isStatic: true, 
        render: { fillStyle: 'gray' },
        collisionFilter: { category: categoryHazard, mask: categoryHero | categoryEnemy }
      }));
    }

    const hero = Bodies.rectangle(50, 350, 40, 40, { 
      restitution: 0.5, 
      render: { fillStyle: 'blue' },
      collisionFilter: { category: categoryHero, mask: 0xFFFF }
    });
    hero.health = 100;
    hero.inventory = { potions: 0 };
    hero.abilities = { 
      jump: { cooldown: 500, lastUsed: 0 }, 
      sword: { cooldown: 1000, lastUsed: 0 }, 
      bow: { cooldown: 2000, lastUsed: 0 }, 
      bomb: { cooldown: 3000, lastUsed: 0 },
      heal: { cooldown: 5000, lastUsed: 0 }
    };

    const sword = Bodies.rectangle(90, 350, 30, 10, { 
      isStatic: true, 
      render: { visible: false, fillStyle: 'silver' },
      collisionFilter: { category: categoryWeapon }
    });

    let arrows = [];
    let bombs = [];
    let enemyProjectiles = [];

    const enemies = [
      Bodies.rectangle(300, 350, 40, 40, { 
        render: { fillStyle: 'red' },
        collisionFilter: { category: categoryEnemy }
      }),
      Bodies.rectangle(450, 350, 40, 40, { 
        render: { fillStyle: 'red' },
        collisionFilter: { category: categoryEnemy }
      }),
      Bodies.rectangle(700, 350, 60, 60, { 
        render: { fillStyle: 'purple' },
        collisionFilter: { category: categoryEnemy },
        isBoss: true, health: 50
      })
    ];
    enemies.forEach(e => e.isEnemy = true);

    const items = [
      Bodies.circle(200, 300, 10, { 
        isStatic: true, 
        render: { fillStyle: 'green' },
        collisionFilter: { category: categoryItem },
        isPotion: true
      }),
      Bodies.circle(500, 300, 10, { 
        isStatic: true, 
        render: { fillStyle: 'green' },
        collisionFilter: { category: categoryItem },
        isPotion: true
      })
    ];

    const portal = Bodies.rectangle(750, 350, 40, 40, { 
      isStatic: true, 
      render: { fillStyle: 'cyan' },
      collisionFilter: { category: categoryStatic },
      isPortal: true
    });

    let score = 0;

    World.add(engine.world, [ground, ...obstacles, hero, sword, ...enemies, ...items, portal]);
    Engine.run(engine);
    Render.run(render);

    // --- Expose Variables Globally ---
    window.engine = engine;
    window.world = engine.world;
    window.hero = hero;
    window.enemies = enemies;
    window.items = items;
    window.obstacles = obstacles;
    window.portal = portal;
    window.sword = sword;

    // --- Task System ---
    const tasks = [
      { id: 1, text: "Collect a potion", condition: () => hero.inventory.potions > 0 },
      { id: 2, text: "Defeat 2 regular enemies", condition: () => enemies.filter(e => !e.isBoss && e.isRemoved).length >= 2 },
      { id: 3, text: "Defeat the boss (purple enemy)", condition: () => enemies.filter(e => e.isBoss)[0].isRemoved },
      { id: 4, text: "Reach the portal with all enemies defeated", condition: () => enemies.every(e => e.isRemoved) && Matter.Bounds.overlaps(hero.bounds, portal.bounds) }
    ];
    let currentTaskIndex = 0;
    function updateTaskText() {
      document.getElementById("taskText").innerText = tasks[currentTaskIndex].text;
    }
    updateTaskText();

    // --- Advanced Enemy AI ---
    function moveEnemies() {
      enemies.forEach(enemy => {
        if (enemy.isRemoved) return;
        const dx = hero.position.x - enemy.position.x;
        const speed = enemy.isBoss ? 2 : 1.5;
        Body.setVelocity(enemy, { x: dx > 0 ? speed : -speed, y: enemy.velocity.y });
        if (Math.random() < (enemy.isBoss ? 0.02 : 0.01)) Body.applyForce(enemy, enemy.position, { x: 0, y: -0.05 });
        if (Math.random() < (enemy.isBoss ? 0.02 : 0.005)) enemyFireProjectile(enemy);
      });
    }
    setInterval(moveEnemies, 100);

    function enemyFireProjectile(enemy) {
      const projectile = Bodies.circle(enemy.position.x, enemy.position.y, enemy.isBoss ? 10 : 5, { 
        restitution: 0.2,
        render: { fillStyle: enemy.isBoss ? 'magenta' : 'orange' },
        collisionFilter: { category: categoryProjectile },
        isProjectile: true,
        damage: enemy.isBoss ? 20 : 10
      });
      const dx = hero.position.x - enemy.position.x;
      const dy = hero.position.y - enemy.position.y;
      const angle = Math.atan2(dy, dx);
      Body.setVelocity(projectile, { x: 5 * Math.cos(angle), y: 5 * Math.sin(angle) });
      enemyProjectiles.push(projectile);
      World.add(engine.world, projectile);
      setTimeout(() => {
        enemyProjectiles = enemyProjectiles.filter(p => p !== projectile);
        World.remove(engine.world, projectile);
      }, 3000);
    }

    // --- Command Handlers with Cooldowns ---
    function customConsoleLog(...args) {
      const outputDiv = document.getElementById("output");
      outputDiv.innerHTML += args.join(" ") + "<br>";
      handleGameAction(args.join(" "));
    }
    
    function handleGameAction(command) {
      const parts = command.split(" ");
      const action = parts[0];
      const param = parts[1];
      const value = parseFloat(parts[1]);
      const now = Date.now();

      function isCooldownReady(ability) {
        return now - hero.abilities[ability].lastUsed >= hero.abilities[ability].cooldown;
      }

      function updateCooldownDisplay(ability, ready) {
        document.getElementById(`cd${ability.charAt(0).toUpperCase() + ability.slice(1)}`).innerText = ready ? "Ready" : `${((hero.abilities[ability].lastUsed + hero.abilities[ability].cooldown - now) / 1000).toFixed(1)}s`;
      }

      if (action === "move" && !isNaN(value)) {
        Body.setVelocity(hero, { x: value, y: hero.velocity.y });
      } else if (action === "jump" && !isNaN(value) && isCooldownReady("jump")) {
        Body.applyForce(hero, hero.position, { x: 0, y: -value });
        hero.abilities.jump.lastUsed = now;
        updateCooldownDisplay("jump", false);
      } else if (action === "attack") {
        if (param === "sword" && isCooldownReady("sword")) {
          performSwordAttack();
          hero.abilities.sword.lastUsed = now;
          updateCooldownDisplay("sword", false);
        } else if (param === "bow" && isCooldownReady("bow")) {
          fireArrow();
          hero.abilities.bow.lastUsed = now;
          updateCooldownDisplay("bow", false);
        } else if (param === "bomb" && isCooldownReady("bomb")) {
          throwBomb();
          hero.abilities.bomb.lastUsed = now;
          updateCooldownDisplay("bomb", false);
        }
      } else if (action === "heal" && !isNaN(value) && isCooldownReady("heal") && hero.inventory.potions > 0) {
        hero.health = Math.min(100, hero.health + value);
        hero.inventory.potions--;
        hero.abilities.heal.lastUsed = now;
        document.getElementById("health").innerText = hero.health;
        document.getElementById("potions").innerText = hero.inventory.potions;
        updateCooldownDisplay("heal", false);
      }
    }
    
    function performSwordAttack() {
      Body.setPosition(sword, { x: hero.position.x + 30, y: hero.position.y });
      sword.render.visible = true;
      enemies.forEach(enemy => {
        if (!enemy.isRemoved && Matter.Bounds.overlaps(sword.bounds, enemy.bounds)) {
          if (enemy.isBoss) {
            enemy.health -= 10;
            if (enemy.health <= 0) {
              World.remove(engine.world, enemy);
              enemy.isRemoved = true;
              score += 50;
            }
          } else {
            World.remove(engine.world, enemy);
            enemy.isRemoved = true;
            score += 10;
          }
          document.getElementById("score").innerText = score;
        }
      });
      setTimeout(() => sword.render.visible = false, 500);
    }
    
    function fireArrow() {
      const arrow = Bodies.rectangle(hero.position.x + 20, hero.position.y, 20, 5, { 
        restitution: 0.2,
        render: { fillStyle: 'yellow' },
        collisionFilter: { category: categoryWeapon },
        isArrow: true
      });
      Body.setVelocity(arrow, { x: 5, y: 0 });
      arrows.push(arrow);
      World.add(engine.world, arrow);
      setTimeout(() => {
        arrows = arrows.filter(a => a !== arrow);
        World.remove(engine.world, arrow);
      }, 3000);
    }
    
    function throwBomb() {
      const bomb = Bodies.circle(hero.position.x, hero.position.y - 10, 10, { 
        restitution: 0.5,
        render: { fillStyle: 'black' },
        collisionFilter: { category: categoryWeapon }
      });
      Body.setVelocity(bomb, { x: 3, y: -3 });
      bombs.push(bomb);
      World.add(engine.world, bomb);
      setTimeout(() => {
        bombs = bombs.filter(b => b !== bomb);
        World.remove(engine.world, bomb);
        enemies.forEach(enemy => {
          if (!enemy.isRemoved && Matter.Bounds.overlaps(bomb.bounds, enemy.bounds)) {
            if (enemy.isBoss) {
              enemy.health -= 20;
              if (enemy.health <= 0) {
                World.remove(engine.world, enemy);
                enemy.isRemoved = true;
                score += 50;
              }
            } else {
              World.remove(engine.world, enemy);
              enemy.isRemoved = true;
              score += 10;
            }
            document.getElementById("score").innerText = score;
          }
        });
      }, 2000);
    }

    // --- Collision Handling ---
    Events.on(engine, 'collisionStart', function(event) {
      const pairs = event.pairs;
      pairs.forEach(pair => {
        const bodyA = pair.bodyA;
        const bodyB = pair.bodyB;

        // Hero vs Enemy
        if ((bodyA === hero && bodyB.isEnemy) || (bodyB === hero && bodyA.isEnemy)) {
          hero.health -= bodyA.isBoss || bodyB.isBoss ? 30 : 20;
          checkHeroHealth();
        }

        // Hero vs Enemy Projectile
        if ((bodyA === hero && bodyB.isProjectile) || (bodyB === hero && bodyA.isProjectile)) {
          const projectile = bodyA.isProjectile ? bodyA : bodyB;
          hero.health -= projectile.damage;
          World.remove(engine.world, projectile);
          enemyProjectiles = enemyProjectiles.filter(p => p !== projectile);
          checkHeroHealth();
        }

        // Enemy vs Hero Weapon
        if ((bodyA.isArrow && bodyB.isEnemy) || (bodyB.isArrow && bodyA.isEnemy)) {
          const arrow = bodyA.isArrow ? bodyA : bodyB;
          const enemy = bodyA.isEnemy ? bodyA : bodyB;
          if (enemy.isBoss) {
            enemy.health -= 5;
            if (enemy.health <= 0) {
              World.remove(engine.world, enemy);
              enemy.isRemoved = true;
              score += 50;
            }
          } else {
            World.remove(engine.world, enemy);
            enemy.isRemoved = true;
            score += 10;
          }
          World.remove(engine.world, arrow);
          arrows = arrows.filter(a => a !== arrow);
          document.getElementById("score").innerText = score;
        }

        // Hero vs Item
        if ((bodyA === hero && bodyB.isPotion) || (bodyB === hero && bodyA.isPotion)) {
          const potion = bodyA === hero ? bodyB : bodyA;
          hero.inventory.potions++;
          World.remove(engine.world, potion);
          items.splice(items.indexOf(potion), 1);
          document.getElementById("potions").innerText = hero.inventory.potions;
        }

        // Hero vs Obstacle (Hazard)
        if ((bodyA === hero && bodyB.collisionFilter.category === categoryHazard) || (bodyB === hero && bodyA.collisionFilter.category === categoryHazard)) {
          hero.health -= 5;
          checkHeroHealth();
        }
      });
    });

    function checkHeroHealth() {
      document.getElementById("health").innerText = hero.health;
      if (hero.health <= 0) {
        alert("Game Over! Hero defeated.");
        resetGame();
      }
    }

    // --- Game Logic ---
    function checkTaskCompletion() {
      if (tasks[currentTaskIndex].condition()) {
        alert(`✅ Task Completed: ${tasks[currentTaskIndex].text}`);
        currentTaskIndex++;
        if (currentTaskIndex < tasks.length) {
          updateTaskText();
          resetGame(false);
        } else {
          alert("🎉 Victory! You’ve conquered the level!");
        }
      }
    }
    
    function resetGame(fullReset = true) {
      hero.health = 100;
      Body.setPosition(hero, { x: 50, y: 350 });
      Body.setVelocity(hero, { x: 0, y: 0 });
      document.getElementById("health").innerText = hero.health;
      if (fullReset) {
        hero.inventory.potions = 0;
        score = 0;
        document.getElementById("score").innerText = score;
        document.getElementById("potions").innerText = hero.inventory.potions;
        enemies.forEach(e => e.isRemoved && World.remove(engine.world, e));
        enemies.length = 0;
        enemies.push(...[
          Bodies.rectangle(300, 350, 40, 40, { render: { fillStyle: 'red' }, collisionFilter: { category: categoryEnemy } }),
          Bodies.rectangle(450, 350, 40, 40, { render: { fillStyle: 'red' }, collisionFilter: { category: categoryEnemy } }),
          Bodies.rectangle(700, 350, 60, 60, { render: { fillStyle: 'purple' }, collisionFilter: { category: categoryEnemy }, isBoss: true, health: 50 })
        ]);
        enemies.forEach(e => e.isEnemy = true);
        World.add(engine.world, enemies);
        items.length = 0;
        items.push(...[
          Bodies.circle(200, 300, 10, { isStatic: true, render: { fillStyle: 'green' }, collisionFilter: { category: categoryItem }, isPotion: true }),
          Bodies.circle(500, 300, 10, { isStatic: true, render: { fillStyle: 'green' }, collisionFilter: { category: categoryItem }, isPotion: true })
        ]);
        World.add(engine.world, items);
        currentTaskIndex = 0;
        updateTaskText();
      }
    }

    // --- Continuous Code Execution ---
    let updateInterval;
    document.getElementById("runBtn").addEventListener("click", () => {
      if (updateInterval) clearInterval(updateInterval);
      document.getElementById("output").innerHTML = "";
      const userCode = editor.getValue();
      try {
        const oldConsoleLog = console.log;
        console.log = customConsoleLog;
        const userFunction = new Function(userCode + '; return update;');
        const updateFn = userFunction();
        if (typeof updateFn === 'function') {
          updateInterval = setInterval(() => {
            try {
              updateFn();
              checkTaskCompletion();
              const now = Date.now();
              ['jump', 'sword', 'bow', 'bomb', 'heal'].forEach(ability => {
                if (now - hero.abilities[ability].lastUsed >= hero.abilities[ability].cooldown) {
                  document.getElementById(`cd${ability.charAt(0).toUpperCase() + ability.slice(1)}`).innerText = "Ready";
                }
              });
            } catch (error) {
              document.getElementById("output").innerHTML += 'Error in update: ' + error.message + '<br>';
            }
          }, 100);
        }
        console.log = oldConsoleLog;
      } catch (error) {
        document.getElementById("output").innerHTML = "Error: " + error.message;
      }
    });

    document.getElementById("stopBtn").addEventListener("click", () => {
      if (updateInterval) clearInterval(updateInterval);
    });
  </script>
</body>
</html>