inital commit with game file and Readme

This commit is contained in:
2025-08-10 17:53:58 +01:00
commit 75a3ae6a6f
2 changed files with 685 additions and 0 deletions

673
index.html Normal file
View File

@@ -0,0 +1,673 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Loot Collector</title>
<style>
body {
margin: 0;
padding: 0;
background: linear-gradient(135deg, #1a1a2e, #16213e, #0f3460);
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
font-family: 'Courier New', monospace;
color: white;
}
canvas {
border: 3px solid #00ff88;
border-radius: 10px;
box-shadow: 0 0 30px rgba(0, 255, 136, 0.3);
}
.ui {
position: absolute;
top: 20px;
left: 50%;
transform: translateX(-50%);
text-align: center;
z-index: 10;
}
.score {
font-size: 24px;
color: #00ff88;
text-shadow: 0 0 10px rgba(0, 255, 136, 0.5);
}
.instructions {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
text-align: center;
color: #aaa;
}
.countdown {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
font-size: 72px;
color: #00ff88;
text-shadow: 0 0 30px rgba(0, 255, 136, 0.8);
z-index: 20;
font-weight: bold;
}
.winner-screen {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.9);
display: none;
flex-direction: column;
justify-content: center;
align-items: center;
z-index: 30;
}
.winner-text {
font-size: 64px;
color: #00ff88;
text-shadow: 0 0 40px rgba(0, 255, 136, 1);
margin-bottom: 20px;
animation: pulse 2s infinite;
}
.winner-score {
font-size: 32px;
color: #fff;
margin-bottom: 30px;
}
.restart-button {
padding: 15px 30px;
font-size: 24px;
background: linear-gradient(45deg, #00ff88, #00cc6a);
color: #000;
border: none;
border-radius: 10px;
cursor: pointer;
font-weight: bold;
margin-bottom: 15px;
transition: transform 0.2s;
}
.restart-button:hover {
transform: scale(1.05);
}
.space-hint {
color: #aaa;
font-size: 16px;
}
@keyframes pulse {
0%, 100% { transform: scale(1); }
50% { transform: scale(1.05); }
}
</style>
</head>
<body>
<div class="ui">
<div class="score">Player: <span id="playerScore">0</span> | Enemy: <span id="enemyScore">0</span></div>
</div>
<div id="countdownDisplay" class="countdown">5</div>
<div id="winnerScreen" class="winner-screen">
<div id="winnerText" class="winner-text">PLAYER WINS!</div>
<div id="finalScore" class="winner-score">Final Score: Player 1000 - Enemy 850</div>
<button id="restartButton" class="restart-button">PLAY AGAIN</button>
<div class="space-hint">or press SPACE to restart</div>
</div>
<canvas id="gameCanvas" width="800" height="600"></canvas>
<div class="instructions">
Use A/D or Left/Right arrows to steer • Collect the glowing loot!
</div>
<script>
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const playerScoreElement = document.getElementById('playerScore');
const enemyScoreElement = document.getElementById('enemyScore');
const countdownElement = document.getElementById('countdownDisplay');
const winnerScreen = document.getElementById('winnerScreen');
const winnerText = document.getElementById('winnerText');
const finalScore = document.getElementById('finalScore');
const restartButton = document.getElementById('restartButton');
// Game state
let playerScore = 0;
let enemyScore = 0;
let gameRunning = false; // Start paused for countdown
let gameEnded = false;
let countdown = 5; // 5 second countdown
let countdownTimer = 0;
// Player object
const player = {
x: 0, // Will be set randomly
y: 0, // Will be set randomly
size: 20,
speed: 4,
color: '#00ff88',
direction: 0, // angle in radians
velocity: { x: 0, y: 0 }
};
// Trail system
let playerTrail = [];
// Enemy object
const enemy = {
x: 0, // Will be set randomly
y: 0, // Will be set randomly
size: 20,
speed: 3.5,
color: '#ff4444',
direction: 0, // Will be set randomly
velocity: { x: 0, y: 0 },
turnCooldown: 0,
targetLoot: null
};
// Enemy trail system
let enemyTrail = [];
// Loot array
let loot = [];
let lootSpawnTimer = 0;
const lootSpawnInterval = 120; // frames between spawns (2 seconds at 60fps)
// Input handling
const keys = {};
document.addEventListener('keydown', (e) => {
keys[e.key.toLowerCase()] = true;
// Restart with spacebar
if (e.key === ' ' && gameEnded) {
restartGame();
}
});
document.addEventListener('keyup', (e) => {
keys[e.key.toLowerCase()] = false;
});
// Create loot
function createLoot() {
const lootItem = {
x: Math.random() * (canvas.width - 20) + 10,
y: Math.random() * (canvas.height - 20) + 10,
size: 12,
color: `hsl(0, 100%, 60%)`, // Start red
glow: Math.random() * 10 + 5,
pulse: Math.random() * 0.1 + 0.05,
alpha: 0, // Start invisible
fadeSpeed: 0.02,
age: 1 // Start at age 1
};
loot.push(lootItem);
}
// Restart button event
restartButton.addEventListener('click', restartGame);
// Randomize starting positions
function randomizeStartingPositions() {
// Keep players away from edges and each other
const margin = 50;
let validPosition = false;
let attempts = 0;
// Set player position
player.x = Math.random() * (canvas.width - 2 * margin) + margin;
player.y = Math.random() * (canvas.height - 2 * margin) + margin;
player.direction = Math.random() * 2 * Math.PI;
// Set enemy position (make sure it's not too close to player)
while (!validPosition && attempts < 20) {
enemy.x = Math.random() * (canvas.width - 2 * margin) + margin;
enemy.y = Math.random() * (canvas.height - 2 * margin) + margin;
const distance = Math.sqrt(
Math.pow(player.x - enemy.x, 2) +
Math.pow(player.y - enemy.y, 2)
);
if (distance > 100) { // Minimum distance between players
validPosition = true;
}
attempts++;
}
enemy.direction = Math.random() * 2 * Math.PI;
}
// Restart game function
function restartGame() {
// Reset game state
playerScore = 0;
enemyScore = 0;
gameRunning = false;
gameEnded = false;
countdown = 5;
countdownTimer = 0;
// Reset UI
playerScoreElement.textContent = '0';
enemyScoreElement.textContent = '0';
countdownElement.textContent = '5';
countdownElement.style.display = 'block';
winnerScreen.style.display = 'none';
// Randomize starting positions
randomizeStartingPositions();
// Reset velocities
player.velocity.x = 0;
player.velocity.y = 0;
enemy.velocity.x = 0;
enemy.velocity.y = 0;
enemy.turnCooldown = 0;
// Clear trails and loot
playerTrail = [];
enemyTrail = [];
loot = [];
lootSpawnTimer = 0;
}
// Check for winner
function checkWinner() {
if (gameEnded) return;
if (playerScore >= 1000) {
gameEnded = true;
gameRunning = false;
winnerText.textContent = "PLAYER WINS!";
winnerText.style.color = "#00ff88";
finalScore.textContent = `Final Score: Player ${playerScore} - Enemy ${enemyScore}`;
winnerScreen.style.display = 'flex';
} else if (enemyScore >= 1000) {
gameEnded = true;
gameRunning = false;
winnerText.textContent = "ENEMY WINS!";
winnerText.style.color = "#ff4444";
finalScore.textContent = `Final Score: Player ${playerScore} - Enemy ${enemyScore}`;
winnerScreen.style.display = 'flex';
}
}
// Update countdown
function updateCountdown() {
if (countdown > 0) {
countdownTimer++;
if (countdownTimer >= 60) { // 1 second at 60fps
countdown--;
countdownTimer = 0;
if (countdown > 0) {
countdownElement.textContent = countdown;
} else {
countdownElement.textContent = "GO!";
// Start the game after a brief "GO!" display
setTimeout(() => {
gameRunning = true;
countdownElement.style.display = 'none';
// Spawn initial loot
for (let i = 0; i < 5; i++) {
const lootItem = {
x: Math.random() * (canvas.width - 20) + 10,
y: Math.random() * (canvas.height - 20) + 10,
size: 12,
color: `hsl(0, 100%, 60%)`,
glow: Math.random() * 10 + 5,
pulse: Math.random() * 0.1 + 0.05,
alpha: 1,
fadeSpeed: 0.02,
age: 1
};
loot.push(lootItem);
}
}, 500);
}
}
}
}
// Update player position
function updatePlayer() {
if (!gameRunning || gameEnded) return; // Don't move during countdown or when game ended
// Handle steering input
let turning = 0;
if (keys['a'] || keys['arrowleft']) turning -= 0.06;
if (keys['d'] || keys['arrowright']) turning += 0.06;
// Apply turning
player.direction += turning;
// Always move forward in current direction
player.velocity.x = Math.cos(player.direction) * player.speed;
player.velocity.y = Math.sin(player.direction) * player.speed;
// Update position
player.x += player.velocity.x;
player.y += player.velocity.y;
// Bounce off walls
if (player.x - player.size < 0) {
player.x = player.size;
player.direction = Math.PI - player.direction; // Reflect horizontally
}
if (player.x + player.size > canvas.width) {
player.x = canvas.width - player.size;
player.direction = Math.PI - player.direction; // Reflect horizontally
}
if (player.y - player.size < 0) {
player.y = player.size;
player.direction = -player.direction; // Reflect vertically
}
if (player.y + player.size > canvas.height) {
player.y = canvas.height - player.size;
player.direction = -player.direction; // Reflect vertically
}
// Add current position to trail
playerTrail.push({
x: player.x,
y: player.y,
alpha: 1.0,
size: player.size
});
// Limit trail length and fade out
if (playerTrail.length > 15) {
playerTrail.shift();
}
// Fade trail segments
playerTrail.forEach((segment, index) => {
segment.alpha = (index + 1) / playerTrail.length * 0.7;
segment.size = player.size * (segment.alpha * 0.8 + 0.2);
});
}
// Update enemy AI
function updateEnemy() {
if (!gameRunning || gameEnded) return; // Don't move during countdown or when game ended
// Find closest loot
let closestLoot = null;
let closestDistance = Infinity;
loot.forEach(item => {
if (item.alpha > 0.5) { // Only target visible loot
const dx = enemy.x - item.x;
const dy = enemy.y - item.y;
const distance = Math.sqrt(dx * dx + dy * dy);
if (distance < closestDistance) {
closestDistance = distance;
closestLoot = item;
}
}
});
// AI steering towards loot
if (closestLoot && enemy.turnCooldown <= 0) {
const dx = closestLoot.x - enemy.x;
const dy = closestLoot.y - enemy.y;
const targetAngle = Math.atan2(dy, dx);
// Calculate angle difference
let angleDiff = targetAngle - enemy.direction;
// Normalize angle difference to -PI to PI
while (angleDiff > Math.PI) angleDiff -= 2 * Math.PI;
while (angleDiff < -Math.PI) angleDiff += 2 * Math.PI;
// Turn towards target (similar turning speed to player)
if (Math.abs(angleDiff) > 0.1) {
if (angleDiff > 0) {
enemy.direction += 0.05;
} else {
enemy.direction -= 0.05;
}
}
}
// Reduce turn cooldown
if (enemy.turnCooldown > 0) enemy.turnCooldown--;
// Always move forward in current direction
enemy.velocity.x = Math.cos(enemy.direction) * enemy.speed;
enemy.velocity.y = Math.sin(enemy.direction) * enemy.speed;
// Update position
enemy.x += enemy.velocity.x;
enemy.y += enemy.velocity.y;
// Bounce off walls (same as player)
if (enemy.x - enemy.size < 0) {
enemy.x = enemy.size;
enemy.direction = Math.PI - enemy.direction;
enemy.turnCooldown = 30; // Brief cooldown after bouncing
}
if (enemy.x + enemy.size > canvas.width) {
enemy.x = canvas.width - enemy.size;
enemy.direction = Math.PI - enemy.direction;
enemy.turnCooldown = 30;
}
if (enemy.y - enemy.size < 0) {
enemy.y = enemy.size;
enemy.direction = -enemy.direction;
enemy.turnCooldown = 30;
}
if (enemy.y + enemy.size > canvas.height) {
enemy.y = canvas.height - enemy.size;
enemy.direction = -enemy.direction;
enemy.turnCooldown = 30;
}
// Add current position to enemy trail
enemyTrail.push({
x: enemy.x,
y: enemy.y,
alpha: 1.0,
size: enemy.size
});
// Limit trail length and fade out
if (enemyTrail.length > 15) {
enemyTrail.shift();
}
// Fade trail segments
enemyTrail.forEach((segment, index) => {
segment.alpha = (index + 1) / enemyTrail.length * 0.7;
segment.size = enemy.size * (segment.alpha * 0.8 + 0.2);
});
}
// Update loot spawning
function updateLoot() {
if (!gameRunning || gameEnded) return; // Don't spawn during countdown or when game ended
lootSpawnTimer++;
// Spawn new loot periodically
if (lootSpawnTimer >= lootSpawnInterval) {
createLoot();
lootSpawnTimer = 0;
}
// Update existing loot
loot.forEach(item => {
// Fade in effect
if (item.alpha < 1) {
item.alpha += item.fadeSpeed;
if (item.alpha > 1) item.alpha = 1;
}
// Age the loot
item.age += 0.008; // Slow aging rate
// Update color based on age (red -> orange -> yellow -> green -> blue -> purple)
const hue = Math.min((item.age - 1) * 60, 300); // 0 to 300 degrees over time
item.color = `hsl(${hue}, 100%, 60%)`;
// Update glow animation
item.glow += item.pulse;
if (item.glow > 15 || item.glow < 5) {
item.pulse = -item.pulse;
}
});
}
// Check collisions
function checkCollisions() {
loot = loot.filter(item => {
const playerDx = player.x - item.x;
const playerDy = player.y - item.y;
const playerDistance = Math.sqrt(playerDx * playerDx + playerDy * playerDy);
const enemyDx = enemy.x - item.x;
const enemyDy = enemy.y - item.y;
const enemyDistance = Math.sqrt(enemyDx * enemyDx + enemyDy * enemyDy);
// Check player collision
if (playerDistance < player.size + item.size && item.alpha > 0.5) {
// Calculate points based on age (younger = more points)
const points = Math.max(50 - Math.floor(item.age * 10), 5);
playerScore += points;
playerScoreElement.textContent = playerScore;
checkWinner();
return false; // Remove this loot
}
// Check enemy collision
if (enemyDistance < enemy.size + item.size && item.alpha > 0.5) {
// Calculate points based on age (younger = more points)
const points = Math.max(50 - Math.floor(item.age * 10), 5);
enemyScore += points;
enemyScoreElement.textContent = enemyScore;
checkWinner();
return false; // Remove this loot
}
return true;
});
}
// Draw everything
function draw() {
// Clear canvas completely
ctx.fillStyle = 'rgb(26, 26, 46)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
// Draw player trail
playerTrail.forEach(segment => {
if (segment.alpha > 0) {
ctx.shadowColor = player.color;
ctx.shadowBlur = 10 * segment.alpha;
ctx.fillStyle = `rgba(0, 255, 136, ${segment.alpha * 0.6})`;
ctx.beginPath();
ctx.arc(segment.x, segment.y, segment.size, 0, Math.PI * 2);
ctx.fill();
}
});
// Draw enemy trail
enemyTrail.forEach(segment => {
if (segment.alpha > 0) {
ctx.shadowColor = enemy.color;
ctx.shadowBlur = 10 * segment.alpha;
ctx.fillStyle = `rgba(255, 68, 68, ${segment.alpha * 0.6})`;
ctx.beginPath();
ctx.arc(segment.x, segment.y, segment.size, 0, Math.PI * 2);
ctx.fill();
}
});
// Draw loot with glow effect
loot.forEach(item => {
if (item.alpha > 0) {
// Draw glow
ctx.shadowColor = item.color;
ctx.shadowBlur = item.glow * item.alpha;
ctx.fillStyle = item.color.replace('60%)', `${60 * item.alpha}%)`);
ctx.beginPath();
ctx.arc(item.x, item.y, item.size * item.alpha, 0, Math.PI * 2);
ctx.fill();
// Draw inner bright core
ctx.shadowBlur = 0;
ctx.fillStyle = `rgba(255, 255, 255, ${item.alpha})`;
ctx.beginPath();
ctx.arc(item.x, item.y, item.size * 0.3 * item.alpha, 0, Math.PI * 2);
ctx.fill();
}
});
// Draw player (main body)
ctx.shadowColor = player.color;
ctx.shadowBlur = 15;
ctx.fillStyle = player.color;
ctx.beginPath();
ctx.arc(player.x, player.y, player.size, 0, Math.PI * 2);
ctx.fill();
// Player inner glow
ctx.shadowBlur = 0;
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(player.x, player.y, player.size * 0.4, 0, Math.PI * 2);
ctx.fill();
// Draw enemy (main body)
ctx.shadowColor = enemy.color;
ctx.shadowBlur = 15;
ctx.fillStyle = enemy.color;
ctx.beginPath();
ctx.arc(enemy.x, enemy.y, enemy.size, 0, Math.PI * 2);
ctx.fill();
// Enemy inner glow
ctx.shadowBlur = 0;
ctx.fillStyle = 'white';
ctx.beginPath();
ctx.arc(enemy.x, enemy.y, enemy.size * 0.4, 0, Math.PI * 2);
ctx.fill();
}
// Game loop
function gameLoop() {
updateCountdown();
updatePlayer();
updateEnemy();
updateLoot();
checkCollisions();
draw();
requestAnimationFrame(gameLoop);
}
// Start the game with random positions
randomizeStartingPositions();
gameLoop();
</script>
</body>
</html>