Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
<title>Cyber Pong Explosion - Online Multiplayer</title> | |
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
<style> | |
* { | |
margin: 0; | |
padding: 0; | |
box-sizing: border-box; | |
touch-action: manipulation; | |
} | |
body { | |
background-color: #0a0a1a; | |
color: #00fffc; | |
font-family: 'Orbitron', 'Arial', sans-serif; | |
overflow: hidden; | |
height: 100vh; | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: center; | |
-webkit-tap-highlight-color: transparent; | |
} | |
@font-face { | |
font-family: 'Orbitron'; | |
src: url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap'); | |
} | |
.game-container { | |
position: relative; | |
width: 100%; | |
max-width: 800px; | |
height: 100vh; | |
max-height: 500px; | |
border: 3px solid #00fffc; | |
box-shadow: 0 0 20px #00fffc, inset 0 0 20px rgba(0, 255, 252, 0.3); | |
border-radius: 10px; | |
overflow: hidden; | |
background: radial-gradient(circle at center, #0a0a1a 0%, #000 100%); | |
} | |
@media (max-width: 600px) { | |
.game-container { | |
border-radius: 0; | |
border: none; | |
max-height: none; | |
height: 100vh; | |
} | |
} | |
.paddle { | |
position: absolute; | |
width: 15px; | |
height: 100px; | |
background: linear-gradient(to bottom, #00fffc, #0084ff); | |
border-radius: 5px; | |
box-shadow: 0 0 10px #00fffc; | |
} | |
@media (max-width: 600px) { | |
.paddle { | |
width: 12px; | |
height: 80px; | |
} | |
} | |
#playerPaddle { | |
left: 20px; | |
} | |
#aiPaddle, #opponentPaddle { | |
right: 20px; | |
} | |
.ball { | |
position: absolute; | |
width: 20px; | |
height: 20px; | |
background: #ff00e4; | |
border-radius: 50%; | |
box-shadow: 0 0 15px #ff00e4; | |
} | |
@media (max-width: 600px) { | |
.ball { | |
width: 16px; | |
height: 16px; | |
} | |
} | |
.score { | |
position: absolute; | |
top: 20px; | |
font-size: 2rem; | |
font-weight: bold; | |
text-shadow: 0 0 10px #00fffc; | |
} | |
@media (max-width: 600px) { | |
.score { | |
font-size: 1.5rem; | |
top: 10px; | |
} | |
} | |
#playerScore { | |
left: 20%; | |
} | |
#aiScore, #opponentScore { | |
right: 20%; | |
} | |
.center-line { | |
position: absolute; | |
top: 0; | |
left: 50%; | |
width: 2px; | |
height: 100%; | |
background: linear-gradient(to bottom, | |
transparent 0%, | |
rgba(0, 255, 252, 0.3) 10%, | |
transparent 20%, | |
rgba(0, 255, 252, 0.3) 30%, | |
transparent 40%, | |
rgba(0, 255, 252, 0.3) 50%, | |
transparent 60%, | |
rgba(0, 255, 252, 0.3) 70%, | |
transparent 80%, | |
rgba(0, 255, 252, 0.3) 90%, | |
transparent 100%); | |
} | |
.particle { | |
position: absolute; | |
width: 5px; | |
height: 5px; | |
border-radius: 50%; | |
pointer-events: none; | |
} | |
.power-up { | |
position: absolute; | |
width: 20px; | |
height: 20px; | |
border-radius: 50%; | |
box-shadow: 0 0 10px currentColor; | |
animation: pulse 1.5s infinite; | |
} | |
@media (max-width: 600px) { | |
.power-up { | |
width: 16px; | |
height: 16px; | |
} | |
} | |
@keyframes pulse { | |
0% { transform: scale(1); opacity: 1; } | |
50% { transform: scale(1.2); opacity: 0.7; } | |
100% { transform: scale(1); opacity: 1; } | |
} | |
.power-up.speed { | |
background-color: #ff00e4; | |
color: #ff00e4; | |
} | |
.power-up.size { | |
background-color: #00ff00; | |
color: #00ff00; | |
} | |
.power-up.freeze { | |
background-color: #00b4ff; | |
color: #00b4ff; | |
} | |
.game-over { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(0, 0, 0, 0.8); | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
z-index: 10; | |
display: none; | |
} | |
.game-over h1 { | |
font-size: 2.5rem; | |
margin-bottom: 20px; | |
text-shadow: 0 0 15px #ff00e4; | |
color: #ff00e4; | |
text-align: center; | |
padding: 0 20px; | |
} | |
@media (max-width: 600px) { | |
.game-over h1 { | |
font-size: 2rem; | |
} | |
} | |
.game-over p { | |
font-size: 1.2rem; | |
margin-bottom: 30px; | |
text-align: center; | |
padding: 0 20px; | |
} | |
.btn { | |
padding: 10px 20px; | |
background: linear-gradient(to right, #00fffc, #0084ff); | |
border: none; | |
border-radius: 5px; | |
color: #0a0a1a; | |
font-family: 'Orbitron', sans-serif; | |
font-size: 1rem; | |
cursor: pointer; | |
transition: all 0.3s; | |
box-shadow: 0 0 10px #00fffc; | |
margin: 5px; | |
} | |
@media (max-width: 600px) { | |
.btn { | |
padding: 8px 16px; | |
font-size: 0.9rem; | |
} | |
} | |
.btn:hover { | |
transform: scale(1.05); | |
box-shadow: 0 0 20px #00fffc; | |
} | |
.start-screen { | |
position: absolute; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(0, 0, 0, 0.8); | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
z-index: 10; | |
padding: 20px; | |
} | |
.start-screen h1 { | |
font-size: 2.5rem; | |
margin-bottom: 20px; | |
text-shadow: 0 0 15px #00fffc; | |
color: #00fffc; | |
text-align: center; | |
} | |
@media (max-width: 600px) { | |
.start-screen h1 { | |
font-size: 2rem; | |
} | |
} | |
.start-screen p { | |
font-size: 1rem; | |
margin-bottom: 20px; | |
max-width: 500px; | |
text-align: center; | |
line-height: 1.6; | |
} | |
@media (max-width: 600px) { | |
.start-screen p { | |
font-size: 0.9rem; | |
} | |
} | |
.instructions { | |
margin-top: 20px; | |
font-size: 0.9rem; | |
color: #00b4ff; | |
text-align: center; | |
} | |
@media (max-width: 600px) { | |
.instructions { | |
font-size: 0.8rem; | |
margin-top: 15px; | |
} | |
} | |
.power-up-indicator { | |
position: absolute; | |
bottom: 20px; | |
font-size: 0.8rem; | |
color: #00fffc; | |
text-align: center; | |
width: 100%; | |
display: none; | |
padding: 0 10px; | |
} | |
@media (max-width: 600px) { | |
.power-up-indicator { | |
font-size: 0.7rem; | |
bottom: 15px; | |
} | |
} | |
.glow { | |
animation: glow 1.5s infinite alternate; | |
} | |
@keyframes glow { | |
from { | |
text-shadow: 0 0 5px #00fffc; | |
} | |
to { | |
text-shadow: 0 0 15px #00fffc, 0 0 20px #0084ff; | |
} | |
} | |
.multiplayer-options { | |
display: flex; | |
gap: 15px; | |
margin-top: 20px; | |
flex-wrap: wrap; | |
justify-content: center; | |
} | |
@media (max-width: 600px) { | |
.multiplayer-options { | |
gap: 10px; | |
} | |
} | |
.room-code-container { | |
margin-top: 20px; | |
display: none; | |
flex-direction: column; | |
align-items: center; | |
text-align: center; | |
padding: 0 20px; | |
} | |
.room-code { | |
font-size: 1.2rem; | |
letter-spacing: 3px; | |
margin: 10px 0; | |
padding: 8px 15px; | |
background: rgba(0, 255, 252, 0.1); | |
border: 1px solid #00fffc; | |
border-radius: 5px; | |
} | |
@media (max-width: 600px) { | |
.room-code { | |
font-size: 1rem; | |
letter-spacing: 2px; | |
padding: 6px 12px; | |
} | |
} | |
.waiting-message { | |
margin-top: 15px; | |
color: #00b4ff; | |
font-size: 1rem; | |
} | |
@media (max-width: 600px) { | |
.waiting-message { | |
font-size: 0.9rem; | |
} | |
} | |
.connection-status { | |
position: absolute; | |
top: 10px; | |
right: 10px; | |
font-size: 0.7rem; | |
display: flex; | |
align-items: center; | |
} | |
.connection-status .indicator { | |
width: 8px; | |
height: 8px; | |
border-radius: 50%; | |
margin-right: 5px; | |
} | |
.connection-status .connected { | |
background-color: #00ff00; | |
box-shadow: 0 0 5px #00ff00; | |
} | |
.connection-status .disconnected { | |
background-color: #ff0000; | |
box-shadow: 0 0 5px #ff0000; | |
} | |
.connection-status .connecting { | |
background-color: #ffff00; | |
box-shadow: 0 0 5px #ffff00; | |
animation: pulse 1s infinite; | |
} | |
.player-name { | |
position: absolute; | |
top: 50px; | |
font-size: 0.8rem; | |
} | |
@media (max-width: 600px) { | |
.player-name { | |
top: 40px; | |
font-size: 0.7rem; | |
} | |
} | |
#playerName { | |
left: 20%; | |
} | |
#opponentName { | |
right: 20%; | |
} | |
.touch-controls { | |
position: absolute; | |
bottom: 20px; | |
left: 0; | |
width: 100%; | |
display: flex; | |
justify-content: space-between; | |
padding: 0 30px; | |
z-index: 5; | |
display: none; | |
} | |
@media (max-width: 600px) { | |
.touch-controls { | |
display: flex; | |
} | |
} | |
.touch-btn { | |
width: 70px; | |
height: 70px; | |
background: rgba(0, 255, 252, 0.2); | |
border: 2px solid #00fffc; | |
border-radius: 50%; | |
display: flex; | |
align-items: center; | |
justify-content: center; | |
color: #00fffc; | |
font-size: 1.5rem; | |
user-select: none; | |
-webkit-user-select: none; | |
touch-action: manipulation; | |
} | |
@media (max-width: 400px) { | |
.touch-btn { | |
width: 60px; | |
height: 60px; | |
font-size: 1.2rem; | |
} | |
} | |
.orientation-warning { | |
position: fixed; | |
top: 0; | |
left: 0; | |
width: 100%; | |
height: 100%; | |
background-color: #0a0a1a; | |
color: #00fffc; | |
display: flex; | |
flex-direction: column; | |
justify-content: center; | |
align-items: center; | |
z-index: 100; | |
display: none; | |
text-align: center; | |
padding: 20px; | |
} | |
.orientation-warning i { | |
font-size: 3rem; | |
margin-bottom: 20px; | |
color: #ff00e4; | |
} | |
.orientation-warning h2 { | |
font-size: 1.5rem; | |
margin-bottom: 15px; | |
} | |
.orientation-warning p { | |
font-size: 1rem; | |
max-width: 300px; | |
line-height: 1.5; | |
} | |
</style> | |
</head> | |
<body> | |
<div class="game-container"> | |
<div class="center-line"></div> | |
<div class="paddle" id="playerPaddle"></div> | |
<div class="paddle" id="opponentPaddle" style="display: none;"></div> | |
<div class="paddle" id="aiPaddle"></div> | |
<div class="ball"></div> | |
<div class="score" id="playerScore">0</div> | |
<div class="score" id="opponentScore" style="display: none;">0</div> | |
<div class="score" id="aiScore">0</div> | |
<div class="player-name" id="playerName">YOU</div> | |
<div class="player-name" id="opponentName" style="display: none;">OPPONENT</div> | |
<div class="power-up-indicator" id="powerUpIndicator"></div> | |
<div class="touch-controls" id="touchControls"> | |
<div class="touch-btn" id="upBtn"><i class="fas fa-arrow-up"></i></div> | |
<div class="touch-btn" id="downBtn"><i class="fas fa-arrow-down"></i></div> | |
</div> | |
<div class="game-over"> | |
<h1>GAME OVER</h1> | |
<p id="gameResult"></p> | |
<button class="btn" id="restartBtn">PLAY AGAIN</button> | |
</div> | |
<div class="start-screen"> | |
<h1>CYBER PONG EXPLOSION</h1> | |
<p>Experience the ultimate futuristic pong battle with explosive effects and power-ups!</p> | |
<div class="multiplayer-options"> | |
<button class="btn" id="singlePlayerBtn">SINGLE PLAYER</button> | |
<button class="btn" id="multiplayerBtn">ONLINE MULTIPLAYER</button> | |
</div> | |
<div class="room-code-container" id="roomCodeContainer"> | |
<p>Share this code with your friend:</p> | |
<div class="room-code" id="roomCode"></div> | |
<div class="waiting-message" id="waitingMessage">Waiting for opponent to join...</div> | |
</div> | |
<div class="instructions"> | |
<p><i class="fas fa-arrow-up"></i> <i class="fas fa-arrow-down"></i> Move your paddle with UP/DOWN keys or touch controls</p> | |
<p>Collect power-ups to gain advantages!</p> | |
</div> | |
</div> | |
</div> | |
<div class="orientation-warning" id="orientationWarning"> | |
<i class="fas fa-rotate"></i> | |
<h2>Please rotate your device</h2> | |
<p>For the best gaming experience, please play in landscape mode.</p> | |
</div> | |
<div class="connection-status" id="connectionStatus"> | |
<div class="indicator disconnected"></div> | |
<span>Disconnected</span> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', () => { | |
// Game elements | |
const gameContainer = document.querySelector('.game-container'); | |
const playerPaddle = document.getElementById('playerPaddle'); | |
const aiPaddle = document.getElementById('aiPaddle'); | |
const opponentPaddle = document.getElementById('opponentPaddle'); | |
const ball = document.querySelector('.ball'); | |
const playerScore = document.getElementById('playerScore'); | |
const aiScore = document.getElementById('aiScore'); | |
const opponentScore = document.getElementById('opponentScore'); | |
const startScreen = document.querySelector('.start-screen'); | |
const gameOverScreen = document.querySelector('.game-over'); | |
const gameResult = document.getElementById('gameResult'); | |
const singlePlayerBtn = document.getElementById('singlePlayerBtn'); | |
const multiplayerBtn = document.getElementById('multiplayerBtn'); | |
const startBtn = document.getElementById('startBtn'); | |
const restartBtn = document.getElementById('restartBtn'); | |
const powerUpIndicator = document.getElementById('powerUpIndicator'); | |
const roomCodeContainer = document.getElementById('roomCodeContainer'); | |
const roomCodeDisplay = document.getElementById('roomCode'); | |
const waitingMessage = document.getElementById('waitingMessage'); | |
const connectionStatus = document.getElementById('connectionStatus'); | |
const playerNameDisplay = document.getElementById('playerName'); | |
const opponentNameDisplay = document.getElementById('opponentName'); | |
const touchControls = document.getElementById('touchControls'); | |
const upBtn = document.getElementById('upBtn'); | |
const downBtn = document.getElementById('downBtn'); | |
const orientationWarning = document.getElementById('orientationWarning'); | |
// Check if mobile device | |
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); | |
// Game state | |
let gameRunning = false; | |
let isMultiplayer = false; | |
let isHost = false; | |
let roomCode = ''; | |
let playerName = 'Player' + Math.floor(Math.random() * 1000); | |
let opponentName = 'Opponent'; | |
let playerScoreValue = 0; | |
let aiScoreValue = 0; | |
let opponentScoreValue = 0; | |
let gameSpeed = 1; | |
let lastPowerUpTime = 0; | |
let activePowerUp = null; | |
let powerUpEndTime = 0; | |
let touchUpActive = false; | |
let touchDownActive = false; | |
// Ball properties | |
let ballX = 400; | |
let ballY = 250; | |
let ballSpeedX = 3 * gameSpeed; // Velocità iniziale più lenta | |
let ballSpeedY = 3 * gameSpeed; // Velocità iniziale più lenta | |
let ballSize = 20; | |
let speedIncreaseFactor = 1.05; // Fattore di incremento della velocità ad ogni rimbalzo | |
// Paddle properties | |
const paddleHeight = isMobile ? 80 : 100; | |
const paddleWidth = isMobile ? 12 : 15; | |
let playerPaddleY = 200; | |
let aiPaddleY = 200; | |
let opponentPaddleY = 200; | |
const paddleSpeed = 8 * gameSpeed; | |
// Game area dimensions | |
const gameWidth = gameContainer.clientWidth; | |
const gameHeight = gameContainer.clientHeight; | |
// WebSocket connection | |
let socket = null; | |
let reconnectAttempts = 0; | |
const maxReconnectAttempts = 5; | |
// Power-ups | |
const powerUps = [ | |
{ type: 'speed', color: '#ff00e4', duration: 5000, effect: () => { gameSpeed = 1.5; } }, | |
{ type: 'size', color: '#00ff00', duration: 7000, effect: () => { playerPaddle.style.height = (paddleHeight * 1.5) + 'px'; } }, | |
{ type: 'freeze', color: '#00b4ff', duration: 3000, effect: () => { | |
if (isMultiplayer) { | |
opponentPaddle.style.opacity = '0.5'; | |
} else { | |
aiPaddle.style.opacity = '0.5'; | |
} | |
}} | |
]; | |
// Initialize game | |
init(); | |
function init() { | |
// Set up event listeners | |
singlePlayerBtn.addEventListener('click', () => { | |
isMultiplayer = false; | |
startGame(); | |
}); | |
multiplayerBtn.addEventListener('click', startMultiplayerGame); | |
restartBtn.addEventListener('click', startGame); | |
// Generate a random player name | |
playerName = 'Player' + Math.floor(Math.random() * 1000); | |
playerNameDisplay.textContent = playerName; | |
// Initialize connection status | |
updateConnectionStatus('disconnected'); | |
// Set up touch controls | |
if (isMobile) { | |
touchControls.style.display = 'flex'; | |
upBtn.addEventListener('touchstart', (e) => { | |
e.preventDefault(); | |
touchUpActive = true; | |
}); | |
upBtn.addEventListener('touchend', (e) => { | |
e.preventDefault(); | |
touchUpActive = false; | |
}); | |
downBtn.addEventListener('touchstart', (e) => { | |
e.preventDefault(); | |
touchDownActive = true; | |
}); | |
downBtn.addEventListener('touchend', (e) => { | |
e.preventDefault(); | |
touchDownActive = false; | |
}); | |
// Prevent scrolling when touching game area | |
gameContainer.addEventListener('touchmove', (e) => { | |
e.preventDefault(); | |
}, { passive: false }); | |
} | |
// Check orientation on mobile | |
if (isMobile) { | |
checkOrientation(); | |
window.addEventListener('resize', checkOrientation); | |
window.addEventListener('orientationchange', checkOrientation); | |
} | |
} | |
// Check device orientation | |
function checkOrientation() { | |
if (isMobile) { | |
const isPortrait = window.innerHeight > window.innerWidth; | |
if (Portrait) { | |
orientationWarning.style.display = 'flex'; | |
gameContainer.style.display = 'none'; | |
} else { | |
orientationWarning.style.display = 'none'; | |
gameContainer.style.display = 'block'; | |
} | |
} | |
} | |
// Start multiplayer game | |
function startMultiplayerGame() { | |
isMultiplayer = true; | |
connectToServer(); | |
// Show room code input | |
roomCodeContainer.style.display = 'flex'; | |
waitingMessage.textContent = 'Connecting to server...'; | |
} | |
// Connect to WebSocket server | |
function connectToServer() { | |
updateConnectionStatus('connecting'); | |
// In a real implementation, you would connect to your actual WebSocket server | |
// For this example, we'll simulate a connection with a delay | |
setTimeout(() => { | |
// Simulate successful connection | |
updateConnectionStatus('connected'); | |
// Generate a random room code | |
roomCode = generateRoomCode(); | |
roomCodeDisplay.textContent = roomCode; | |
waitingMessage.textContent = 'Waiting for opponent to join...'; | |
// Simulate opponent joining after a delay | |
setTimeout(() => { | |
opponentName = 'Opponent' + Math.floor(Math.random() * 1000); | |
opponentNameDisplay.textContent = opponentName; | |
waitingMessage.textContent = 'Opponent joined! Starting game...'; | |
// Start the game after a short delay | |
setTimeout(() => { | |
startGame(); | |
}, 1000); | |
}, 2000); | |
}, 1000); | |
} | |
// Generate a random room code | |
function generateRoomCode() { | |
const chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789'; | |
let result = ''; | |
for (let i = 0; i < 5; i++) { | |
result += chars.charAt(Math.floor(Math.random() * chars.length)); | |
} | |
return result; | |
} | |
// Update connection status UI | |
function updateConnectionStatus(status) { | |
const indicator = connectionStatus.querySelector('.indicator'); | |
const text = connectionStatus.querySelector('span'); | |
indicator.className = 'indicator'; | |
switch(status) { | |
case 'connected': | |
indicator.classList.add('connected'); | |
text.textContent = 'Connected'; | |
break; | |
case 'disconnected': | |
indicator.classList.add('disconnected'); | |
text.textContent = 'Disconnected'; | |
break; | |
case 'connecting': | |
indicator.classList.add('connecting'); | |
text.textContent = 'Connecting...'; | |
break; | |
} | |
} | |
// Start game | |
function startGame() { | |
// Reset scores | |
playerScoreValue = 0; | |
aiScoreValue = 0; | |
opponentScoreValue = 0; | |
playerScore.textContent = '0'; | |
aiScore.textContent = '0'; | |
opponentScore.textContent = '0'; | |
// Show/hide appropriate elements based on game mode | |
if (isMultiplayer) { | |
aiPaddle.style.display = 'none'; | |
opponentPaddle.style.display = 'block'; | |
aiScore.style.display = 'none'; | |
opponentScore.style.display = 'block'; | |
opponentNameDisplay.style.display = 'block'; | |
} else { | |
aiPaddle.style.display = 'block'; | |
opponentPaddle.style.display = 'none'; | |
aiScore.style.display = 'block'; | |
opponentScore.style.display = 'none'; | |
opponentNameDisplay.style.display = 'none'; | |
} | |
// Reset positions | |
ballX = gameWidth / 2; | |
ballY = gameHeight / 2; | |
playerPaddleY = (gameHeight - paddleHeight) / 2; | |
aiPaddleY = (gameHeight - paddleHeight) / 2; | |
opponentPaddleY = (gameHeight - paddleHeight) / 2; | |
// Reset ball speed and direction (più lento all'inizio) | |
ballSpeedX = 3 * (Math.random() > 0.5 ? 1 : -1); | |
ballSpeedY = 3 * (Math.random() > 0.5 ? 1 : -1); | |
// Reset power-ups | |
clearPowerUps(); | |
activePowerUp = null; | |
powerUpIndicator.style.display = 'none'; | |
// Hide screens | |
startScreen.style.display = 'none'; | |
roomCodeContainer.style.display = 'none'; | |
gameOverScreen.style.display = 'none'; | |
// Start game loop | |
gameRunning = true; | |
requestAnimationFrame(gameLoop); | |
} | |
// Game loop | |
function gameLoop(timestamp) { | |
if (!gameRunning) return; | |
update(); | |
render(); | |
// Check for power-up expiration | |
if (activePowerUp && timestamp >= powerUpEndTime) { | |
clearPowerUps(); | |
} | |
// Spawn power-ups randomly | |
if (timestamp - lastPowerUpTime > 10000 && Math.random() < 0.01) { | |
spawnPowerUp(); | |
lastPowerUpTime = timestamp; | |
} | |
// In multiplayer mode, send paddle position updates | |
if (isMultiplayer && socket && socket.readyState === WebSocket.OPEN) { | |
// Simulate sending data to server | |
const data = { | |
type: 'paddleUpdate', | |
y: playerPaddleY, | |
room: roomCode | |
}; | |
// In a real implementation: socket.send(JSON.stringify(data)); | |
} | |
requestAnimationFrame(gameLoop); | |
} | |
// Update game state | |
function update() { | |
// Move player paddle | |
if ((keys.ArrowUp || touchUpActive) && playerPaddleY > 0) { | |
playerPaddleY -= paddleSpeed; | |
} | |
if ((keys.ArrowDown || touchDownActive) && playerPaddleY < gameHeight - paddleHeight) { | |
playerPaddleY += paddleSpeed; | |
} | |
// AI or opponent paddle movement | |
if (isMultiplayer) { | |
// In a real multiplayer game, opponent paddle position would come from the server | |
// For this example, we'll just simulate some movement | |
if (Math.random() < 0.02) { | |
const moveDirection = Math.random() > 0.5 ? 1 : -1; | |
opponentPaddleY += paddleSpeed * 0.7 * moveDirection; | |
opponentPaddleY = Math.max(0, Math.min(gameHeight - paddleHeight, opponentPaddleY)); | |
} | |
} else { | |
// AI paddle movement (simple tracking) | |
const aiPaddleCenter = aiPaddleY + paddleHeight / 2; | |
if (aiPaddleCenter < ballY - 15) { | |
aiPaddleY += paddleSpeed * 0.7 * gameSpeed; | |
} else if (aiPaddleCenter > ballY + 15) { | |
aiPaddleY -= paddleSpeed * 0.7 * gameSpeed; | |
} | |
// Keep AI paddle in bounds | |
aiPaddleY = Math.max(0, Math.min(gameHeight - paddleHeight, aiPaddleY)); | |
} | |
// Move ball | |
ballX += ballSpeedX * gameSpeed; | |
ballY += ballSpeedY * gameSpeed; | |
// Ball collision with top and bottom walls | |
if (ballY <= 0 || ballY >= gameHeight - ballSize) { | |
ballSpeedY = -ballSpeedY; | |
createParticles(ballX, ballY, 10, '#ff00e4'); | |
} | |
// Ball collision with player paddle | |
if ( | |
ballX <= 20 + paddleWidth && | |
ballY + ballSize >= playerPaddleY && | |
ballY <= playerPaddleY + paddleHeight | |
) { | |
// Aumenta la velocità della pallina ad ogni rimbalzo | |
ballSpeedX = Math.abs(ballSpeedX) * speedIncreaseFactor; | |
// Add angle based on where ball hits paddle | |
const hitPosition = (ballY - playerPaddleY) / paddleHeight; | |
ballSpeedY = (hitPosition - 0.5) * 10; | |
createParticles(ballX, ballY, 15, '#00fffc'); | |
// In multiplayer, notify server about the hit | |
if (isMultiplayer && socket) { | |
const data = { | |
type: 'ballHit', | |
x: ballX, | |
y: ballY, | |
speedX: ballSpeedX, | |
speedY: ballSpeedY, | |
room: roomCode | |
}; | |
// In a real implementation: socket.send(JSON.stringify(data)); | |
} | |
} | |
// Ball collision with opponent/AI paddle | |
const opponentPaddleToUse = isMultiplayer ? opponentPaddle : aiPaddle; | |
const opponentPaddleYToUse = isMultiplayer ? opponentPaddleY : aiPaddleY; | |
if ( | |
ballX >= gameWidth - 20 - paddleWidth - ballSize && | |
ballY + ballSize >= opponentPaddleYToUse && | |
ballY <= opponentPaddleYToUse + paddleHeight | |
) { | |
// Aumenta la velocità della pallina ad ogni rimbalzo | |
ballSpeedX = -Math.abs(ballSpeedX) * speedIncreaseFactor; | |
// Add angle based on where ball hits paddle | |
const hitPosition = (ballY - opponentPaddleYToUse) / paddleHeight; | |
ballSpeedY = (hitPosition - 0.5) * 10; | |
createParticles(ballX, ballY, 15, '#ff00e4'); | |
} | |
// Ball out of bounds (score) | |
if (ballX < 0) { | |
if (isMultiplayer) { | |
opponentScoreValue++; | |
opponentScore.textContent = opponentScoreValue; | |
if (socket) { | |
const data = { | |
type: 'score', | |
player: 'opponent', | |
room: roomCode | |
}; | |
// In a real implementation: socket.send(JSON.stringify(data)); | |
} | |
} else { | |
aiScoreValue++; | |
aiScore.textContent = aiScoreValue; | |
} | |
resetBall(); | |
const winningScore = isMultiplayer ? 5 : 5; | |
if ((isMultiplayer && opponentScoreValue >= winningScore) || | |
(!isMultiplayer && aiScoreValue >= winningScore)) { | |
endGame(false); | |
} | |
} | |
if (ballX > gameWidth) { | |
if (isMultiplayer) { | |
playerScoreValue++; | |
playerScore.textContent = playerScoreValue; | |
if (socket) { | |
const data = { | |
type: 'score', | |
player: 'player', | |
room: roomCode | |
}; | |
// In a real implementation: socket.send(JSON.stringify(data)); | |
} | |
} else { | |
playerScoreValue++; | |
playerScore.textContent = playerScoreValue; | |
} | |
resetBall(); | |
const winningScore = isMultiplayer ? 5 : 5; | |
if (playerScoreValue >= winningScore) { | |
endGame(true); | |
} | |
} | |
// Update power-ups | |
document.querySelectorAll('.power-up').forEach(powerUp => { | |
const rect = powerUp.getBoundingClientRect(); | |
const gameRect = gameContainer.getBoundingClientRect(); | |
const powerUpX = rect.left - gameRect.left; | |
const powerUpY = rect.top - gameRect.top; | |
const powerUpSize = 20; | |
// Check collision with ball | |
if ( | |
ballX + ballSize > powerUpX && | |
ballX < powerUpX + powerUpSize && | |
ballY + ballSize > powerUpY && | |
ballY < powerUpY + powerUpSize | |
) { | |
const powerUpType = powerUp.classList.contains('speed') ? 'speed' : | |
powerUp.classList.contains('size') ? 'size' : 'freeze'; | |
activatePowerUp(powerUpType); | |
powerUp.remove(); | |
// In multiplayer, notify server about power-up collection | |
if (isMultiplayer && socket) { | |
const data = { | |
type: 'powerUp', | |
powerUpType: powerUpType, | |
room: roomCode | |
}; | |
// In a real implementation: socket.send(JSON.stringify(data)); | |
} | |
} | |
}); | |
} | |
// Render game | |
function render() { | |
// Update paddle positions | |
playerPaddle.style.top = playerPaddleY + 'px'; | |
if (isMultiplayer) { | |
opponentPaddle.style.top = opponentPaddleY + 'px'; | |
} else { | |
aiPaddle.style.top = aiPaddleY + 'px'; | |
} | |
// Update ball position | |
ball.style.left = ballX + 'px'; | |
ball.style.top = ballY + 'px'; | |
ball.style.width = ballSize + 'px'; | |
ball.style.height = ballSize + 'px'; | |
// Update particles | |
document.querySelectorAll('.particle').forEach(particle => { | |
const opacity = parseFloat(particle.style.opacity); | |
particle.style.opacity = opacity - 0.02; | |
if (opacity <= 0) { | |
particle.remove(); | |
} | |
}); | |
} | |
// Reset ball after scoring | |
function resetBall() { | |
ballX = gameWidth / 2; | |
ballY = gameHeight / 2; | |
// Resetta la velocità a quella iniziale più lenta | |
ballSpeedX = 3 * (Math.random() > 0.5 ? 1 : -1); | |
ballSpeedY = 3 * (Math.random() > 0.5 ? 1 : -1); | |
gameSpeed = 1; | |
// In multiplayer, notify server about ball reset | |
if (isMultiplayer && socket) { | |
const data = { | |
type: 'ballReset', | |
room: roomCode | |
}; | |
// In a real implementation: socket.send(JSON.stringify(data)); | |
} | |
} | |
// End game | |
function endGame(playerWon) { | |
gameRunning = false; | |
if (isMultiplayer) { | |
gameResult.textContent = playerWon ? | |
`YOU WIN! ${opponentName.toUpperCase()} WAS NO MATCH!` : | |
`DEFEAT! ${opponentName.toUpperCase()} TRIUMPHED!`; | |
// Notify server about game end | |
if (socket) { | |
const data = { | |
type: 'gameOver', | |
winner: playerWon ? playerName : opponentName, | |
room: roomCode | |
}; | |
// In a real implementation: socket.send(JSON.stringify(data)); | |
} | |
} else { | |
gameResult.textContent = playerWon ? | |
'YOU WIN! CYBER DOMINATION ACHIEVED!' : | |
'DEFEAT! THE AI HAS TRIUMPHED!'; | |
} | |
gameOverScreen.style.display = 'flex'; | |
} | |
// Create explosion particles | |
function createParticles(x, y, count, color) { | |
for (let i = 0; i < count; i++) { | |
const particle = document.createElement('div'); | |
particle.className = 'particle'; | |
particle.style.left = x + 'px'; | |
particle.style.top = y + 'px'; | |
particle.style.backgroundColor = color; | |
particle.style.opacity = '1'; | |
// Random direction and speed | |
const angle = Math.random() * Math.PI * 2; | |
const speed = Math.random() * 3 + 1; | |
const vx = Math.cos(angle) * speed; | |
const vy = Math.sin(angle) * speed; | |
gameContainer.appendChild(particle); | |
// Animate particle | |
let frame = 0; | |
const animateParticle = () => { | |
frame++; | |
const currentX = parseFloat(particle.style.left); | |
const currentY = parseFloat(particle.style.top); | |
particle.style.left = (currentX + vx) + 'px'; | |
particle.style.top = (currentY + vy) + 'px'; | |
if (frame < 30) { | |
requestAnimationFrame(animateParticle); | |
} | |
}; | |
animateParticle(); | |
} | |
} | |
// Spawn power-up | |
function spawnPowerUp() { | |
if (activePowerUp) return; | |
const powerUp = powerUps[Math.floor(Math.random() * powerUps.length)]; | |
const powerUpElement = document.createElement('div'); | |
powerUpElement.className = `power-up ${powerUp.type}`; | |
powerUpElement.style.left = (Math.random() * (gameWidth - 50) + 25) + 'px'; | |
powerUpElement.style.top = (Math.random() * (gameHeight - 50) + 25) + 'px'; | |
powerUpElement.style.backgroundColor = powerUp.color; | |
powerUpElement.style.boxShadow = `0 0 10px ${powerUp.color}`; | |
gameContainer.appendChild(powerUpElement); | |
// Remove power-up after 5 seconds if not collected | |
setTimeout(() => { | |
if (powerUpElement.parentNode) { | |
powerUpElement.remove(); | |
} | |
}, 5000); | |
// In multiplayer, notify server about power-up spawn | |
if (isMultiplayer && socket) { | |
const rect = powerUpElement.getBoundingClientRect(); | |
const gameRect = gameContainer.getBoundingClientRect(); | |
const x = rect.left - gameRect.left; | |
const y = rect.top - gameRect.top; | |
const data = { | |
type: 'powerUpSpawn', | |
powerUpType: powerUp.type, | |
x: x, | |
y: y, | |
room: roomCode | |
}; | |
// In a real implementation: socket.send(JSON.stringify(data)); | |
} | |
} | |
// Activate power-up | |
function activatePowerUp(type) { | |
clearPowerUps(); | |
const powerUp = powerUps.find(p => p.type === type); | |
if (!powerUp) return; | |
powerUp.effect(); | |
activePowerUp = type; | |
powerUpEndTime = performance.now() + powerUp.duration; | |
// Show indicator | |
powerUpIndicator.textContent = `POWER UP: ${type.toUpperCase()}!`; | |
powerUpIndicator.style.color = powerUp.color; | |
powerUpIndicator.style.display = 'block'; | |
powerUpIndicator.classList.add('glow'); | |
// Remove indicator after duration | |
setTimeout(() => { | |
powerUpIndicator.style.display = 'none'; | |
powerUpIndicator.classList.remove('glow'); | |
}, powerUp.duration); | |
} | |
// Clear active power-ups | |
function clearPowerUps() { | |
if (!activePowerUp) return; | |
// Reset game speed | |
gameSpeed = 1; | |
// Reset paddle size | |
playerPaddle.style.height = paddleHeight + 'px'; | |
// Reset opponent/AI paddle opacity | |
if (isMultiplayer) { | |
opponentPaddle.style.opacity = '1'; | |
} else { | |
aiPaddle.style.opacity = '1'; | |
} | |
activePowerUp = null; | |
} | |
// Handle WebSocket messages | |
function handleSocketMessage(event) { | |
try { | |
const data = JSON.parse(event.data); | |
switch(data.type) { | |
case 'paddleUpdate': | |
if (data.player !== playerName) { | |
opponentPaddleY = data.y; | |
} | |
break; | |
case 'ballUpdate': | |
ballX = data.x; | |
ballY = data.y; | |
ballSpeedX = data.speedX; | |
ballSpeedY = data.speedY; | |
break; | |
case 'powerUpSpawn': | |
// Spawn power-up at specified position | |
const powerUpElement = document.createElement('div'); | |
powerUpElement.className = `power-up ${data.powerUpType}`; | |
powerUpElement.style.left = data.x + 'px'; | |
powerUpElement.style.top = data.y + 'px'; | |
powerUpElement.style.backgroundColor = powerUps.find(p => p.type === data.powerUpType).color; | |
powerUpElement.style.boxShadow = `0 0 10px ${powerUps.find(p => p.type === data.powerUpType).color}`; | |
gameContainer.appendChild(powerUpElement); | |
break; | |
case 'score': | |
if (data.player === 'player') { | |
playerScoreValue = data.score; | |
playerScore.textContent = playerScoreValue; | |
} else { | |
opponentScoreValue = data.score; | |
opponentScore.textContent = opponentScoreValue; | |
} | |
break; | |
case 'gameOver': | |
gameRunning = false; | |
gameResult.textContent = data.winner === playerName ? | |
'YOU WIN! CYBER DOMINATION ACHIEVED!' : | |
'DEFEAT! YOUR OPPONENT TRIUMPHED!'; | |
gameOverScreen.style.display = 'flex'; | |
break; | |
case 'playerJoined': | |
opponentName = data.playerName; | |
opponentNameDisplay.textContent = opponentName; | |
waitingMessage.textContent = `${opponentName} has joined! Starting game...`; | |
setTimeout(() => { | |
startGame(); | |
}, 1000); | |
break; | |
} | |
} catch (e) { | |
console.error('Error processing message:', e); | |
} | |
} | |
// Keyboard input | |
const keys = {}; | |
document.addEventListener('keydown', (e) => { | |
keys[e.key] = true; | |
}); | |
document.addEventListener('keyup', (e) => { | |
keys[e.key] = false; | |
}); | |
}); | |
</script> | |
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <a href="https://enzostvs-deepsite.hf.space" style="color: #fff;" target="_blank" >DeepSite</a> <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;"></p></body> | |
</html> |