// ==================== STAR BACKGROUND ==================== (function createStars() { const container = document.getElementById('starsContainer'); const count = 120; for (let i = 0; i < count; i++) { const star = document.createElement('div'); star.classList.add('star'); const size = Math.random() * 3 + 1; star.style.width = size + 'px'; star.style.height = size + 'px'; star.style.left = Math.random() * 100 + '%'; star.style.top = Math.random() * 100 + '%'; star.style.setProperty('--duration', (Math.random() * 3 + 2) + 's'); star.style.setProperty('--delay', (Math.random() * 5) + 's'); container.appendChild(star); } })(); // ==================== HIGH SCORE MANAGEMENT ==================== function getHighScore(game) { return parseInt(localStorage.getItem('arcade_' + game + '_highscore')) || 0; } function setHighScore(game, score) { const current = getHighScore(game); if (score > current) { localStorage.setItem('arcade_' + game + '_highscore', score); return true; } return false; } function updateHighScoreDisplay() { const games = ['snake', 'pong', 'breakout', 'memory']; const display = document.getElementById('highScoreDisplay'); if (!display) return; let html = 'đ HIGH SCORES: '; games.forEach((g, i) => { const score = getHighScore(g); html += `${g.toUpperCase()}: ${score}`; if (i < games.length - 1) html += ' | '; }); display.innerHTML = html; } updateHighScoreDisplay(); // ==================== MODAL MANAGEMENT ==================== const modal = document.getElementById('gameModal'); const modalTitle = document.getElementById('modalTitle'); const gameArea = document.getElementById('gameArea'); const gameScoreEl = document.getElementById('gameScore'); const mobileControls = document.getElementById('mobileControls'); const restartBtn = document.getElementById('restartGame'); const closeModalBtn = document.getElementById('closeModal'); let currentGame = null; let gameLoopId = null; let gameState = null; function openModal(gameName) { modal.classList.remove('hidden'); document.body.style.overflow = 'hidden'; currentGame = gameName; gameState = {}; mobileControls.classList.add('hidden'); restartBtn.classList.add('hidden'); gameScoreEl.textContent = ''; gameArea.innerHTML = ''; switch (gameName) { case 'snake': modalTitle.textContent = 'đ SNAKE'; startSnake(); break; case 'pong': modalTitle.textContent = 'đ PONG'; startPong(); break; case 'breakout': modalTitle.textContent = 'đ§ą BREAKOUT'; startBreakout(); break; case 'memory': modalTitle.textContent = 'đ§ MEMORY MATCH'; startMemory(); break; } } function closeModal() { modal.classList.add('hidden'); document.body.style.overflow = ''; if (gameLoopId) { cancelAnimationFrame(gameLoopId); gameLoopId = null; } // Remove event listeners by cloning canvas if (gameArea.querySelector('canvas')) { const canvas = gameArea.querySelector('canvas'); const clone = canvas.cloneNode(true); canvas.parentNode.replaceChild(clone, canvas); } currentGame = null; gameState = null; gameArea.innerHTML = ''; updateHighScoreDisplay(); } closeModalBtn.addEventListener('click', closeModal); modal.addEventListener('click', (e) => { if (e.target === modal) closeModal(); }); document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && currentGame) { closeModal(); } }); // ==================== GAME CARDS CLICK ==================== document.querySelectorAll('.game-card').forEach(card => { card.addEventListener('click', () => { const game = card.dataset.game; if (game) openModal(game); }); }); // ==================== SNAKE GAME ==================== function startSnake() { const canvas = document.createElement('canvas'); canvas.width = 400; canvas.height = 400; canvas.style.background = '#0a0a1a'; canvas.style.border = '2px solid var(--neon-green)'; canvas.style.boxShadow = '0 0 10px var(--neon-green)'; gameArea.appendChild(canvas); const ctx = canvas.getContext('2d'); const gridSize = 20; const cellSize = canvas.width / gridSize; let snake = [{ x: 10, y: 10 }]; let direction = { x: 1, y: 0 }; let nextDirection = { x: 1, y: 0 }; let food = spawnFood(); let score = 0; let speed = 120; let lastTime = 0; let gameOver = false; restartBtn.classList.remove('hidden'); restartBtn.onclick = () => { cancelAnimationFrame(gameLoopId); startSnake(); }; // Show mobile D-pad on touch devices if ('ontouchstart' in window || navigator.maxTouchPoints > 0) { mobileControls.classList.remove('hidden'); mobileControls.querySelectorAll('.mobile-btn').forEach(btn => { btn.onclick = (e) => { e.preventDefault(); const dir = btn.dataset.dir; changeDir(dir); }; }); } function spawnFood() { let pos; do { pos = { x: Math.floor(Math.random() * gridSize), y: Math.floor(Math.random() * gridSize) }; } while (snake.some(seg => seg.x === pos.x && seg.y === pos.y)); return pos; } function changeDir(dir) { if (gameOver) return; switch (dir) { case 'up': if (direction.y !== 1) nextDirection = { x: 0, y: -1 }; break; case 'down': if (direction.y !== -1) nextDirection = { x: 0, y: 1 }; break; case 'left': if (direction.x !== 1) nextDirection = { x: -1, y: 0 }; break; case 'right': if (direction.x !== -1) nextDirection = { x: 1, y: 0 }; break; } } document.addEventListener('keydown', handleKey); function handleKey(e) { if (currentGame !== 'snake') return; switch (e.key) { case 'ArrowUp': case 'w': case 'W': e.preventDefault(); changeDir('up'); break; case 'ArrowDown': case 's': case 'S': e.preventDefault(); changeDir('down'); break; case 'ArrowLeft': case 'a': case 'A': e.preventDefault(); changeDir('left'); break; case 'ArrowRight': case 'd': case 'D': e.preventDefault(); changeDir('right'); break; } } gameState.cleanup = () => document.removeEventListener('keydown', handleKey); function gameLoop(timestamp) { if (gameOver) return; if (timestamp - lastTime > speed) { lastTime = timestamp; direction = { ...nextDirection }; const head = { x: snake[0].x + direction.x, y: snake[0].y + direction.y }; // Wall collision if (head.x < 0 || head.x >= gridSize || head.y < 0 || head.y >= gridSize) { endGame(); return; } // Self collision if (snake.some(seg => seg.x === head.x && seg.y === head.y)) { endGame(); return; } snake.unshift(head); // Food if (head.x === food.x && head.y === food.y) { score += 10; food = spawnFood(); speed = Math.max(50, speed - 2); gameScoreEl.textContent = 'Score: ' + score; } else { snake.pop(); } } // Draw ctx.clearRect(0, 0, canvas.width, canvas.height); // Grid ctx.strokeStyle = 'rgba(57, 255, 20, 0.05)'; ctx.lineWidth = 0.5; for (let i = 0; i < gridSize; i++) { ctx.beginPath(); ctx.moveTo(i * cellSize, 0); ctx.lineTo(i * cellSize, canvas.height); ctx.stroke(); ctx.beginPath(); ctx.moveTo(0, i * cellSize); ctx.lineTo(canvas.width, i * cellSize); ctx.stroke(); } // Snake body snake.forEach((seg, i) => { const gradient = ctx.createRadialGradient( seg.x * cellSize + cellSize / 2, seg.y * cellSize + cellSize / 2, 2, seg.x * cellSize + cellSize / 2, seg.y * cellSize + cellSize / 2, cellSize / 1.5 ); if (i === 0) { gradient.addColorStop(0, '#39ff14'); gradient.addColorStop(1, '#1a8a0a'); } else { gradient.addColorStop(0, '#2ad810'); gradient.addColorStop(1, '#0d5a05'); } ctx.fillStyle = gradient; ctx.shadowColor = '#39ff14'; ctx.shadowBlur = i === 0 ? 12 : 6; ctx.fillRect(seg.x * cellSize + 2, seg.y * cellSize + 2, cellSize - 4, cellSize - 4); ctx.shadowBlur = 0; }); // Food ctx.fillStyle = '#ff00e5'; ctx.shadowColor = '#ff00e5'; ctx.shadowBlur = 15; ctx.beginPath(); ctx.arc(food.x * cellSize + cellSize / 2, food.y * cellSize + cellSize / 2, cellSize / 2 - 2, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0; gameLoopId = requestAnimationFrame(gameLoop); } function endGame() { gameOver = true; const isNew = setHighScore('snake', score); gameScoreEl.textContent = 'GAME OVER! Score: ' + score + (isNew ? ' đ NEW HIGH!' : ''); gameScoreEl.classList.add('neon-text-pink'); // Draw game over overlay ctx.fillStyle = 'rgba(0,0,0,0.7)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.font = '24px "Press Start 2P", cursive'; ctx.fillStyle = '#ff00e5'; ctx.textAlign = 'center'; ctx.fillText('GAME OVER', canvas.width / 2, canvas.height / 2 - 10); ctx.font = '14px "Orbitron", sans-serif'; ctx.fillText('Score: ' + score, canvas.width / 2, canvas.height / 2 + 30); cancelAnimationFrame(gameLoopId); } gameScoreEl.textContent = 'Score: 0'; gameLoopId = requestAnimationFrame(gameLoop); } // ==================== PONG GAME ==================== function startPong() { const canvas = document.createElement('canvas'); canvas.width = 500; canvas.height = 350; canvas.style.background = '#0a0a1a'; canvas.style.border = '2px solid var(--neon-pink)'; canvas.style.boxShadow = '0 0 10px var(--neon-pink)'; gameArea.appendChild(canvas); const ctx = canvas.getContext('2d'); const paddleW = 12; const paddleH = 70; const ballSize = 10; let playerY = canvas.height / 2 - paddleH / 2; let aiY = canvas.height / 2 - paddleH / 2; let ballX = canvas.width / 2; let ballY = canvas.height / 2; let ballVX = (Math.random() > 0.5 ? 1 : -1) * 4; let ballVY = (Math.random() * 6 - 3); let playerScore = 0; let aiScore = 0; const winScore = 7; let gameOver = false; let keys = {}; restartBtn.classList.remove('hidden'); restartBtn.onclick = () => { cancelAnimationFrame(gameLoopId); startPong(); }; document.addEventListener('keydown', (e) => { keys[e.key] = true; }); document.addEventListener('keyup', (e) => { keys[e.key] = false; }); // Mouse/touch support canvas.addEventListener('mousemove', (e) => { const rect = canvas.getBoundingClientRect(); const scaleY = canvas.height / rect.height; playerY = (e.clientY - rect.top) * scaleY - paddleH / 2; playerY = Math.max(0, Math.min(canvas.height - paddleH, playerY)); }); canvas.addEventListener('touchmove', (e) => { e.preventDefault(); const rect = canvas.getBoundingClientRect(); const scaleY = canvas.height / rect.height; playerY = (e.touches[0].clientY - rect.top) * scaleY - paddleH / 2; playerY = Math.max(0, Math.min(canvas.height - paddleH, playerY)); }, { passive: false }); gameState.cleanup = () => {}; function resetBall() { ballX = canvas.width / 2; ballY = canvas.height / 2; ballVX = (Math.random() > 0.5 ? 1 : -1) * 4; ballVY = (Math.random() * 6 - 3); } function gameLoop() { if (gameOver) return; // Player movement if (keys['ArrowUp'] || keys['w'] || keys['W']) playerY -= 6; if (keys['ArrowDown'] || keys['s'] || keys['S']) playerY += 6; playerY = Math.max(0, Math.min(canvas.height - paddleH, playerY)); // AI movement const aiCenter = aiY + paddleH / 2; if (aiCenter < ballY - 15) aiY += 3.5; else if (aiCenter > ballY + 15) aiY -= 3.5; aiY = Math.max(0, Math.min(canvas.height - paddleH, aiY)); // Ball movement ballX += ballVX; ballY += ballVY; // Top/bottom wall bounce if (ballY - ballSize / 2 <= 0 || ballY + ballSize / 2 >= canvas.height) { ballVY *= -1; ballY = Math.max(ballSize / 2, Math.min(canvas.height - ballSize / 2, ballY)); } // Player paddle collision if (ballX - ballSize / 2 <= 20 + paddleW && ballY > playerY && ballY < playerY + paddleH && ballVX < 0) { const hitPos = (ballY - playerY) / paddleH; ballVX *= -1.05; ballVY = (hitPos - 0.5) * 8; ballX = 20 + paddleW + ballSize / 2; } // AI paddle collision if (ballX + ballSize / 2 >= canvas.width - 20 - paddleW && ballY > aiY && ballY < aiY + paddleH && ballVX > 0) { const hitPos = (ballY - aiY) / paddleH; ballVX *= -1.05; ballVY = (hitPos - 0.5) * 8; ballX = canvas.width - 20 - paddleW - ballSize / 2; } // Scoring if (ballX < 0) { aiScore++; if (aiScore >= winScore) endPongGame(); else resetBall(); } if (ballX > canvas.width) { playerScore++; if (playerScore >= winScore) endPongGame(); else resetBall(); } // Clamp ball speed const speed = Math.sqrt(ballVX * ballVX + ballVY * ballVY); if (speed > 10) { ballVX = (ballVX / speed) * 10; ballVY = (ballVY / speed) * 10; } // Draw ctx.clearRect(0, 0, canvas.width, canvas.height); // Center line ctx.setLineDash([8, 12]); ctx.strokeStyle = 'rgba(255,255,255,0.1)'; ctx.beginPath(); ctx.moveTo(canvas.width / 2, 0); ctx.lineTo(canvas.width / 2, canvas.height); ctx.stroke(); ctx.setLineDash([]); // Paddles ctx.fillStyle = '#ff00e5'; ctx.shadowColor = '#ff00e5'; ctx.shadowBlur = 10; ctx.fillRect(20, playerY, paddleW, paddleH); ctx.fillStyle = '#00f0ff'; ctx.shadowColor = '#00f0ff'; ctx.fillRect(canvas.width - 20 - paddleW, aiY, paddleW, paddleH); // Ball ctx.fillStyle = '#ffffff'; ctx.shadowColor = '#ffffff'; ctx.shadowBlur = 12; ctx.beginPath(); ctx.arc(ballX, ballY, ballSize / 2, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0; // Scores ctx.font = '28px "Press Start 2P", cursive'; ctx.fillStyle = '#ff00e5'; ctx.textAlign = 'center'; ctx.fillText(playerScore, canvas.width / 4, 45); ctx.fillStyle = '#00f0ff'; ctx.fillText(aiScore, (canvas.width / 4) * 3, 45); gameScoreEl.textContent = `YOU ${playerScore} - ${aiScore} AI | First to ${winScore}`; gameLoopId = requestAnimationFrame(gameLoop); } function endPongGame() { gameOver = true; const winner = playerScore >= winScore ? 'YOU WIN!' : 'AI WINS!'; const isNew = setHighScore('pong', playerScore); gameScoreEl.textContent = winner + ' Final: ' + playerScore + '-' + aiScore + (isNew ? ' đ' : ''); ctx.fillStyle = 'rgba(0,0,0,0.7)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.font = '24px "Press Start 2P", cursive'; ctx.fillStyle = playerScore >= winScore ? '#39ff14' : '#ff00e5'; ctx.textAlign = 'center'; ctx.fillText(winner, canvas.width / 2, canvas.height / 2); cancelAnimationFrame(gameLoopId); } gameLoopId = requestAnimationFrame(gameLoop); } // ==================== BREAKOUT GAME ==================== function startBreakout() { const canvas = document.createElement('canvas'); canvas.width = 480; canvas.height = 400; canvas.style.background = '#0a0a1a'; canvas.style.border = '2px solid var(--neon-cyan)'; canvas.style.boxShadow = '0 0 10px var(--neon-cyan)'; gameArea.appendChild(canvas); const ctx = canvas.getContext('2d'); const paddleW = 80; const paddleH = 12; const ballRadius = 7; const brickRows = 6; const brickCols = 8; const brickW = (canvas.width - 40) / brickCols; const brickH = 22; let paddleX = canvas.width / 2 - paddleW / 2; let ballX = canvas.width / 2; let ballY = canvas.height - 60; let ballVX = (Math.random() * 4 - 2); let ballVY = -5; let score = 0; let gameOver = false; let ballLaunched = false; const brickColors = ['#ff00e5', '#ff6600', '#ffff00', '#39ff14', '#00f0ff', '#8b5cf6']; let bricks = []; for (let row = 0; row < brickRows; row++) { bricks[row] = []; for (let col = 0; col < brickCols; col++) { bricks[row][col] = { alive: true, color: brickColors[row] }; } } restartBtn.classList.remove('hidden'); restartBtn.onclick = () => { cancelAnimationFrame(gameLoopId); startBreakout(); }; // Mouse/touch canvas.addEventListener('mousemove', (e) => { const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; paddleX = (e.clientX - rect.left) * scaleX - paddleW / 2; paddleX = Math.max(0, Math.min(canvas.width - paddleW, paddleX)); if (!ballLaunched) ballX = paddleX + paddleW / 2; }); canvas.addEventListener('touchmove', (e) => { e.preventDefault(); const rect = canvas.getBoundingClientRect(); const scaleX = canvas.width / rect.width; paddleX = (e.touches[0].clientX - rect.left) * scaleX - paddleW / 2; paddleX = Math.max(0, Math.min(canvas.width - paddleW, paddleX)); if (!ballLaunched) ballX = paddleX + paddleW / 2; }, { passive: false }); canvas.addEventListener('click', () => { if (!ballLaunched && !gameOver) { ballLaunched = true; ballVY = -5; ballVX = (Math.random() * 4 - 2) || 2; } }); gameState.cleanup = () => {}; function gameLoop() { if (gameOver) return; // Ball movement if (ballLaunched) { ballX += ballVX; ballY += ballVY; // Wall collisions if (ballX - ballRadius <= 0 || ballX + ballRadius >= canvas.width) ballVX *= -1; if (ballY - ballRadius <= 0) ballVY *= -1; // Bottom (game over) if (ballY + ballRadius >= canvas.height) { endBreakout(false); return; } // Paddle collision if (ballY + ballRadius >= canvas.height - paddleH - 10 && ballY + ballRadius <= canvas.height - 5 && ballX > paddleX && ballX < paddleX + paddleW && ballVY > 0) { const hitPos = (ballX - paddleX) / paddleW; ballVY *= -1; ballVX = (hitPos - 0.5) * 7; ballY = canvas.height - paddleH - 10 - ballRadius; const speed = Math.sqrt(ballVX * ballVX + ballVY * ballVY); if (speed < 5) { ballVX = (ballVX / speed) * 5; ballVY = (ballVY / speed) * 5; } } // Brick collisions let allDestroyed = true; for (let row = 0; row < brickRows; row++) { for (let col = 0; col < brickCols; col++) { if (!bricks[row][col].alive) continue; allDestroyed = false; const bx = 20 + col * brickW; const by = 40 + row * brickH; if (ballX + ballRadius > bx && ballX - ballRadius < bx + brickW && ballY + ballRadius > by && ballY - ballRadius < by + brickH) { bricks[row][col].alive = false; score += 10; gameScoreEl.textContent = 'Score: ' + score; // Determine bounce direction const overlapLeft = (ballX + ballRadius) - bx; const overlapRight = (bx + brickW) - (ballX - ballRadius); const overlapTop = (ballY + ballRadius) - by; const overlapBottom = (by + brickH) - (ballY - ballRadius); const minOverlapX = Math.min(overlapLeft, overlapRight); const minOverlapY = Math.min(overlapTop, overlapBottom); if (minOverlapX < minOverlapY) ballVX *= -1; else ballVY *= -1; } } } if (allDestroyed) { endBreakout(true); return; } } // Clamp ball if (ballLaunched) { const speed = Math.sqrt(ballVX * ballVX + ballVY * ballVY); if (speed > 9) { ballVX = (ballVX / speed) * 9; ballVY = (ballVY / speed) * 9; } } // Draw ctx.clearRect(0, 0, canvas.width, canvas.height); // Bricks for (let row = 0; row < brickRows; row++) { for (let col = 0; col < brickCols; col++) { if (!bricks[row][col].alive) continue; const bx = 20 + col * brickW; const by = 40 + row * brickH; ctx.fillStyle = bricks[row][col].color; ctx.shadowColor = bricks[row][col].color; ctx.shadowBlur = 4; ctx.fillRect(bx + 2, by + 2, brickW - 4, brickH - 4); ctx.shadowBlur = 0; } } // Paddle ctx.fillStyle = '#00f0ff'; ctx.shadowColor = '#00f0ff'; ctx.shadowBlur = 10; ctx.beginPath(); ctx.roundRect(paddleX, canvas.height - paddleH - 10, paddleW, paddleH, 6); ctx.fill(); ctx.shadowBlur = 0; // Ball ctx.fillStyle = '#ffffff'; ctx.shadowColor = '#ffffff'; ctx.shadowBlur = 10; ctx.beginPath(); ctx.arc(ballX, ballY, ballRadius, 0, Math.PI * 2); ctx.fill(); ctx.shadowBlur = 0; if (!ballLaunched && !gameOver) { ctx.font = '14px "Orbitron", sans-serif'; ctx.fillStyle = '#ffffff'; ctx.textAlign = 'center'; ctx.fillText('CLICK TO LAUNCH', canvas.width / 2, canvas.height - 40); } gameLoopId = requestAnimationFrame(gameLoop); } function endBreakout(won) { gameOver = true; const isNew = setHighScore('breakout', score); const msg = won ? 'YOU WIN! đ' : 'GAME OVER'; gameScoreEl.textContent = msg + ' Score: ' + score + (isNew ? ' đ NEW HIGH!' : ''); ctx.fillStyle = 'rgba(0,0,0,0.7)'; ctx.fillRect(0, 0, canvas.width, canvas.height); ctx.font = '22px "Press Start 2P", cursive'; ctx.fillStyle = won ? '#39ff14' : '#ff00e5'; ctx.textAlign = 'center'; ctx.fillText(won ? 'YOU WIN!' : 'GAME OVER', canvas.width / 2, canvas.height / 2 - 10); ctx.font = '14px "Orbitron", sans-serif'; ctx.fillText('Score: ' + score, canvas.width / 2, canvas.height / 2 + 30); cancelAnimationFrame(gameLoopId); } gameScoreEl.textContent = 'Score: 0'; gameLoopId = requestAnimationFrame(gameLoop); } // ==================== MEMORY MATCH GAME ==================== function startMemory() { const emojis = ['đ', 'đŽ', 'đšī¸', 'đž', 'đ', 'đĨ', 'âĄ', 'đ']; const cards = [...emojis, ...emojis]; // Shuffle for (let i = cards.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [cards[i], cards[j]] = [cards[j], cards[i]]; } let flippedCards = []; let matchedCount = 0; let moves = 0; let lockBoard = false; let gameOver = false; const grid = document.createElement('div'); grid.style.display = 'grid'; grid.style.gridTemplateColumns = 'repeat(4, 1fr)'; grid.style.gap = '10px'; grid.style.maxWidth = '360px'; grid.style.width = '100%'; grid.style.margin = '0 auto'; gameArea.appendChild(grid); restartBtn.classList.remove('hidden'); restartBtn.onclick = () => { startMemory(); }; cards.forEach((emoji, index) => { const card = document.createElement('div'); card.classList.add('memory-card'); card.dataset.index = index; card.dataset.emoji = emoji; card.innerHTML = `