<!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>