code-game / index.html
Ramesh-vani's picture
Update index.html
34d9b9c verified
<!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>