Created with ChatGPT4o
A 3D version of the same game with ChatGPT4o
Created with ChatGPT4o
A 3D version of the same game with ChatGPT4o
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Gauntlet-like Game</title> | |
<link rel="stylesheet" href="styles.css"> | |
</head> | |
<body> | |
<canvas id="gameCanvas" width="800" height="600"></canvas> | |
<div id="flash" class="flash"></div> | |
<script src="game.js"></script> | |
</body> | |
</html> |
const canvas = document.getElementById('gameCanvas'); | |
const ctx = canvas.getContext('2d'); | |
const flash = document.getElementById('flash'); | |
const player = { x: 50, y: 50, width: 20, height: 20, speed: 5, dx: 0, dy: 0, health: 100 }; | |
let score = 0; | |
let kills = 0; | |
let projectiles = []; | |
const enemies = []; | |
const generators = []; | |
const maxInitialEnemies = 30; | |
const tileSize = 10; | |
let walls = []; | |
let treasures = []; | |
let level = 1; | |
let treasuresCollected = 0; | |
let totalTreasures = 0; | |
// Function to initialize a new level | |
function initLevel() { | |
// Reset treasures collected for the new level | |
treasuresCollected = 0; | |
walls = generateWalls(); | |
treasures = generateTreasures(); | |
totalTreasures = treasures.length; | |
generateGenerators(); | |
// Increment the level number | |
level++; | |
} | |
function initGame() { | |
walls = generateWalls(); | |
treasures = generateTreasures(); | |
totalTreasures = treasures.length; | |
generateGenerators(); | |
score = 0; | |
kills = 0; | |
projectiles = []; | |
player.health = 100; | |
} | |
function generateWalls() { | |
const walls = []; | |
const rooms = 5; | |
const roomWidth = 100; | |
const roomHeight = 100; | |
for (let i = 0; i < rooms; i++) { | |
const x = Math.random() * (canvas.width - roomWidth); | |
const y = Math.random() * (canvas.height - roomHeight); | |
walls.push({ x, y, width: roomWidth, height: 20 }); // Top wall | |
walls.push({ x, y, width: 20, height: roomHeight }); // Left wall | |
walls.push({ x: x + roomWidth - 20, y, width: 20, height: roomHeight }); // Right wall | |
walls.push({ x, y: y + roomHeight - 20, width: roomWidth, height: 20 }); // Bottom wall | |
const entranceWall = Math.floor(Math.random() * 4); | |
const entrancePos = Math.random() * (roomWidth - 40) + 20; | |
switch (entranceWall) { | |
case 0: // Top wall | |
walls.pop(); | |
break; | |
case 1: // Left wall | |
walls.pop(); | |
break; | |
case 2: // Right wall | |
walls.pop(); | |
break; | |
case 3: // Bottom wall | |
walls.pop(); | |
break; | |
} | |
} | |
// Adding random walls to the dungeon | |
const extraWalls = 10; | |
for (let i = 0; i < extraWalls; i++) { | |
const x = Math.random() * (canvas.width - 50); | |
const y = Math.random() * (canvas.height - 50); | |
const width = Math.random() * 50 + 20; | |
const height = Math.random() * 50 + 20; | |
walls.push({ x, y, width, height }); | |
} | |
return walls; | |
} | |
function generateTreasures() { | |
const treasures = []; | |
for (let i = 0; i < 10; i++) { | |
const x = Math.random() * (canvas.width - 15); | |
const y = Math.random() * (canvas.height - 15); | |
treasures.push({ x, y, width: 15, height: 15 }); | |
} | |
return treasures; | |
} | |
function generateGenerators() { | |
const generatorCount = Math.floor(Math.random() * 5) + 3; | |
for (let i = 0; i < generatorCount; i++) { | |
const x = Math.random() * (canvas.width - 40) + 20; | |
const y = Math.random() * (canvas.height - 40) + 20; | |
const generator = { x, y, width: 20, height: 20, health: 5 }; | |
generators.push(generator); | |
spawnEnemiesFromGenerator(generator); | |
} | |
} | |
function spawnEnemiesFromGenerator(generator) { | |
if (generator.health > 0 && enemies.length < maxInitialEnemies) { | |
const enemy = { x: generator.x, y: generator.y, width: 20, height: 20, speed: 2, dx: 0, dy: 0 }; | |
enemies.push(enemy); | |
setTimeout(() => spawnEnemiesFromGenerator(generator), 3000); | |
} | |
} | |
function drawPlayer() { | |
ctx.fillStyle = 'blue'; | |
ctx.fillRect(player.x, player.y, player.width, player.height); | |
} | |
function drawEnemies() { | |
ctx.fillStyle = 'red'; | |
enemies.forEach(enemy => { | |
ctx.fillRect(enemy.x, enemy.y, enemy.width, enemy.height); | |
}); | |
} | |
function drawWalls() { | |
ctx.fillStyle = 'gray'; | |
walls.forEach(wall => { | |
ctx.fillRect(wall.x, wall.y, wall.width, wall.height); | |
}); | |
} | |
function drawTreasures() { | |
ctx.fillStyle = 'yellow'; | |
treasures.forEach(treasure => { | |
ctx.fillRect(treasure.x, treasure.y, treasure.width, treasure.height); | |
}); | |
} | |
function drawProjectiles() { | |
ctx.fillStyle = 'white'; | |
projectiles.forEach(projectile => { | |
ctx.fillRect(projectile.x, projectile.y, projectile.width, projectile.height); | |
}); | |
} | |
function drawGenerators() { | |
generators.forEach(generator => { | |
ctx.fillStyle = `rgb(${255 - generator.health * 50}, 0, 0)`; | |
ctx.fillRect(generator.x, generator.y, generator.width, generator.height); | |
}); | |
} | |
function drawScore() { | |
ctx.fillStyle = 'white'; | |
ctx.font = '20px Arial'; | |
ctx.fillText(`Score: ${score}`, 10, 30); | |
ctx.fillText(`Kills: ${kills}`, 150, 30); | |
ctx.fillText(`Health: ${player.health}`, 300, 30); | |
} | |
function updatePlayer() { | |
player.x += player.dx; | |
player.y += player.dy; | |
walls.forEach(wall => { | |
if (isColliding(player, wall)) { | |
player.x -= player.dx; | |
player.y -= player.dy; | |
} | |
}); | |
if (player.x < 0) player.x = 0; | |
if (player.y < 0) player.y = 0; | |
if (player.x + player.width > canvas.width) player.x = canvas.width - player.width; | |
if (player.y + player.height > canvas.height) player.y = canvas.height - player.height; | |
} | |
// Define a Node class for A* pathfinding | |
class Node { | |
constructor(x, y) { | |
this.x = x; | |
this.y = y; | |
this.parent = null; | |
this.f = 0; | |
this.g = 0; | |
this.h = 0; | |
} | |
} | |
function findPath(start, end) { | |
// Check if start and end nodes are the same | |
if (start.x === end.x && start.y === end.y) { | |
return []; | |
} | |
const openList = []; | |
const closedList = []; | |
const grid = []; | |
const path = []; | |
for (let y = 0; y < canvas.height / tileSize; y++) { | |
grid[y] = []; | |
for (let x = 0; x < canvas.width / tileSize; x++) { | |
grid[y][x] = { x, y, walkable: !walls.some(wall => isColliding({ x: x * tileSize, y: y * tileSize, width: tileSize, height: tileSize }, wall)) }; | |
} | |
} | |
const startNode = new Node(Math.floor(start.x / tileSize), Math.floor(start.y / tileSize)); | |
const endNode = new Node(Math.floor(end.x / tileSize), Math.floor(end.y / tileSize)); | |
openList.push(startNode); | |
while (openList.length > 0) { | |
let currentNode = openList[0]; | |
for (let i = 1; i < openList.length; i++) { | |
if (openList[i].f < currentNode.f || (openList[i].f === currentNode.f && openList[i].h < currentNode.h)) { | |
currentNode = openList[i]; | |
} | |
} | |
openList.splice(openList.indexOf(currentNode), 1); | |
closedList.push(currentNode); | |
if (currentNode.x === endNode.x && currentNode.y === endNode.y) { | |
let current = currentNode; | |
while (current.parent) { | |
path.push({ x: current.x, y: current.y }); | |
current = current.parent; | |
} | |
return path.reverse(); | |
} | |
const neighbors = []; | |
for (let x = -1; x <= 1; x++) { | |
for (let y = -1; y <= 1; y++) { | |
if (x === 0 && y === 0) continue; | |
const checkX = currentNode.x + x; | |
const checkY = currentNode.y + y; | |
if (checkX >= 0 && checkX < grid[0].length && checkY >= 0 && checkY < grid.length && grid[checkY][checkX].walkable) { | |
neighbors.push(grid[checkY][checkX]); | |
} | |
} | |
} | |
neighbors.forEach(neighbor => { | |
if (!closedList.some(node => node.x === neighbor.x && node.y === neighbor.y)) { | |
const gScore = currentNode.g + 1; | |
const hScore = Math.abs(neighbor.x - endNode.x) + Math.abs(neighbor.y - endNode.y); | |
const fScore = gScore + hScore; | |
if (!openList.some(node => node.x === neighbor.x && node.y === neighbor.y) || gScore < neighbor.g) { | |
neighbor.parent = currentNode; | |
neighbor.g = gScore; | |
neighbor.h = hScore; | |
neighbor.f = fScore; | |
if (!openList.some(node => node.x === neighbor.x && node.y === neighbor.y)) { | |
openList.push(neighbor); | |
} | |
} | |
} | |
}); | |
} | |
// No path found | |
return []; | |
} | |
function updateEnemies() { | |
enemies.forEach((enemy, enemyIndex) => { | |
const path = findPath(enemy, player); | |
if (path.length > 1) { | |
const nextTile = path[1]; | |
const targetX = nextTile.x * tileSize + tileSize / 2; | |
const targetY = nextTile.y * tileSize + tileSize / 2; | |
const dx = targetX - enemy.x; | |
const dy = targetY - enemy.y; | |
const distance = Math.sqrt(dx * dx + dy * dy); | |
enemy.dx = (dx / distance) * enemy.speed; | |
enemy.dy = (dy / distance) * enemy.speed; | |
enemy.x += enemy.dx; | |
enemy.y += enemy.dy; | |
} | |
if (isColliding(player, enemy)) { | |
enemies.splice(enemyIndex, 1); | |
player.health -= 20; | |
flashScreen(); | |
if (player.health <= 0) { | |
console.log("Game Over"); | |
initGame(); | |
} | |
} | |
}); | |
} | |
function flashScreen() { | |
flash.style.backgroundColor = 'rgba(255, 0, 0, 0.5)'; | |
setTimeout(() => { | |
flash.style.backgroundColor = 'rgba(255, 0, 0, 0)'; | |
}, 100); | |
} | |
function updateProjectiles() { | |
projectiles.forEach((projectile, projectileIndex) => { | |
projectile.x += projectile.dx; | |
projectile.y += projectile.dy; | |
enemies.forEach((enemy, enemyIndex) => { | |
if (isColliding(projectile, enemy)) { | |
enemies.splice(enemyIndex, 1); | |
projectiles.splice(projectileIndex, 1); | |
kills += 1; | |
} | |
}); | |
generators.forEach((generator, generatorIndex) => { | |
if (isColliding(projectile, generator)) { | |
generator.health -= 1; | |
projectiles.splice(projectileIndex, 1); | |
if (generator.health <= 0) { | |
generators.splice(generatorIndex, 1); | |
} | |
} | |
}); | |
if (projectile.x < 0 || projectile.y < 0 || projectile.x > canvas.width || projectile.y > canvas.height) { | |
projectiles.splice(projectileIndex, 1); | |
} | |
// Check for collision with walls | |
if (isProjectileCollidingWithWalls(projectile)) { | |
projectiles.splice(projectileIndex, 1); | |
} | |
}); | |
} | |
function isProjectileCollidingWithWalls(projectile) { | |
return walls.some(wall => isColliding(projectile, wall)); | |
} | |
function isColliding(rect1, rect2) { | |
return ( | |
rect1.x < rect2.x + rect2.width && | |
rect1.x + rect1.width > rect2.x && | |
rect1.y < rect2.y + rect2.height && | |
rect1.y + rect1.height > rect2.y | |
); | |
} | |
function shootProjectile() { | |
let target = null; | |
// Find the closest enemy | |
if (enemies.length > 0) { | |
target = enemies.reduce((closest, enemy) => { | |
const distToClosest = Math.hypot(closest.x - player.x, closest.y - player.y); | |
const distToEnemy = Math.hypot(enemy.x - player.x, enemy.y - player.y); | |
return distToEnemy < distToClosest ? enemy : closest; | |
}, enemies[0]); | |
} | |
// Find the closest generator | |
if (generators.length > 0) { | |
const generatorTarget = generators.reduce((closest, generator) => { | |
const distToClosest = Math.hypot(closest.x - player.x, closest.y - player.y); | |
const distToGenerator = Math.hypot(generator.x - player.x, generator.y - player.y); | |
return distToGenerator < distToClosest ? generator : closest; | |
}, generators[0]); | |
// If there are no enemies or generator is closer, target the generator | |
if (!target || Math.hypot(generatorTarget.x - player.x, generatorTarget.y - player.y) < Math.hypot(target.x - player.x, target.y - player.y)) { | |
target = generatorTarget; | |
} | |
} | |
if (target) { | |
const dx = target.x - player.x; | |
const dy = target.y - player.y; | |
const distance = Math.sqrt(dx * dx + dy * dy); | |
const projectile = { | |
x: player.x + player.width / 2, | |
y: player.y + player.height / 2, | |
width: 5, | |
height: 5, | |
dx: (dx / distance) * 10, | |
dy: (dy / distance) * 10 | |
}; | |
projectiles.push(projectile); | |
} | |
} | |
// Function to draw the level number on the screen | |
function drawLevelNumber() { | |
ctx.fillStyle = "white"; | |
ctx.font = "20px Arial"; | |
ctx.fillText("Level: " + level, 500, 30); | |
} | |
function update() { | |
clear(); | |
drawWalls(); | |
drawPlayer(); | |
drawEnemies(); | |
drawTreasures(); | |
drawProjectiles(); | |
drawGenerators(); | |
drawScore(); | |
drawLevelNumber(); | |
updatePlayer(); | |
updateEnemies(); | |
updateProjectiles(); | |
checkTreasureCollection(); | |
requestAnimationFrame(update); | |
} | |
function checkTreasureCollection() { | |
treasures.forEach((treasure, index) => { | |
if (isColliding(player, treasure)) { | |
treasures.splice(index, 1); | |
score += 10; | |
treasuresCollected++; | |
console.log(`${treasuresCollected} === ${totalTreasures}`) | |
if (treasuresCollected === totalTreasures) { | |
// Trigger cool animation effect | |
triggerAnimationEffect(() => { | |
// After animation, initialize the next level | |
initLevel(); | |
}); | |
} | |
} | |
}); | |
} | |
// Function to trigger a cool animation effect | |
function triggerAnimationEffect(callback) { | |
// Define animation parameters | |
const animationDuration = 1000; // in milliseconds | |
const rotationSpeed = Math.PI / 180; // 1 degree rotation per frame | |
let elapsedTime = 0; | |
// Start animation loop | |
const animationLoop = () => { | |
// Clear the canvas | |
clear(); | |
// Draw the rotating and fading effect | |
ctx.save(); | |
ctx.translate(canvas.width / 2, canvas.height / 2); | |
ctx.rotate(elapsedTime * rotationSpeed); | |
ctx.globalAlpha = 1 - (elapsedTime / animationDuration); | |
ctx.fillStyle = "rgba(255, 255, 255, 0.5)"; | |
ctx.fillRect(-canvas.width / 2, -canvas.height / 2, canvas.width, canvas.height); | |
ctx.restore(); | |
// Update elapsed time | |
elapsedTime += 16; // Assuming 60 frames per second | |
if (elapsedTime < animationDuration) { | |
// Continue animation loop | |
requestAnimationFrame(animationLoop); | |
} else { | |
// Animation complete, call the callback function | |
callback(); | |
} | |
}; | |
// Start the animation loop | |
animationLoop(); | |
} | |
function clear() { | |
ctx.clearRect(0, 0, canvas.width, canvas.height); | |
} | |
function movePlayer(e) { | |
switch (e.key) { | |
case 'ArrowUp': | |
player.dy = -player.speed; | |
break; | |
case 'ArrowDown': | |
player.dy = player.speed; | |
break; | |
case 'ArrowLeft': | |
player.dx = -player.speed; | |
break; | |
case 'ArrowRight': | |
player.dx = player.speed; | |
break; | |
case ' ': | |
shootProjectile(); | |
break; | |
} | |
} | |
function stopPlayer(e) { | |
switch (e.key) { | |
case 'ArrowUp': | |
case 'ArrowDown': | |
player.dy = 0; | |
break; | |
case 'ArrowLeft': | |
case 'ArrowRight': | |
player.dx = 0; | |
break; | |
} | |
} | |
document.addEventListener('keydown', movePlayer); | |
document.addEventListener('keyup', stopPlayer); | |
initGame(); | |
update(); |
body { | |
margin: 0; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
height: 100vh; | |
background-color: #000; | |
} | |
canvas { | |
background-color: #111; | |
border: 1px solid #fff; | |
} | |
.flash { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(255, 0, 0, 0); | |
transition: background-color 0.5s ease-out; | |
} |