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; | |
| } |