game-space / index.html
Kus669's picture
Add 1 files
dc0039a verified
Raw
History Blame Contribute Delete
24.6 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Advanced Ping Pong</title>
<script src="https://cdn.tailwindcss.com"></script>
<style>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
body {
font-family: 'Press Start 2P', cursive;
overflow: hidden;
background: linear-gradient(135deg, #1a1a2e, #16213e);
color: white;
}
#game-container {
position: relative;
width: 100vw;
height: 100vh;
}
#game-board {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
border: 8px solid #4cc9f0;
border-radius: 10px;
box-shadow: 0 0 30px rgba(76, 201, 240, 0.5);
background-color: #0f0f1a;
}
.paddle {
position: absolute;
background: linear-gradient(to right, #f72585, #b5179e);
border-radius: 10px;
box-shadow: 0 0 15px rgba(247, 37, 133, 0.7);
}
#ball {
position: absolute;
background: radial-gradient(circle at 30% 30%, #fff, #4cc9f0);
border-radius: 50%;
box-shadow: 0 0 20px rgba(76, 201, 240, 0.8);
}
.power-up {
position: absolute;
border-radius: 50%;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0% { transform: scale(1); }
50% { transform: scale(1.2); }
100% { transform: scale(1); }
}
.score {
position: absolute;
top: 20px;
font-size: 2rem;
text-shadow: 0 0 10px rgba(76, 201, 240, 0.8);
}
#player-score {
left: 30%;
}
#computer-score {
right: 30%;
}
#game-over {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
display: none;
z-index: 10;
}
#start-screen {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
text-align: center;
z-index: 10;
}
button {
background: linear-gradient(to right, #f72585, #b5179e);
border: none;
color: white;
padding: 15px 30px;
margin: 10px;
border-radius: 5px;
font-family: 'Press Start 2P', cursive;
cursor: pointer;
box-shadow: 0 0 15px rgba(247, 37, 133, 0.5);
transition: all 0.3s;
}
button:hover {
transform: scale(1.05);
box-shadow: 0 0 20px rgba(247, 37, 133, 0.8);
}
.effect {
position: absolute;
border-radius: 50%;
pointer-events: none;
opacity: 0;
}
#instructions {
position: absolute;
bottom: 20px;
left: 50%;
transform: translateX(-50%);
font-size: 0.8rem;
text-align: center;
color: rgba(255, 255, 255, 0.7);
}
</style>
</head>
<body>
<div id="game-container">
<div id="start-screen">
<h1 class="text-4xl mb-8 text-transparent bg-clip-text bg-gradient-to-r from-pink-500 to-purple-600">ADVANCED PING PONG</h1>
<button id="start-btn">START GAME</button>
<button id="how-to-play-btn">HOW TO PLAY</button>
<div id="how-to-play" class="mt-6 hidden">
<p class="mb-2">Use MOUSE or TOUCH to move your paddle</p>
<p class="mb-2">First to score 7 points wins!</p>
<p>Collect power-ups for special abilities</p>
</div>
</div>
<canvas id="game-board"></canvas>
<div id="player-score" class="score">0</div>
<div id="computer-score" class="score">0</div>
<div id="game-over">
<h1 id="game-over-text" class="text-4xl mb-8"></h1>
<button id="restart-btn">PLAY AGAIN</button>
</div>
<div id="instructions">
<p>Move your mouse or finger to control the paddle</p>
</div>
</div>
<script>
// Game variables
const canvas = document.getElementById('game-board');
const ctx = canvas.getContext('2d');
const playerScoreDisplay = document.getElementById('player-score');
const computerScoreDisplay = document.getElementById('computer-score');
const gameOverScreen = document.getElementById('game-over');
const gameOverText = document.getElementById('game-over-text');
const startScreen = document.getElementById('start-screen');
const startBtn = document.getElementById('start-btn');
const restartBtn = document.getElementById('restart-btn');
const howToPlayBtn = document.getElementById('how-to-play-btn');
const howToPlay = document.getElementById('how-to-play');
// Game settings
const WINNING_SCORE = 7;
const POWER_UP_CHANCE = 0.02; // 2% chance per frame
const POWER_UP_DURATION = 300; // frames (5 seconds at 60fps)
// Game state
let gameRunning = false;
let playerScore = 0;
let computerScore = 0;
let lastTime = 0;
let powerUps = [];
let activeEffects = {
player: null,
computer: null
};
let effects = [];
// Game objects
const ball = {
x: 0,
y: 0,
radius: 10,
speedX: 5,
speedY: 5,
color: '#4cc9f0'
};
const playerPaddle = {
x: 0,
y: 0,
width: 15,
height: 100,
speed: 8,
color: '#f72585'
};
const computerPaddle = {
x: 0,
y: 0,
width: 15,
height: 100,
speed: 5,
color: '#7209b7'
};
// Power-up types
const POWER_UP_TYPES = {
EXTRA_LIFE: { color: '#4cc9f0', radius: 12, effect: 'extraLife' },
SPEED_BOOST: { color: '#f72585', radius: 12, effect: 'speedBoost' },
PADDLE_GROW: { color: '#b5179e', radius: 12, effect: 'paddleGrow' },
PADDLE_SHRINK: { color: '#7209b7', radius: 12, effect: 'paddleShrink' },
BALL_SLOW: { color: '#3a0ca3', radius: 12, effect: 'ballSlow' },
BALL_FAST: { color: '#4361ee', radius: 12, effect: 'ballFast' }
};
// Initialize game
function init() {
resizeCanvas();
resetGame();
// Event listeners
window.addEventListener('resize', resizeCanvas);
canvas.addEventListener('mousemove', movePaddle);
canvas.addEventListener('touchmove', (e) => {
e.preventDefault();
const touch = e.touches[0];
const rect = canvas.getBoundingClientRect();
const mouseY = touch.clientY - rect.top;
movePaddle({ clientY: mouseY });
});
startBtn.addEventListener('click', startGame);
restartBtn.addEventListener('click', startGame);
howToPlayBtn.addEventListener('click', () => {
howToPlay.classList.toggle('hidden');
});
}
// Resize canvas to fit window
function resizeCanvas() {
canvas.width = Math.min(window.innerWidth - 40, 800);
canvas.height = Math.min(window.innerHeight - 40, 500);
resetPositions();
}
// Reset game objects to starting positions
function resetPositions() {
ball.x = canvas.width / 2;
ball.y = canvas.height / 2;
// Randomize ball direction
ball.speedX = 5 * (Math.random() > 0.5 ? 1 : -1);
ball.speedY = 5 * (Math.random() * 2 - 1);
playerPaddle.x = 20;
playerPaddle.y = (canvas.height - playerPaddle.height) / 2;
computerPaddle.x = canvas.width - 20 - computerPaddle.width;
computerPaddle.y = (canvas.height - computerPaddle.height) / 2;
// Reset power-ups and effects
powerUps = [];
effects = [];
activeEffects.player = null;
activeEffects.computer = null;
}
// Reset game state
function resetGame() {
playerScore = 0;
computerScore = 0;
playerScoreDisplay.textContent = '0';
computerScoreDisplay.textContent = '0';
resetPositions();
}
// Start the game
function startGame() {
resetGame();
gameRunning = true;
startScreen.style.display = 'none';
gameOverScreen.style.display = 'none';
requestAnimationFrame(gameLoop);
}
// End the game
function endGame(winner) {
gameRunning = false;
gameOverScreen.style.display = 'block';
gameOverText.textContent = `${winner} WINS!`;
}
// Move player paddle
function movePaddle(e) {
if (!gameRunning) return;
const rect = canvas.getBoundingClientRect();
const mouseY = e.clientY - rect.top;
// Keep paddle within canvas
playerPaddle.y = Math.max(
0,
Math.min(
mouseY - playerPaddle.height / 2,
canvas.height - playerPaddle.height
)
);
}
// AI for computer paddle
function moveComputerPaddle() {
// Simple AI - follow the ball with some delay
const computerPaddleCenter = computerPaddle.y + computerPaddle.height / 2;
const ballCenter = ball.y + ball.radius;
// Add some imperfection to the AI
const reactionThreshold = 0.3;
const reactionSpeed = computerPaddle.speed * (0.5 + Math.random() * 0.5);
if (computerPaddleCenter < ballCenter - reactionThreshold * computerPaddle.height) {
computerPaddle.y += reactionSpeed;
} else if (computerPaddleCenter > ballCenter + reactionThreshold * computerPaddle.height) {
computerPaddle.y -= reactionSpeed;
}
// Keep paddle within canvas
computerPaddle.y = Math.max(0, Math.min(computerPaddle.y, canvas.height - computerPaddle.height));
}
// Check collision between ball and paddle
function collision(ball, paddle) {
return (
ball.x + ball.radius > paddle.x &&
ball.x - ball.radius < paddle.x + paddle.width &&
ball.y + ball.radius > paddle.y &&
ball.y - ball.radius < paddle.y + paddle.height
);
}
// Spawn random power-up
function spawnPowerUp() {
if (Math.random() < POWER_UP_CHANCE && powerUps.length < 2) {
const types = Object.keys(POWER_UP_TYPES);
const randomType = types[Math.floor(Math.random() * types.length)];
const powerUp = {
...POWER_UP_TYPES[randomType],
x: Math.random() * (canvas.width - 40) + 20,
y: Math.random() * (canvas.height - 40) + 20,
type: randomType,
lifetime: 300 // 5 seconds at 60fps
};
powerUps.push(powerUp);
}
}
// Apply power-up effect
function applyPowerUp(powerUp, target) {
// Remove any existing effect
if (activeEffects[target]) {
removeEffect(activeEffects[target], target);
}
// Apply new effect
switch (powerUp.effect) {
case 'extraLife':
if (target === 'player') playerScore++;
else computerScore++;
updateScore();
createEffect(powerUp.x, powerUp.y, powerUp.color, '+1');
break;
case 'speedBoost':
if (target === 'player') {
playerPaddle.speed *= 1.5;
} else {
computerPaddle.speed *= 1.5;
}
activeEffects[target] = { type: 'speedBoost', duration: POWER_UP_DURATION };
createEffect(powerUp.x, powerUp.y, powerUp.color, 'SPEED!');
break;
case 'paddleGrow':
if (target === 'player') {
playerPaddle.height *= 1.5;
} else {
computerPaddle.height *= 1.5;
}
activeEffects[target] = { type: 'paddleGrow', duration: POWER_UP_DURATION };
createEffect(powerUp.x, powerUp.y, powerUp.color, 'GROW!');
break;
case 'paddleShrink':
if (target === 'opponent') {
playerPaddle.height *= 0.7;
activeEffects.player = { type: 'paddleShrink', duration: POWER_UP_DURATION };
} else {
computerPaddle.height *= 0.7;
activeEffects.computer = { type: 'paddleShrink', duration: POWER_UP_DURATION };
}
createEffect(powerUp.x, powerUp.y, powerUp.color, 'SHRINK!');
break;
case 'ballSlow':
ball.speedX *= 0.7;
ball.speedY *= 0.7;
activeEffects[target] = { type: 'ballSlow', duration: POWER_UP_DURATION };
createEffect(powerUp.x, powerUp.y, powerUp.color, 'SLOW!');
break;
case 'ballFast':
ball.speedX *= 1.3;
ball.speedY *= 1.3;
activeEffects[target] = { type: 'ballFast', duration: POWER_UP_DURATION };
createEffect(powerUp.x, powerUp.y, powerUp.color, 'FAST!');
break;
}
}
// Remove effect after duration expires
function removeEffect(effect, target) {
switch (effect.type) {
case 'speedBoost':
if (target === 'player') {
playerPaddle.speed /= 1.5;
} else {
computerPaddle.speed /= 1.5;
}
break;
case 'paddleGrow':
if (target === 'player') {
playerPaddle.height /= 1.5;
} else {
computerPaddle.height /= 1.5;
}
break;
case 'paddleShrink':
if (target === 'player') {
playerPaddle.height /= 0.7;
} else {
computerPaddle.height /= 0.7;
}
break;
case 'ballSlow':
ball.speedX /= 0.7;
ball.speedY /= 0.7;
break;
case 'ballFast':
ball.speedX /= 1.3;
ball.speedY /= 1.3;
break;
}
}
// Create visual effect
function createEffect(x, y, color, text) {
effects.push({
x,
y,
color,
text,
alpha: 1,
size: 20,
lifetime: 60
});
}
// Update score display
function updateScore() {
playerScoreDisplay.textContent = playerScore;
computerScoreDisplay.textContent = computerScore;
// Check for winner
if (playerScore >= WINNING_SCORE) {
endGame('PLAYER');
} else if (computerScore >= WINNING_SCORE) {
endGame('COMPUTER');
}
}
// Main game loop
function gameLoop(timestamp) {
if (!gameRunning) return;
// Calculate delta time for smooth animation
const deltaTime = timestamp - lastTime;
lastTime = timestamp;
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw center line
ctx.beginPath();
ctx.setLineDash([10, 10]);
ctx.moveTo(canvas.width / 2, 0);
ctx.lineTo(canvas.width / 2, canvas.height);
ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
ctx.lineWidth = 4;
ctx.stroke();
ctx.setLineDash([]);
// Draw paddles
ctx.fillStyle = playerPaddle.color;
ctx.fillRect(playerPaddle.x, playerPaddle.y, playerPaddle.width, playerPaddle.height);
ctx.fillStyle = computerPaddle.color;
ctx.fillRect(computerPaddle.x, computerPaddle.y, computerPaddle.width, computerPaddle.height);
// Draw ball
ctx.beginPath();
ctx.arc(ball.x, ball.y, ball.radius, 0, Math.PI * 2);
ctx.fillStyle = ball.color;
ctx.fill();
// Move ball
ball.x += ball.speedX;
ball.y += ball.speedY;
// Ball collision with top and bottom walls
if (ball.y - ball.radius < 0 || ball.y + ball.radius > canvas.height) {
ball.speedY = -ball.speedY;
// Add some randomness to bounce
ball.speedY *= (0.9 + Math.random() * 0.2);
}
// Ball collision with paddles
if (collision(ball, playerPaddle)) {
// Calculate angle based on where ball hits paddle
const hitPosition = (ball.y - (playerPaddle.y + playerPaddle.height / 2)) / (playerPaddle.height / 2);
const angle = hitPosition * Math.PI / 4; // Max 45 degree angle
ball.speedX = Math.abs(ball.speedX) * 1.05; // Increase speed slightly
ball.speedY = Math.sin(angle) * Math.abs(ball.speedX) * 1.2;
// Add some randomness
ball.speedX *= (0.95 + Math.random() * 0.1);
ball.speedY *= (0.95 + Math.random() * 0.1);
// Create hit effect
createEffect(ball.x, ball.y, playerPaddle.color, '');
}
if (collision(ball, computerPaddle)) {
// Calculate angle based on where ball hits paddle
const hitPosition = (ball.y - (computerPaddle.y + computerPaddle.height / 2)) / (computerPaddle.height / 2);
const angle = hitPosition * Math.PI / 4; // Max 45 degree angle
ball.speedX = -Math.abs(ball.speedX) * 1.05; // Increase speed slightly
ball.speedY = Math.sin(angle) * Math.abs(ball.speedX) * 1.2;
// Add some randomness
ball.speedX *= (0.95 + Math.random() * 0.1);
ball.speedY *= (0.95 + Math.random() * 0.1);
// Create hit effect
createEffect(ball.x, ball.y, computerPaddle.color, '');
}
// Ball out of bounds
if (ball.x - ball.radius < 0) {
computerScore++;
updateScore();
resetPositions();
} else if (ball.x + ball.radius > canvas.width) {
playerScore++;
updateScore();
resetPositions();
}
// Move computer paddle
moveComputerPaddle();
// Spawn and update power-ups
spawnPowerUp();
for (let i = powerUps.length - 1; i >= 0; i--) {
const powerUp = powerUps[i];
// Draw power-up
ctx.beginPath();
ctx.arc(powerUp.x, powerUp.y, powerUp.radius, 0, Math.PI * 2);
ctx.fillStyle = powerUp.color;
ctx.fill();
// Draw outline
ctx.beginPath();
ctx.arc(powerUp.x, powerUp.y, powerUp.radius + 2, 0, Math.PI * 2);
ctx.strokeStyle = 'white';
ctx.lineWidth = 2;
ctx.stroke();
// Check collision with paddles
const playerDist = Math.sqrt(
Math.pow(powerUp.x - (playerPaddle.x + playerPaddle.width / 2), 2) +
Math.pow(powerUp.y - (playerPaddle.y + playerPaddle.height / 2), 2)
);
const computerDist = Math.sqrt(
Math.pow(powerUp.x - (computerPaddle.x + computerPaddle.width / 2), 2) +
Math.pow(powerUp.y - (computerPaddle.y + computerPaddle.height / 2), 2)
);
const paddleRadius = Math.max(playerPaddle.width, playerPaddle.height) / 2;
if (playerDist < powerUp.radius + paddleRadius) {
applyPowerUp(powerUp, 'player');
powerUps.splice(i, 1);
} else if (computerDist < powerUp.radius + paddleRadius) {
applyPowerUp(powerUp, 'computer');
powerUps.splice(i, 1);
} else {
// Decrease lifetime
powerUp.lifetime--;
// Remove if expired
if (powerUp.lifetime <= 0) {
powerUps.splice(i, 1);
}
}
}
// Update active effects
for (const target in activeEffects) {
if (activeEffects[target]) {
activeEffects[target].duration--;
if (activeEffects[target].duration <= 0) {
removeEffect(activeEffects[target], target);
activeEffects[target] = null;
}
}
}
// Update and draw effects
for (let i = effects.length - 1; i >= 0; i--) {
const effect = effects[i];
ctx.globalAlpha = effect.alpha;
ctx.fillStyle = effect.color;
ctx.font = `${effect.size}px 'Press Start 2P'`;
ctx.textAlign = 'center';
ctx.fillText(effect.text, effect.x, effect.y);
ctx.globalAlpha = 1;
effect.alpha -= 0.02;
effect.size += 0.5;
effect.lifetime--;
if (effect.lifetime <= 0) {
effects.splice(i, 1);
}
}
// Continue game loop
requestAnimationFrame(gameLoop);
}
// Initialize the game when the page loads
window.onload = init;
</script>
</body>
</html>