Files
chrismas_trae_game/app.js
Gemini AI f4c36c48ac Fix all 5 new games with proper logic flow and Tetris 120 FPS
- Neon Puzzle: Fixed angle normalization, visual feedback for aligned connections
- Rhythm Beat: Complete 4-lane system with key indicators and timing-based scoring
- Cosmic Arena: Added particle explosion effects
- Crystal Tetris: Fixed duplicate shape, proper game over, drop speed scaling, 120 FPS with requestAnimationFrame
- Aurora Jumper: Reachable platform placement based on jump physics, proper respawn system
2025-12-26 11:05:28 +04:00

2388 lines
75 KiB
JavaScript

/**
* TRAE Aurora - Crystalline Christmas Logic
* Author: Code Prism (Assistant)
*/
document.addEventListener('DOMContentLoaded', () => {
initBackground();
initCountdown();
initMessageForge();
initGame();
initTraoom();
initNeonPuzzle();
initRhythmBeat();
initCosmicArena();
initTetris();
initPlatformer();
});
// --- Utility: Scroll to Section ---
function scrollToSection(id) {
const el = document.getElementById(id);
if (el) el.scrollIntoView({ behavior: 'smooth' });
}
// --- 1. Background Engine (Aurora + Snow) ---
function initBackground() {
const canvas = document.getElementById('bgCanvas');
const ctx = canvas.getContext('2d');
let width, height, particles = [];
const resize = () => {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
};
class Particle {
constructor() {
this.reset();
}
reset() {
this.x = Math.random() * width;
this.y = Math.random() * height;
this.size = Math.random() * 2 + 1;
this.speedY = Math.random() * 1 + 0.5;
this.speedX = Math.random() * 0.5 - 0.25;
this.opacity = Math.random() * 0.5 + 0.2;
}
update() {
this.y += this.speedY;
this.x += this.speedX;
if (this.y > height) {
this.y = -10;
this.x = Math.random() * width;
}
}
draw() {
ctx.fillStyle = `rgba(0, 255, 102, ${this.opacity})`;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
}
}
const initParticles = () => {
particles = [];
for (let i = 0; i < 150; i++) {
particles.push(new Particle());
}
};
const drawAurora = () => {
const time = Date.now() * 0.001;
const gradient = ctx.createLinearGradient(0, 0, width, height);
gradient.addColorStop(0, '#050b14');
gradient.addColorStop(0.5, '#0a1a2f');
gradient.addColorStop(1, '#050b14');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, width, height);
// Aurora waves
ctx.save();
ctx.globalCompositeOperation = 'screen';
for (let i = 0; i < 3; i++) {
ctx.beginPath();
ctx.strokeStyle = i === 0 ? 'rgba(0, 255, 102, 0.1)' : 'rgba(0, 242, 255, 0.05)';
ctx.lineWidth = 100;
const yOffset = height * 0.3 + (i * 100);
ctx.moveTo(0, yOffset + Math.sin(time + i) * 50);
for (let x = 0; x < width; x += 50) {
ctx.lineTo(x, yOffset + Math.sin(x * 0.002 + time + i) * 80);
}
ctx.stroke();
}
ctx.restore();
};
const animate = () => {
drawAurora();
particles.forEach(p => {
p.update();
p.draw();
});
requestAnimationFrame(animate);
};
window.addEventListener('resize', () => {
isMobile = window.innerWidth < 768;
resize();
});
resize();
initParticles();
animate();
}
// --- 2. Countdown Timer ---
function initCountdown() {
const target = new Date('December 25, 2025 00:00:00').getTime();
const update = () => {
const now = new Date().getTime();
const diff = target - now;
if (diff <= 0) {
document.getElementById('countdown').innerHTML = "<h3>MERRY CHRISTMAS!</h3>";
return;
}
const d = Math.floor(diff / (1000 * 60 * 60 * 24));
const h = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const m = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const s = Math.floor((diff % (1000 * 60)) / 1000);
document.getElementById('days').innerText = d.toString().padStart(2, '0');
document.getElementById('hours').innerText = h.toString().padStart(2, '0');
document.getElementById('minutes').innerText = m.toString().padStart(2, '0');
document.getElementById('seconds').innerText = s.toString().padStart(2, '0');
};
setInterval(update, 1000);
update();
}
// --- 3. Magical Message Forge ---
function initMessageForge() {
const input = document.getElementById('messageInput');
const btn = document.getElementById('forgeBtn');
const output = document.getElementById('outputMessage');
const canvas = document.getElementById('snowflakeCanvas');
const cloudContainer = document.getElementById('cloudContainer');
const ctx = canvas.getContext('2d');
canvas.width = 300;
canvas.height = 300;
// Load persistent messages
let communityMessages = JSON.parse(localStorage.getItem('trae_messages') || '[]');
const updateCloud = () => {
cloudContainer.innerHTML = '';
communityMessages.slice(-15).reverse().forEach(msg => {
const span = document.createElement('span');
span.className = 'cloud-msg';
span.innerText = msg;
cloudContainer.appendChild(span);
});
};
const drawCrystal = (text) => {
ctx.clearRect(0, 0, canvas.width, canvas.height);
const centerX = canvas.width / 2;
const centerY = canvas.height / 2;
ctx.strokeStyle = '#00ff66';
ctx.lineWidth = 2;
const branches = 6 + (text.length % 6);
const radius = 80 + (text.length * 2);
for (let i = 0; i < branches; i++) {
const angle = (i * 2 * Math.PI) / branches;
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.lineTo(centerX + Math.cos(angle) * radius, centerY + Math.sin(angle) * radius);
ctx.stroke();
for (let j = 1; j < 4; j++) {
const subAngle = angle + 0.5;
const subX = centerX + Math.cos(angle) * (radius * j / 4);
const subY = centerY + Math.sin(angle) * (radius * j / 4);
ctx.beginPath();
ctx.moveTo(subX, subY);
ctx.lineTo(subX + Math.cos(subAngle) * 20, subY + Math.sin(subAngle) * 20);
ctx.stroke();
}
}
output.innerText = text.toUpperCase();
output.style.opacity = 0;
setTimeout(() => { output.style.opacity = 1; output.style.transition = 'opacity 1s'; }, 100);
};
btn.addEventListener('click', () => {
const text = input.value.trim();
if (text) {
drawCrystal(text);
communityMessages.push(text);
if (communityMessages.length > 50) communityMessages.shift();
localStorage.setItem('trae_messages', JSON.stringify(communityMessages));
updateCloud();
input.value = '';
}
});
updateCloud();
}
// --- 4. Gift Catcher Mini Game ---
function initGame() {
const canvas = document.getElementById('gameCanvas');
const ctx = canvas.getContext('2d');
const startBtn = document.getElementById('startGameBtn');
const scoreEl = document.getElementById('scoreVal');
const overlay = document.getElementById('gameOverlay');
const submitBtn = document.getElementById('submitScoreBtn');
const nameInput = document.getElementById('playerName');
const leaderboardList = document.getElementById('leaderboardList');
const timerDisplay = document.getElementById('timerDisplay');
const timeBtns = document.querySelectorAll('.time-btn');
let score = 0;
let gameActive = false;
let playerX = 0;
let playerWidth = 90;
let playerHeight = 25;
let playerBottomOffset = 35;
let gifts = [];
let particles = [];
let difficultyMultiplier = 1;
let combo = 0;
let lastCatchTime = 0;
let timeLeft = 15;
let timerInterval = null;
let selectedDuration = 15;
let isMobile = window.innerWidth < 768;
timeBtns.forEach(btn => {
btn.addEventListener('click', () => {
timeBtns.forEach(b => b.classList.remove('active'));
btn.classList.add('active');
selectedDuration = parseInt(btn.dataset.time);
timeLeft = selectedDuration;
timerDisplay.innerText = timeLeft + 's';
});
});
// Leaderboard logic
let leaderboard = JSON.parse(localStorage.getItem('trae_leaderboard') || '[]');
const updateLeaderboardUI = () => {
leaderboardList.innerHTML = '';
leaderboard.sort((a, b) => b.score - a.score).slice(0, 10).forEach((entry, idx) => {
const dateStr = new Date(entry.date).toLocaleDateString();
const durationStr = entry.duration ? `${entry.duration}s` : 'N/A';
const div = document.createElement('div');
div.className = 'leader-item';
div.innerHTML = `
<span class="leader-rank">#${idx + 1}</span>
<span class="leader-name">${entry.name}</span>
<span class="leader-score">${entry.score}</span>
<span class="leader-duration">${durationStr}</span>
<span class="leader-date">${dateStr}</span>
`;
leaderboardList.appendChild(div);
});
};
const startTimer = () => {
timeLeft = selectedDuration;
timerDisplay.innerText = timeLeft + 's';
timerDisplay.classList.remove('warning');
if (timerInterval) clearInterval(timerInterval);
timerInterval = setInterval(() => {
timeLeft--;
timerDisplay.innerText = timeLeft + 's';
const warningThreshold = Math.max(5, Math.floor(selectedDuration * 0.2));
if (timeLeft <= warningThreshold) {
timerDisplay.classList.add('warning');
}
if (timeLeft <= 0) {
clearInterval(timerInterval);
gameOver();
}
}, 1000);
};
const gameOver = () => {
gameActive = false;
if (timerInterval) clearInterval(timerInterval);
document.body.style.overflow = '';
overlay.classList.remove('hidden');
document.getElementById('overlayTitle').innerText = `GAME OVER - SCORE: ${score}`;
};
submitBtn.addEventListener('click', () => {
const name = nameInput.value.trim() || 'Anonymous';
leaderboard.push({ name, score, date: new Date().toISOString(), duration: selectedDuration });
localStorage.setItem('trae_leaderboard', JSON.stringify(leaderboard));
updateLeaderboardUI();
overlay.classList.add('hidden');
startBtn.style.display = 'block';
document.body.style.overflow = '';
});
const resize = () => {
canvas.width = canvas.parentElement.clientWidth;
canvas.height = canvas.parentElement.clientHeight;
playerX = canvas.width / 2;
};
const GIFT_TYPES = {
common: { color: '#00ff66', points: 10, speedMult: 1, size: 25, chance: 0.6 },
rare: { color: '#00f2ff', points: 25, speedMult: 1.3, size: 20, chance: 0.3 },
legendary: { color: '#ff00ff', points: 50, speedMult: 1.8, size: 18, chance: 0.1 }
};
const ARTIFACT_TYPES = {
crown: { color: '#ffd700', points: 100, speedMult: 1.5, size: 30, chance: 0.05, shape: 'crown' },
trophy: { color: '#c0c0c0', points: 75, speedMult: 1.3, size: 28, chance: 0.08, shape: 'trophy' },
gem: { color: '#ff69b4', points: 60, speedMult: 1.6, size: 22, chance: 0.1, shape: 'gem' },
star: { color: '#ffff00', points: 40, speedMult: 1.4, size: 20, chance: 0.12, shape: 'star' }
};
class Particle {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.color = color;
this.size = Math.random() * 5 + 2;
this.speedX = (Math.random() - 0.5) * 8;
this.speedY = (Math.random() - 0.5) * 8 - 3;
this.life = 1;
this.decay = Math.random() * 0.02 + 0.02;
}
update() {
this.x += this.speedX;
this.y += this.speedY;
this.life -= this.decay;
}
draw() {
ctx.save();
ctx.globalAlpha = this.life;
ctx.fillStyle = this.color;
ctx.shadowBlur = 10;
ctx.shadowColor = this.color;
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
class Gift {
constructor() {
const rand = Math.random();
let type;
if (rand < 0.6) type = 'common';
else if (rand < 0.9) type = 'rare';
else type = 'legendary';
const typeData = GIFT_TYPES[type];
this.x = Math.random() * (canvas.width - 50) + 25;
this.y = -60;
this.size = typeData.size;
this.baseSpeed = (3 + Math.random() * 3) * typeData.speedMult * difficultyMultiplier;
this.speed = this.baseSpeed;
this.color = typeData.color;
this.points = typeData.points;
this.rotation = 0;
this.rotationSpeed = (Math.random() - 0.5) * 0.1;
this.type = type;
}
update() {
this.y += this.speed;
this.rotation += this.rotationSpeed;
}
draw() {
ctx.save();
ctx.translate(this.x + this.size/2, this.y + this.size/2);
ctx.rotate(this.rotation);
ctx.shadowBlur = 20;
ctx.shadowColor = this.color;
ctx.fillStyle = this.color;
ctx.fillRect(-this.size/2, -this.size/2, this.size, this.size);
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
ctx.strokeRect(-this.size/2, -this.size/2, this.size, this.size);
// Glow effect for legendary
if (this.type === 'legendary') {
ctx.beginPath();
ctx.arc(0, 0, this.size * 0.8, 0, Math.PI * 2);
ctx.strokeStyle = '#fff';
ctx.lineWidth = 1;
ctx.stroke();
}
ctx.restore();
}
}
class Artifact {
constructor() {
const types = Object.keys(ARTIFACT_TYPES);
const rand = Math.random();
let selectedType = 'star';
let cumulative = 0;
for (const type of types) {
cumulative += ARTIFACT_TYPES[type].chance;
if (rand < cumulative) {
selectedType = type;
break;
}
}
const typeData = ARTIFACT_TYPES[selectedType];
this.x = Math.random() * (canvas.width - 60) + 30;
this.y = -70;
this.size = typeData.size;
this.baseSpeed = (4 + Math.random() * 4) * typeData.speedMult * difficultyMultiplier;
this.speed = this.baseSpeed;
this.color = typeData.color;
this.points = typeData.points;
this.rotation = 0;
this.rotationSpeed = (Math.random() - 0.5) * 0.08;
this.type = selectedType;
this.shape = typeData.shape;
this.wobble = 0;
this.wobbleSpeed = 0.05;
}
update() {
this.y += this.speed;
this.rotation += this.rotationSpeed;
this.wobble += this.wobbleSpeed;
}
draw() {
ctx.save();
ctx.translate(this.x + this.size/2, this.y + this.size/2);
ctx.rotate(this.rotation);
ctx.translate(0, Math.sin(this.wobble) * 3);
ctx.shadowBlur = 25;
ctx.shadowColor = this.color;
ctx.fillStyle = this.color;
ctx.strokeStyle = '#fff';
ctx.lineWidth = 2;
const s = this.size;
switch(this.shape) {
case 'crown':
this.drawCrown(s);
break;
case 'trophy':
this.drawTrophy(s);
break;
case 'gem':
this.drawGem(s);
break;
case 'star':
this.drawStar(s);
break;
}
ctx.restore();
}
drawCrown(s) {
ctx.beginPath();
const base = s * 0.3;
const height = s * 0.5;
ctx.moveTo(-s/2, base);
ctx.lineTo(-s/2 + s/6, -height/2);
ctx.lineTo(-s/6, 0);
ctx.lineTo(0, -height);
ctx.lineTo(s/6, 0);
ctx.lineTo(s/2 - s/6, -height/2);
ctx.lineTo(s/2, base);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.fillStyle = '#fff';
ctx.beginPath();
ctx.arc(0, -height/3, s/12, 0, Math.PI * 2);
ctx.fill();
}
drawTrophy(s) {
const baseW = s * 0.5;
const baseH = s * 0.2;
const cupW = s * 0.4;
const cupH = s * 0.4;
ctx.fillRect(-baseW/2, s/2 - baseH, baseW, baseH);
ctx.strokeRect(-baseW/2, s/2 - baseH, baseW, baseH);
ctx.beginPath();
ctx.moveTo(-cupW/2, s/2 - baseH);
ctx.lineTo(-cupW/2, s/2 - baseH - cupH);
ctx.quadraticCurveTo(0, s/2 - baseH - cupH - cupW/2, cupW/2, s/2 - baseH - cupH);
ctx.lineTo(cupW/2, s/2 - baseH);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(-cupW/3, s/2 - baseH - cupH/2);
ctx.lineTo(cupW/3, s/2 - baseH - cupH/2);
ctx.stroke();
}
drawGem(s) {
const h = s * 0.6;
ctx.beginPath();
ctx.moveTo(0, -h);
ctx.lineTo(s/2, -h/4);
ctx.lineTo(s/3, h/2);
ctx.lineTo(0, h/3);
ctx.lineTo(-s/3, h/2);
ctx.lineTo(-s/2, -h/4);
ctx.closePath();
ctx.fill();
ctx.stroke();
ctx.strokeStyle = 'rgba(255,255,255,0.5)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(0, -h);
ctx.lineTo(0, h/3);
ctx.moveTo(-s/2, -h/4);
ctx.lineTo(s/3, h/2);
ctx.moveTo(s/2, -h/4);
ctx.lineTo(-s/3, h/2);
ctx.stroke();
}
drawStar(s) {
const spikes = 5;
const outerRadius = s/2;
const innerRadius = s/4;
ctx.beginPath();
for(let i = 0; i < spikes * 2; i++) {
const radius = i % 2 === 0 ? outerRadius : innerRadius;
const angle = (i * Math.PI / spikes) - Math.PI / 2;
const x = Math.cos(angle) * radius;
const y = Math.sin(angle) * radius;
if(i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.closePath();
ctx.fill();
ctx.stroke();
}
}
const createExplosion = (x, y, color) => {
for (let i = 0; i < 15; i++) {
particles.push(new Particle(x, y, color));
}
};
const drawPlayer = () => {
ctx.save();
const pulse = Math.sin(Date.now() * 0.005) * 0.2 + 0.8;
ctx.globalAlpha = pulse;
ctx.fillStyle = 'rgba(0, 255, 102, 0.3)';
ctx.strokeStyle = '#00ff66';
ctx.lineWidth = 3;
ctx.shadowBlur = 20;
ctx.shadowColor = '#00ff66';
ctx.beginPath();
ctx.roundRect(playerX - playerWidth / 2, canvas.height - playerBottomOffset, playerWidth, playerHeight, 8);
ctx.fill();
ctx.stroke();
ctx.restore();
};
const updateGame = () => {
if (!gameActive) return;
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw grid background
ctx.save();
ctx.strokeStyle = 'rgba(0, 255, 102, 0.05)';
ctx.lineWidth = 1;
for (let x = 0; x < canvas.width; x += 30) {
ctx.beginPath();
ctx.moveTo(x, 0);
ctx.lineTo(x, canvas.height);
ctx.stroke();
}
for (let y = 0; y < canvas.height; y += 30) {
ctx.beginPath();
ctx.moveTo(0, y);
ctx.lineTo(canvas.width, y);
ctx.stroke();
}
ctx.restore();
drawPlayer();
// Update & Draw Particles
particles = particles.filter(p => p.life > 0);
particles.forEach(p => {
p.update();
p.draw();
});
// Spawn gifts with increasing difficulty
const spawnRate = 0.02 + (difficultyMultiplier - 1) * 0.01;
if (Math.random() < spawnRate) gifts.push(new Gift());
// Spawn rare artifacts
const artifactSpawnRate = 0.005 + (difficultyMultiplier - 1) * 0.002;
if (Math.random() < artifactSpawnRate) gifts.push(new Artifact());
gifts.forEach((gift, index) => {
gift.update();
gift.draw();
// Collision detection
if (gift.y + gift.size > canvas.height - playerBottomOffset - playerHeight / 2 &&
gift.x > playerX - playerWidth / 2 - 10 && gift.x < playerX + playerWidth / 2 + 10) {
// Combo system
const now = Date.now();
if (now - lastCatchTime < 1000) {
combo++;
} else {
combo = 1;
}
lastCatchTime = now;
const comboMultiplier = 1 + (combo - 1) * 0.2;
const finalPoints = Math.floor(gift.points * comboMultiplier);
score += finalPoints;
scoreEl.innerText = score;
createExplosion(gift.x + gift.size/2, gift.y + gift.size/2, gift.color);
gifts.splice(index, 1);
// Increase difficulty
difficultyMultiplier = 1 + (score / 500);
} else if (gift.y > canvas.height) {
gifts.splice(index, 1);
combo = 0;
}
});
// Display combo
if (combo > 1) {
ctx.save();
ctx.fillStyle = '#ff00ff';
ctx.font = 'bold 24px Orbitron';
ctx.textAlign = 'center';
ctx.shadowBlur = 15;
ctx.shadowColor = '#ff00ff';
ctx.fillText(`${combo}x COMBO!`, playerX, canvas.height - 50);
ctx.restore();
}
requestAnimationFrame(updateGame);
};
updateLeaderboardUI();
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
playerX = e.clientX - rect.left;
playerX = Math.max(playerWidth / 2, Math.min(canvas.width - playerWidth / 2, playerX));
});
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const touch = e.touches[0];
playerX = touch.clientX - rect.left;
playerX = Math.max(playerWidth / 2, Math.min(canvas.width - playerWidth / 2, playerX));
}, { passive: false });
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const touch = e.touches[0];
playerX = touch.clientX - rect.left;
playerX = Math.max(playerWidth / 2, Math.min(canvas.width - playerWidth / 2, playerX));
}, { passive: false });
canvas.addEventListener('touchend', (e) => {
e.preventDefault();
}, { passive: false });
canvas.addEventListener('touchcancel', (e) => {
e.preventDefault();
}, { passive: false });
startBtn.addEventListener('click', () => {
gameActive = true;
score = 0;
scoreEl.innerText = score;
gifts = [];
particles = [];
difficultyMultiplier = 1;
combo = 0;
lastCatchTime = 0;
startBtn.style.display = 'none';
document.getElementById('gameUI').style.top = '40px';
document.getElementById('gameUI').style.transform = 'translateX(-50%)';
document.body.style.overflow = 'hidden';
startTimer();
updateGame();
});
document.addEventListener('touchmove', (e) => {
if (gameActive) {
const gameCanvas = document.getElementById('gameCanvas');
if (!gameCanvas.contains(e.target)) {
e.preventDefault();
}
}
}, { passive: false });
window.addEventListener('resize', resize);
resize();
}
// Export/Import Leaderboard
window.exportLeaderboard = () => {
const leaderboard = JSON.parse(localStorage.getItem('trae_leaderboard') || '[]');
const dataStr = JSON.stringify(leaderboard, null, 2);
const blob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `trae_leaderboard_${new Date().toISOString().split('T')[0]}.json`;
a.click();
URL.revokeObjectURL(url);
};
window.importLeaderboard = (event) => {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = JSON.parse(e.target.result);
if (Array.isArray(data)) {
localStorage.setItem('trae_leaderboard', JSON.stringify(data));
location.reload();
}
} catch (err) {
alert('Invalid file format');
}
};
reader.readAsText(file);
};
// --- 5. Traoom - Bug Hunter Game ---
function initTraoom() {
const canvas = document.getElementById('traoomCanvas');
const ctx = canvas.getContext('2d');
const startBtn = document.getElementById('startTraoomBtn');
const restartBtn = document.getElementById('restartTraoomBtn');
const killEl = document.getElementById('killVal');
const timeEl = document.getElementById('traoomTime');
const waveEl = document.getElementById('waveVal');
const overlay = document.getElementById('traoomOverlay');
const finalKillsEl = document.getElementById('finalKills');
const finalTimeEl = document.getElementById('finalTime');
let gameActive = false;
let kills = 0;
let wave = 1;
let gameTime = 0;
let timerInterval = null;
let lastShot = 0;
const SHOOT_COOLDOWN = 200;
const keys = {};
const mouse = { x: 0, y: 0 };
const resize = () => {
canvas.width = canvas.parentElement.clientWidth;
canvas.height = canvas.parentElement.clientHeight;
};
const drawRobloxBug = (x, y, size, color, health, maxHealth) => {
ctx.save();
const scale = size / 40;
ctx.translate(x, y);
ctx.fillStyle = color;
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 2;
const pulse = Math.sin(Date.now() * 0.01) * 0.1 + 1;
ctx.scale(pulse, pulse);
ctx.beginPath();
ctx.roundRect(-20 * scale, -20 * scale, 40 * scale, 40 * scale, 5 * scale);
ctx.fill();
ctx.stroke();
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(-8 * scale, -5 * scale, 4 * scale, 0, Math.PI * 2);
ctx.arc(8 * scale, -5 * scale, 4 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ff0000';
ctx.beginPath();
ctx.arc(-8 * scale, -5 * scale, 2 * scale, 0, Math.PI * 2);
ctx.arc(8 * scale, -5 * scale, 2 * scale, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 3;
ctx.beginPath();
ctx.moveTo(-10 * scale, 8 * scale);
ctx.lineTo(10 * scale, 8 * scale);
ctx.stroke();
const healthPercent = health / maxHealth;
ctx.fillStyle = 'rgba(255, 0, 0, 0.7)';
ctx.fillRect(-15 * scale, -30 * scale, 30 * scale, 5 * scale);
ctx.fillStyle = '#00ff66';
ctx.fillRect(-15 * scale, -30 * scale, 30 * scale * healthPercent, 5 * scale);
ctx.restore();
};
const drawPlayer = (x, y, size) => {
ctx.save();
ctx.translate(x, y);
const angle = Math.atan2(mouse.y - y, mouse.x - x);
ctx.rotate(angle);
ctx.fillStyle = '#00ff66';
ctx.strokeStyle = '#ffffff';
ctx.lineWidth = 2;
ctx.beginPath();
ctx.roundRect(-size / 2, -size / 2, size, size, 5);
ctx.fill();
ctx.stroke();
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(3, -3, 4, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#00f2ff';
ctx.fillRect(size / 2 - 5, -3, 15, 6);
ctx.restore();
};
const drawBullet = (x, y, size) => {
ctx.save();
ctx.fillStyle = '#00f2ff';
ctx.shadowColor = '#00f2ff';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(x, y, size, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
};
const drawParticle = (p) => {
ctx.save();
ctx.globalAlpha = p.life / p.maxLife;
ctx.fillStyle = p.color;
ctx.beginPath();
ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
};
class Bug {
constructor() {
const side = Math.floor(Math.random() * 4);
switch(side) {
case 0:
this.x = -30;
this.y = Math.random() * canvas.height;
break;
case 1:
this.x = canvas.width + 30;
this.y = Math.random() * canvas.height;
break;
case 2:
this.x = Math.random() * canvas.width;
this.y = -30;
break;
case 3:
this.x = Math.random() * canvas.width;
this.y = canvas.height + 30;
break;
}
this.size = 30 + Math.random() * 20;
this.speed = 1 + Math.random() * (1 + wave * 0.3);
this.health = 2 + Math.floor(wave * 0.5);
this.maxHealth = this.health;
const bugTypes = [
{ color: '#ff0000', name: 'SyntaxError' },
{ color: '#ff6600', name: 'TypeError' },
{ color: '#ff00ff', name: 'LogicBug' },
{ color: '#ffff00', name: 'MemoryLeak' },
{ color: '#00ffff', name: 'NullPointer' }
];
this.type = bugTypes[Math.floor(Math.random() * bugTypes.length)];
}
update(playerX, playerY) {
const dx = playerX - this.x;
const dy = playerY - this.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
this.x += (dx / dist) * this.speed;
this.y += (dy / dist) * this.speed;
}
}
draw() {
drawRobloxBug(this.x, this.y, this.size, this.type.color, this.health, this.maxHealth);
}
takeDamage(amount) {
this.health -= amount;
return this.health <= 0;
}
}
class Bullet {
constructor(x, y, targetX, targetY) {
this.x = x;
this.y = y;
const dx = targetX - x;
const dy = targetY - y;
const dist = Math.sqrt(dx * dx + dy * dy);
this.vx = (dx / dist) * 12;
this.vy = (dy / dist) * 12;
this.size = 5;
this.life = 60;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.life--;
return this.life > 0 &&
this.x > 0 && this.x < canvas.width &&
this.y > 0 && this.y < canvas.height;
}
draw() {
drawBullet(this.x, this.y, this.size);
}
}
class Particle {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.vx = (Math.random() - 0.5) * 8;
this.vy = (Math.random() - 0.5) * 8;
this.size = 3 + Math.random() * 4;
this.color = color;
this.life = 30;
this.maxLife = 30;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.vy += 0.2;
this.life--;
return this.life > 0;
}
draw() {
drawParticle(this);
}
}
let player = { x: 0, y: 0, size: 30, speed: 5, health: 100 };
let bugs = [];
let bullets = [];
let particles = [];
let spawnTimer = 0;
let spawnRate = 120;
const spawnBug = () => {
bugs.push(new Bug());
};
const createExplosion = (x, y, color) => {
for (let i = 0; i < 10; i++) {
particles.push(new Particle(x, y, color));
}
};
const checkCollisions = () => {
bullets.forEach((bullet, bi) => {
bugs.forEach((bug, gi) => {
const dx = bullet.x - bug.x;
const dy = bullet.y - bug.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < bug.size / 2 + bullet.size) {
bullets.splice(bi, 1);
if (bug.takeDamage(1)) {
bugs.splice(gi, 1);
kills++;
killEl.innerText = kills;
createExplosion(bug.x, bug.y, bug.type.color);
if (kills % 10 === 0) {
wave++;
waveEl.innerText = wave;
spawnRate = Math.max(30, 120 - wave * 10);
}
} else {
createExplosion(bullet.x, bullet.y, '#ffffff');
}
}
});
});
bugs.forEach((bug) => {
const dx = player.x - bug.x;
const dy = player.y - bug.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < player.size / 2 + bug.size / 2) {
player.health -= 10;
createExplosion(player.x, player.y, '#ff0000');
if (player.health <= 0) {
gameOver();
}
}
});
};
const updateGame = () => {
if (!gameActive) return;
ctx.fillStyle = 'rgba(5, 11, 20, 0.3)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (keys['KeyW'] || keys['ArrowUp']) player.y = Math.max(player.size / 2, player.y - player.speed);
if (keys['KeyS'] || keys['ArrowDown']) player.y = Math.min(canvas.height - player.size / 2, player.y + player.speed);
if (keys['KeyA'] || keys['ArrowLeft']) player.x = Math.max(player.size / 2, player.x - player.speed);
if (keys['KeyD'] || keys['ArrowRight']) player.x = Math.min(canvas.width - player.size / 2, player.x + player.speed);
drawPlayer(player.x, player.y, player.size);
spawnTimer++;
if (spawnTimer >= spawnRate) {
spawnBug();
spawnTimer = 0;
}
bullets = bullets.filter(b => b.update());
bullets.forEach(b => b.draw());
bugs.forEach(bug => {
bug.update(player.x, player.y);
bug.draw();
});
particles = particles.filter(p => p.update());
particles.forEach(p => p.draw());
checkCollisions();
requestAnimationFrame(updateGame);
};
const formatTime = (seconds) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}:${secs.toString().padStart(2, '0')}`;
};
const startTimer = () => {
gameTime = 0;
timerInterval = setInterval(() => {
gameTime++;
timeEl.innerText = formatTime(gameTime);
}, 1000);
};
const gameOver = () => {
gameActive = false;
clearInterval(timerInterval);
finalKillsEl.innerText = kills;
finalTimeEl.innerText = formatTime(gameTime);
overlay.classList.remove('hidden');
};
const startGame = () => {
gameActive = true;
kills = 0;
wave = 1;
gameTime = 0;
player = { x: canvas.width / 2, y: canvas.height / 2, size: 30, speed: 5, health: 100 };
bugs = [];
bullets = [];
particles = [];
spawnTimer = 0;
spawnRate = 120;
killEl.innerText = '0';
waveEl.innerText = '1';
timeEl.innerText = '0:00';
overlay.classList.add('hidden');
startTimer();
updateGame();
};
window.addEventListener('keydown', (e) => {
keys[e.code] = true;
if (['ArrowUp', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'Space'].includes(e.code)) {
e.preventDefault();
}
});
window.addEventListener('keyup', (e) => {
keys[e.code] = false;
});
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
mouse.x = e.clientX - rect.left;
mouse.y = e.clientY - rect.top;
});
canvas.addEventListener('click', (e) => {
if (!gameActive) return;
const now = Date.now();
if (now - lastShot >= SHOOT_COOLDOWN) {
bullets.push(new Bullet(player.x, player.y, mouse.x, mouse.y));
lastShot = now;
}
});
canvas.addEventListener('touchstart', (e) => {
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const touch = e.touches[0];
mouse.x = touch.clientX - rect.left;
mouse.y = touch.clientY - rect.top;
const now = Date.now();
if (now - lastShot >= SHOOT_COOLDOWN) {
bullets.push(new Bullet(player.x, player.y, mouse.x, mouse.y));
lastShot = now;
}
}, { passive: false });
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
const rect = canvas.getBoundingClientRect();
const touch = e.touches[0];
mouse.x = touch.clientX - rect.left;
mouse.y = touch.clientY - rect.top;
}, { passive: false });
startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);
window.addEventListener('resize', resize);
resize();
}
// --- 6. Neon Puzzle Game ---
function initNeonPuzzle() {
const canvas = document.getElementById('neonpuzzleCanvas');
const ctx = canvas.getContext('2d');
const startBtn = document.getElementById('startPuzzleBtn');
const nextBtn = document.getElementById('nextPuzzleBtn');
const levelEl = document.getElementById('puzzleLevel');
const movesEl = document.getElementById('puzzleMoves');
const overlay = document.getElementById('neonpuzzleOverlay');
const finalMovesEl = document.getElementById('finalMoves');
let gameActive = false;
let level = 1;
let moves = 0;
let nodes = [];
let connections = [];
const resize = () => {
canvas.width = canvas.parentElement.clientWidth;
canvas.height = canvas.parentElement.clientHeight;
};
class Node {
constructor(x, y) {
this.x = x;
this.y = y;
this.connections = [];
this.angle = 0;
}
draw() {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.angle);
ctx.fillStyle = '#ff0066';
ctx.shadowColor = '#ff0066';
ctx.shadowBlur = 15;
ctx.beginPath();
ctx.arc(0, 0, 20, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.shadowBlur = 0;
ctx.beginPath();
ctx.arc(0, 0, 8, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
rotate() {
this.angle += Math.PI / 4;
moves++;
movesEl.innerText = moves;
}
}
const generateLevel = () => {
nodes = [];
connections = [];
moves = 0;
movesEl.innerText = '0';
const nodeCount = 4 + level;
const positions = [];
for (let i = 0; i < nodeCount; i++) {
let x, y;
let valid = false;
let attempts = 0;
while (!valid && attempts < 100) {
x = 100 + Math.random() * (canvas.width - 200);
y = 100 + Math.random() * (canvas.height - 200);
valid = true;
for (const pos of positions) {
const dist = Math.sqrt((x - pos.x) ** 2 + (y - pos.y) ** 2);
if (dist < 80) valid = false;
}
attempts++;
}
if (valid) {
positions.push({ x, y });
nodes.push(new Node(x, y));
}
}
for (let i = 0; i < nodes.length - 1; i++) {
connections.push([i, i + 1]);
nodes[i].connections.push(i + 1);
nodes[i + 1].connections.push(i);
}
connections.push([nodes.length - 1, 0]);
nodes[0].connections.push(nodes.length - 1);
nodes[nodes.length - 1].connections.push(0);
};
const checkWin = () => {
for (const [from, to] of connections) {
const nodeA = nodes[from];
const nodeB = nodes[to];
const targetAngle = Math.atan2(nodeB.y - nodeA.y, nodeB.x - nodeA.x);
let currentAngle = nodeA.angle % (Math.PI * 2);
if (currentAngle < 0) currentAngle += Math.PI * 2;
let diff = Math.abs(targetAngle - currentAngle);
if (diff > Math.PI) diff = Math.PI * 2 - diff;
if (diff > 0.3) return false;
}
return true;
};
const updateGame = () => {
if (!gameActive) return;
ctx.fillStyle = 'rgba(5, 11, 20, 0.3)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
connections.forEach(([from, to]) => {
const nodeA = nodes[from];
const nodeB = nodes[to];
const aligned = Math.abs(nodeA.angle - Math.atan2(nodeB.y - nodeA.y, nodeB.x - nodeA.x)) < 0.3;
ctx.strokeStyle = aligned ? '#00ff66' : '#ff6600';
ctx.lineWidth = 3;
ctx.shadowColor = aligned ? '#00ff66' : '#ff6600';
ctx.shadowBlur = aligned ? 20 : 10;
ctx.beginPath();
ctx.moveTo(nodeA.x, nodeA.y);
ctx.lineTo(nodeB.x, nodeB.y);
ctx.stroke();
});
nodes.forEach(node => node.draw());
requestAnimationFrame(updateGame);
};
const startGame = () => {
gameActive = true;
level = 1;
levelEl.innerText = level;
generateLevel();
overlay.classList.add('hidden');
updateGame();
};
const nextLevel = () => {
level++;
levelEl.innerText = level;
generateLevel();
overlay.classList.add('hidden');
};
canvas.addEventListener('click', (e) => {
if (!gameActive) return;
const rect = canvas.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
for (const node of nodes) {
const dist = Math.sqrt((x - node.x) ** 2 + (y - node.y) ** 2);
if (dist < 25) {
node.rotate();
break;
}
}
if (checkWin()) {
gameActive = false;
finalMovesEl.innerText = moves;
overlay.classList.remove('hidden');
}
});
startBtn.addEventListener('click', startGame);
nextBtn.addEventListener('click', nextLevel);
window.addEventListener('resize', resize);
resize();
}
// --- 7. Rhythm Beat Game ---
function initRhythmBeat() {
const canvas = document.getElementById('rhythmCanvas');
const ctx = canvas.getContext('2d');
const startBtn = document.getElementById('startRhythmBtn');
const restartBtn = document.getElementById('restartRhythmBtn');
const scoreEl = document.getElementById('rhythmScore');
const comboEl = document.getElementById('rhythmCombo');
const streakEl = document.getElementById('rhythmStreak');
const overlay = document.getElementById('rhythmOverlay');
const finalScoreEl = document.getElementById('finalRhythmScore');
let gameActive = false;
let score = 0;
let combo = 0;
let streak = 0;
let beats = [];
let beatInterval = 0;
let totalBeats = 0;
let maxBeats = 50;
let keyFeedback = { D: 0, F: 0, J: 0, K: 0 };
const keyLabels = ['D', 'F', 'J', 'K'];
const keyColors = ['#ff0066', '#ff6600', '#00ff66', '#00f2ff'];
const laneX = [];
const resize = () => {
canvas.width = canvas.parentElement.clientWidth;
canvas.height = canvas.parentElement.clientHeight;
const laneWidth = canvas.width / 4;
for (let i = 0; i < 4; i++) {
laneX[i] = laneWidth * i + laneWidth / 2;
}
};
const drawKeyIndicators = () => {
keyLabels.forEach((label, i) => {
const x = laneX[i];
const y = canvas.height - 50;
const feedback = keyFeedback[label];
ctx.save();
ctx.strokeStyle = keyColors[i];
ctx.shadowColor = keyColors[i];
ctx.shadowBlur = 15 + feedback * 20;
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(x, y, 30, 0, Math.PI * 2);
ctx.stroke();
ctx.fillStyle = feedback > 0 ? keyColors[i] : 'rgba(255,255,255,0.3)';
ctx.shadowBlur = feedback * 20;
ctx.beginPath();
ctx.arc(x, y, 25, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.shadowBlur = 0;
ctx.font = 'bold 20px Arial';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(label, x, y);
ctx.restore();
if (feedback > 0) keyFeedback[label] -= 0.1;
});
};
const drawBeat = (beat) => {
const x = laneX[beat.lane];
const y = canvas.height - 50 - beat.y;
const progress = beat.y / (canvas.height - 100);
ctx.save();
ctx.strokeStyle = keyColors[beat.lane];
ctx.shadowColor = keyColors[beat.lane];
ctx.shadowBlur = 20;
ctx.lineWidth = 4;
ctx.beginPath();
ctx.arc(x, y, 20, 0, Math.PI * 2);
ctx.stroke();
ctx.fillStyle = keyColors[beat.lane];
ctx.beginPath();
ctx.arc(x, y, 12, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
};
const spawnBeat = () => {
if (totalBeats >= maxBeats) return;
const lane = Math.floor(Math.random() * 4);
beats.push({ y: 0, lane: lane, hit: false });
totalBeats++;
};
const updateGame = () => {
if (!gameActive) return;
ctx.fillStyle = 'rgba(5, 11, 20, 0.3)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let i = 0; i < 4; i++) {
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
ctx.lineWidth = 1;
ctx.beginPath();
ctx.moveTo(laneX[i], 0);
ctx.lineTo(laneX[i], canvas.height);
ctx.stroke();
}
beatInterval++;
if (beatInterval % 40 === 0) {
spawnBeat();
}
beats = beats.filter(beat => {
if (beat.hit) return false;
beat.y += 4;
drawBeat(beat);
if (beat.y > canvas.height - 50) {
combo = 0;
streak = 0;
comboEl.innerText = '0';
streakEl.innerText = '0';
return false;
}
return true;
});
drawKeyIndicators();
if (totalBeats >= maxBeats && beats.length === 0) {
gameActive = false;
finalScoreEl.innerText = score;
overlay.classList.remove('hidden');
}
requestAnimationFrame(updateGame);
};
const startGame = () => {
gameActive = true;
score = 0;
combo = 0;
streak = 0;
beats = [];
beatInterval = 0;
totalBeats = 0;
keyFeedback = { D: 0, F: 0, J: 0, K: 0 };
scoreEl.innerText = '0';
comboEl.innerText = '0';
streakEl.innerText = '0';
overlay.classList.add('hidden');
updateGame();
};
window.addEventListener('keydown', (e) => {
if (!gameActive) return;
const keyIdx = keyLabels.indexOf(e.code.replace('Key', ''));
if (keyIdx === -1) return;
keyFeedback[keyLabels[keyIdx]] = 1;
for (const beat of beats) {
if (beat.lane === keyIdx && !beat.hit) {
const distance = Math.abs(beat.y - (canvas.height - 100));
if (distance < 40) {
beat.hit = true;
if (distance < 15) {
score += 100 + combo * 20;
combo++;
streak++;
} else if (distance < 25) {
score += 50 + combo * 10;
combo++;
streak++;
} else {
score += 20;
combo = 0;
}
scoreEl.innerText = score;
comboEl.innerText = combo;
streakEl.innerText = streak;
break;
}
}
}
});
startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);
window.addEventListener('resize', resize);
resize();
}
// --- 8. Cosmic Arena Game ---
function initCosmicArena() {
const canvas = document.getElementById('arenaCanvas');
const ctx = canvas.getContext('2d');
const startBtn = document.getElementById('startArenaBtn');
const restartBtn = document.getElementById('restartArenaBtn');
const hpEl = document.getElementById('arenaHP');
const killsEl = document.getElementById('arenaKills');
const waveEl = document.getElementById('arenaWave');
const overlay = document.getElementById('arenaOverlay');
const finalKillsEl = document.getElementById('finalArenaKills');
let gameActive = false;
let player = { x: 0, y: 0, hp: 100, speed: 4, size: 30 };
let enemies = [];
let bullets = [];
let particles = [];
let kills = 0;
let wave = 1;
let enemySpawnTimer = 0;
let spawnRate = 90;
const keys = {};
const mouse = { x: 0, y: 0 };
const resize = () => {
canvas.width = canvas.parentElement.clientWidth;
canvas.height = canvas.parentElement.clientHeight;
};
const drawPlayer = () => {
ctx.save();
ctx.translate(player.x, player.y);
const angle = Math.atan2(mouse.y - player.y, mouse.x - player.x);
ctx.rotate(angle);
ctx.fillStyle = '#00ffcc';
ctx.shadowColor = '#00ffcc';
ctx.shadowBlur = 15;
ctx.beginPath();
ctx.moveTo(20, 0);
ctx.lineTo(-15, -10);
ctx.lineTo(-15, 10);
ctx.closePath();
ctx.fill();
ctx.restore();
};
const drawEnemy = (enemy) => {
ctx.save();
ctx.translate(enemy.x, enemy.y);
ctx.fillStyle = enemy.type;
ctx.shadowColor = enemy.type;
ctx.shadowBlur = 10;
const pulse = Math.sin(Date.now() * 0.01) * 0.2 + 1;
ctx.scale(pulse, pulse);
ctx.beginPath();
for (let i = 0; i < 6; i++) {
const angle = (i / 6) * Math.PI * 2;
const x = Math.cos(angle) * enemy.size;
const y = Math.sin(angle) * enemy.size;
if (i === 0) ctx.moveTo(x, y);
else ctx.lineTo(x, y);
}
ctx.closePath();
ctx.fill();
ctx.restore();
};
const drawBullet = (bullet) => {
ctx.save();
ctx.fillStyle = '#ffffff';
ctx.shadowColor = '#ffffff';
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(bullet.x, bullet.y, 4, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
};
class Particle {
constructor(x, y, color) {
this.x = x;
this.y = y;
this.color = color;
this.vx = (Math.random() - 0.5) * 6;
this.vy = (Math.random() - 0.5) * 6;
this.life = 1;
this.decay = 0.03 + Math.random() * 0.02;
}
update() {
this.x += this.vx;
this.y += this.vy;
this.life -= this.decay;
}
draw() {
ctx.save();
ctx.globalAlpha = this.life;
ctx.fillStyle = this.color;
ctx.shadowColor = this.color;
ctx.shadowBlur = 10;
ctx.beginPath();
ctx.arc(this.x, this.y, 3, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
}
}
const createExplosion = (x, y, color) => {
for (let i = 0; i < 15; i++) {
particles.push(new Particle(x, y, color));
}
};
const spawnEnemy = () => {
const side = Math.floor(Math.random() * 4);
let x, y;
switch(side) {
case 0: x = -30; y = Math.random() * canvas.height; break;
case 1: x = canvas.width + 30; y = Math.random() * canvas.height; break;
case 2: x = Math.random() * canvas.width; y = -30; break;
case 3: x = Math.random() * canvas.width; y = canvas.height + 30; break;
}
const types = ['#ff0066', '#ff6600', '#ffcc00'];
enemies.push({
x, y,
size: 15 + Math.random() * 10,
speed: 1 + wave * 0.2,
hp: 2 + Math.floor(wave * 0.5),
type: types[Math.floor(Math.random() * types.length)]
});
};
const updateGame = () => {
if (!gameActive) return;
ctx.fillStyle = 'rgba(5, 11, 20, 0.2)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (keys['KeyW'] || keys['ArrowUp']) player.y = Math.max(player.size, player.y - player.speed);
if (keys['KeyS'] || keys['ArrowDown']) player.y = Math.min(canvas.height - player.size, player.y + player.speed);
if (keys['KeyA'] || keys['ArrowLeft']) player.x = Math.max(player.size, player.x - player.speed);
if (keys['KeyD'] || keys['ArrowRight']) player.x = Math.min(canvas.width - player.size, player.x + player.speed);
drawPlayer();
enemySpawnTimer++;
if (enemySpawnTimer >= spawnRate) {
spawnEnemy();
enemySpawnTimer = 0;
}
enemies = enemies.filter(enemy => {
const dx = player.x - enemy.x;
const dy = player.y - enemy.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist > 0) {
enemy.x += (dx / dist) * enemy.speed;
enemy.y += (dy / dist) * enemy.speed;
}
if (dist < player.size + enemy.size) {
player.hp -= 10;
hpEl.innerText = Math.max(0, player.hp);
if (player.hp <= 0) {
gameActive = false;
finalKillsEl.innerText = kills;
overlay.classList.remove('hidden');
}
}
drawEnemy(enemy);
return true;
});
bullets = bullets.filter(bullet => {
bullet.x += bullet.vx;
bullet.y += bullet.vy;
for (let i = enemies.length - 1; i >= 0; i--) {
const enemy = enemies[i];
const dx = bullet.x - enemy.x;
const dy = bullet.y - enemy.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < enemy.size) {
enemy.hp--;
createExplosion(bullet.x, bullet.y, '#ffffff');
if (enemy.hp <= 0) {
createExplosion(enemy.x, enemy.y, enemy.type);
enemies.splice(i, 1);
kills++;
killsEl.innerText = kills;
if (kills % 5 === 0) {
wave++;
waveEl.innerText = wave;
spawnRate = Math.max(30, 90 - wave * 5);
}
}
return false;
}
}
if (bullet.x < 0 || bullet.x > canvas.width || bullet.y < 0 || bullet.y > canvas.height) {
return false;
}
drawBullet(bullet);
return true;
});
particles = particles.filter(p => {
p.update();
p.draw();
return p.life > 0;
});
requestAnimationFrame(updateGame);
};
const startGame = () => {
gameActive = true;
player = { x: canvas.width / 2, y: canvas.height / 2, hp: 100, speed: 4, size: 30 };
enemies = [];
bullets = [];
particles = [];
kills = 0;
wave = 1;
enemySpawnTimer = 0;
spawnRate = 90;
hpEl.innerText = '100';
killsEl.innerText = '0';
waveEl.innerText = '1';
overlay.classList.add('hidden');
updateGame();
};
window.addEventListener('keydown', (e) => keys[e.code] = true);
window.addEventListener('keyup', (e) => keys[e.code] = false);
canvas.addEventListener('mousemove', (e) => {
const rect = canvas.getBoundingClientRect();
mouse.x = e.clientX - rect.left;
mouse.y = e.clientY - rect.top;
});
canvas.addEventListener('click', (e) => {
if (!gameActive) return;
const dx = mouse.x - player.x;
const dy = mouse.y - player.y;
const dist = Math.sqrt(dx * dx + dy * dy);
bullets.push({
x: player.x, y: player.y,
vx: (dx / dist) * 10,
vy: (dy / dist) * 10
});
});
startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);
window.addEventListener('resize', resize);
resize();
}
// --- 9. Crystal Tetris Game ---
function initTetris() {
const canvas = document.getElementById('tetrisCanvas');
const ctx = canvas.getContext('2d');
const startBtn = document.getElementById('startTetrisBtn');
const restartBtn = document.getElementById('restartTetrisBtn');
const scoreEl = document.getElementById('tetrisScore');
const linesEl = document.getElementById('tetrisLines');
const levelEl = document.getElementById('tetrisLevel');
const overlay = document.getElementById('tetrisOverlay');
const finalScoreEl = document.getElementById('finalTetrisScore');
let gameActive = false;
let score = 0;
let lines = 0;
let level = 1;
let grid = [];
let currentPiece = null;
let frameCount = 0;
let animationId = null;
const COLS = 10;
const ROWS = 20;
const BLOCK_SIZE = 25;
const TARGET_FPS = 120;
const FRAME_INTERVAL = 1000 / TARGET_FPS;
let lastTime = 0;
const COLORS = ['#ff0066', '#ff6600', '#ffcc00', '#00ff66', '#00f2ff', '#7000ff', '#ff00ff'];
const SHAPES = [
[[1, 1, 1, 1]],
[[1, 1, 1], [1]],
[[1, 1], [1, 1]],
[[0, 1, 0], [1, 1, 1]],
[[1, 0, 0], [1, 1, 1]],
[[0, 1, 1], [1, 1, 0]],
[[0, 1, 1, 1], [1, 0, 0, 0]]
];
const getDropSpeed = () => {
const framesPerDrop = Math.max(2, 120 - (level - 1) * 10);
return framesPerDrop;
};
const resize = () => {
canvas.width = canvas.parentElement.clientWidth;
canvas.height = canvas.parentElement.clientHeight;
};
const createGrid = () => {
grid = [];
for (let r = 0; r < ROWS; r++) {
grid[r] = [];
for (let c = 0; c < COLS; c++) {
grid[r][c] = 0;
}
}
};
const createPiece = () => {
const shapeIdx = Math.floor(Math.random() * SHAPES.length);
const color = COLORS[shapeIdx];
currentPiece = {
shape: SHAPES[shapeIdx],
color: color,
x: Math.floor(COLS / 2) - 1,
y: 0
};
};
const drawBlock = (x, y, color) => {
ctx.fillStyle = color;
ctx.shadowColor = color;
ctx.shadowBlur = 5;
ctx.fillRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1);
ctx.strokeStyle = 'rgba(255, 255, 255, 0.5)';
ctx.lineWidth = 2;
ctx.strokeRect(x * BLOCK_SIZE, y * BLOCK_SIZE, BLOCK_SIZE - 1, BLOCK_SIZE - 1);
};
const drawGrid = () => {
ctx.fillStyle = 'rgba(5, 11, 20, 0.3)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (let r = 0; r < ROWS; r++) {
for (let c = 0; c < COLS; c++) {
if (grid[r][c]) {
drawBlock(c, r, grid[r][c]);
}
}
}
};
const drawPiece = () => {
if (!currentPiece) return;
currentPiece.shape.forEach((row, r) => {
row.forEach((value, c) => {
if (value) {
drawBlock(currentPiece.x + c, currentPiece.y + r, currentPiece.color);
}
});
});
};
const isValidMove = (piece, offsetX, offsetY) => {
return piece.shape.every((row, r) => {
return row.every((value, c) => {
if (!value) return true;
const newX = piece.x + c + offsetX;
const newY = piece.y + r + offsetY;
return newX >= 0 && newX < COLS && newY < ROWS && newY >= 0 && !grid[newY][newX];
});
});
};
const lockPiece = () => {
currentPiece.shape.forEach((row, r) => {
row.forEach((value, c) => {
if (value) {
grid[currentPiece.y + r][currentPiece.x + c] = currentPiece.color;
}
});
});
clearLines();
createPiece();
if (!isValidMove(currentPiece, 0, 0)) {
gameOver();
}
};
const clearLines = () => {
let linesCleared = 0;
for (let r = ROWS - 1; r >= 0; r--) {
if (grid[r].every(cell => cell !== 0)) {
grid.splice(r, 1);
grid.unshift(Array(COLS).fill(0));
linesCleared++;
}
}
if (linesCleared > 0) {
lines += linesCleared;
score += linesCleared * 100 * linesCleared;
level = Math.floor(lines / 10) + 1;
scoreEl.innerText = score;
linesEl.innerText = lines;
levelEl.innerText = level;
}
};
const updateGame = (timestamp) => {
if (!gameActive) return;
const deltaTime = timestamp - lastTime;
if (deltaTime >= FRAME_INTERVAL) {
lastTime = timestamp;
frameCount++;
const dropSpeed = getDropSpeed();
if (frameCount % dropSpeed === 0) {
if (isValidMove(currentPiece, 0, 1)) {
currentPiece.y++;
} else {
lockPiece();
}
}
}
draw();
animationId = requestAnimationFrame(updateGame);
};
const draw = () => {
drawGrid();
drawPiece();
};
const startGame = () => {
gameActive = true;
score = 0;
lines = 0;
level = 1;
frameCount = 0;
lastTime = performance.now();
createGrid();
createPiece();
scoreEl.innerText = '0';
linesEl.innerText = '0';
levelEl.innerText = '1';
overlay.classList.add('hidden');
if (animationId) cancelAnimationFrame(animationId);
animationId = requestAnimationFrame(updateGame);
};
const gameOver = () => {
gameActive = false;
if (animationId) cancelAnimationFrame(animationId);
finalScoreEl.innerText = score;
overlay.classList.remove('hidden');
};
window.addEventListener('keydown', (e) => {
if (!gameActive || !currentPiece) return;
switch(e.code) {
case 'ArrowLeft':
if (isValidMove(currentPiece, -1, 0)) currentPiece.x--;
break;
case 'ArrowRight':
if (isValidMove(currentPiece, 1, 0)) currentPiece.x++;
break;
case 'ArrowDown':
if (isValidMove(currentPiece, 0, 1)) {
currentPiece.y++;
score += 1;
scoreEl.innerText = score;
if (!isValidMove(currentPiece, 0, 1)) {
lockPiece();
}
}
break;
case 'ArrowUp':
const rotated = currentPiece.shape[0].map((_, i) => currentPiece.shape.map(row => row[i]).reverse());
if (isValidMove({ ...currentPiece, shape: rotated }, 0, 0)) {
currentPiece.shape = rotated;
}
break;
case 'Space':
while (isValidMove(currentPiece, 0, 1)) {
currentPiece.y++;
score += 2;
}
scoreEl.innerText = score;
lockPiece();
break;
}
draw();
});
startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);
window.addEventListener('resize', resize);
resize();
}
// --- 10. Aurora Jumper Platformer Game ---
function initPlatformer() {
const canvas = document.getElementById('platformerCanvas');
const ctx = canvas.getContext('2d');
const startBtn = document.getElementById('startPlatformerBtn');
const restartBtn = document.getElementById('restartPlatformerBtn');
const coinsEl = document.getElementById('coinsVal');
const timeEl = document.getElementById('platformerTime');
const overlay = document.getElementById('platformerOverlay');
const finalCoinsEl = document.getElementById('finalCoins');
const finalTimeEl = document.getElementById('finalPlatformerTime');
let gameActive = false;
let player = { x: 50, y: 300, vx: 0, vy: 0, onGround: false, size: 25 };
let platforms = [];
let coins = [];
let collected = 0;
let gameTime = 0;
let timerInterval = null;
let gravity = 0.5;
let jumpForce = -12;
const keys = {};
const resize = () => {
canvas.width = canvas.parentElement.clientWidth;
canvas.height = canvas.parentElement.clientHeight;
};
const generateLevel = () => {
platforms = [];
coins = [];
const platformCount = 8 + Math.floor(Math.random() * 5);
const jumpHeight = Math.abs(jumpForce * jumpForce / (2 * gravity));
const maxReachableY = jumpHeight * 0.9;
let prevX = 50;
let prevY = canvas.height - 80;
for (let i = 0; i < platformCount; i++) {
let x, y;
if (i === 0) {
x = 30;
y = canvas.height - 80;
} else if (i === platformCount - 1) {
x = canvas.width - 130;
y = prevY - maxReachableY + Math.random() * 20;
} else {
const minDeltaX = 40;
const maxDeltaX = canvas.width / platformCount - 20;
x = prevX + minDeltaX + Math.random() * maxDeltaX;
x = Math.max(50, Math.min(canvas.width - 130, x));
const direction = Math.random() > 0.5 ? 1 : -1;
y = prevY - (maxReachableY * 0.4 + Math.random() * (maxReachableY * 0.5)) * direction;
y = Math.max(80, Math.min(canvas.height - 80, y));
}
const width = 100 + Math.random() * 40;
platforms.push({ x, y, width: Math.floor(width), height: 15, type: i === platformCount - 1 ? 'goal' : 'normal' });
if (i < platformCount - 1 && Math.random() > 0.2) {
coins.push({
x: x + width / 2,
y: y - 30,
size: 12,
collected: false
});
}
prevX = x;
prevY = y;
}
player.x = platforms[0].x + platforms[0].width / 2;
player.y = platforms[0].y - player.size;
player.vx = 0;
player.vy = 0;
};
const drawPlayer = () => {
ctx.save();
ctx.fillStyle = '#00ff00';
ctx.shadowColor = '#00ff00';
ctx.shadowBlur = 15;
ctx.beginPath();
ctx.roundRect(player.x - player.size / 2, player.y - player.size, player.size, player.size, 5);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(player.x + 3, player.y - player.size + 8, 4, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
};
const drawPlatform = (platform) => {
ctx.fillStyle = platform.type === 'goal' ? '#00ff66' : '#0099cc';
ctx.shadowColor = platform.type === 'goal' ? '#00ff00' : '#00f2ff';
ctx.shadowBlur = 10;
ctx.fillRect(platform.x, platform.y, platform.width, platform.height);
if (platform.type === 'goal') {
ctx.fillStyle = '#ffffff';
ctx.font = '12px Orbitron';
ctx.fillText('GOAL', platform.x + platform.width / 2 - 20, platform.y + 12);
}
};
const drawCoin = (coin) => {
if (coin.collected) return;
ctx.save();
const pulse = Math.sin(Date.now() * 0.01) * 0.2 + 1;
ctx.translate(coin.x, coin.y);
ctx.scale(pulse, pulse);
ctx.fillStyle = '#ffcc00';
ctx.shadowColor = '#ffcc00';
ctx.shadowBlur = 15;
ctx.beginPath();
ctx.arc(0, 0, coin.size, 0, Math.PI * 2);
ctx.fill();
ctx.fillStyle = '#ffffff';
ctx.beginPath();
ctx.arc(0, 0, coin.size / 2, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
};
const updateGame = () => {
if (!gameActive) return;
ctx.fillStyle = 'rgba(5, 11, 20, 0.2)';
ctx.fillRect(0, 0, canvas.width, canvas.height);
if (keys['KeyA'] || keys['ArrowLeft']) player.vx = -5;
else if (keys['KeyD'] || keys['ArrowRight']) player.vx = 5;
else player.vx *= 0.8;
if ((keys['Space'] || keys['ArrowUp']) && player.onGround) {
player.vy = jumpForce;
player.onGround = false;
}
player.vy += gravity;
player.x += player.vx;
player.y += player.vy;
player.onGround = false;
platforms.forEach(platform => {
if (player.x + player.size / 2 > platform.x &&
player.x - player.size / 2 < platform.x + platform.width &&
player.y >= platform.y &&
player.y <= platform.y + platform.height + player.vy + 5) {
if (player.vy > 0) {
player.y = platform.y;
player.vy = 0;
player.onGround = true;
}
if (platform.type === 'goal') {
gameActive = false;
clearInterval(timerInterval);
finalCoinsEl.innerText = collected;
finalTimeEl.innerText = timeEl.innerText;
overlay.classList.remove('hidden');
}
}
});
player.x = Math.max(player.size / 2, Math.min(canvas.width - player.size / 2, player.x));
player.y = Math.min(canvas.height, player.y);
coins.forEach(coin => {
if (coin.collected) return;
const dx = player.x - coin.x;
const dy = player.y - coin.y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < player.size + coin.size) {
coin.collected = true;
collected++;
coinsEl.innerText = collected;
}
drawCoin(coin);
});
platforms.forEach(drawPlatform);
drawPlayer();
if (player.y > canvas.height + 50) {
player.x = platforms[0].x + platforms[0].width / 2;
player.y = platforms[0].y - player.size;
player.vx = 0;
player.vy = 0;
collected = Math.max(0, collected - 1);
coinsEl.innerText = collected;
}
requestAnimationFrame(updateGame);
};
const formatTime = (seconds) => {
const mins = Math.floor(seconds / 60);
const secs = seconds % 60;
return `${mins}:${secs.toString().padStart(2, '0')}`;
};
const startTimer = () => {
gameTime = 0;
timerInterval = setInterval(() => {
gameTime++;
timeEl.innerText = formatTime(gameTime);
}, 1000);
};
const startGame = () => {
gameActive = true;
collected = 0;
if (timerInterval) clearInterval(timerInterval);
generateLevel();
coinsEl.innerText = '0';
timeEl.innerText = '0:00';
overlay.classList.add('hidden');
startTimer();
updateGame();
};
window.addEventListener('keydown', (e) => keys[e.code] = true);
window.addEventListener('keyup', (e) => keys[e.code] = false);
startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);
window.addEventListener('resize', resize);
resize();
}
// Export/Import Messages
window.exportMessages = () => {
const messages = JSON.parse(localStorage.getItem('trae_messages') || '[]');
const dataStr = JSON.stringify(messages, null, 2);
const blob = new Blob([dataStr], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `trae_messages_${new Date().toISOString().split('T')[0]}.json`;
a.click();
URL.revokeObjectURL(url);
};
window.importMessages = (event) => {
const file = event.target.files[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => {
try {
const data = JSON.parse(e.target.result);
if (Array.isArray(data)) {
localStorage.setItem('trae_messages', JSON.stringify(data));
location.reload();
}
} catch (err) {
alert('Invalid file format');
}
};
reader.readAsText(file);
};