|
<!DOCTYPE html> |
|
<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>Mobile CyberPong</title> |
|
<style> |
|
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Rajdhani:wght@300;500&display=swap'); |
|
|
|
:root { |
|
--neon-pink: #ff00ff; |
|
--neon-blue: #00ffff; |
|
--neon-purple: #9461fb; |
|
--neon-green: #00ff88; |
|
--dark-bg: #0a0a1a; |
|
--grid-color: rgba(0, 255, 255, 0.1); |
|
} |
|
|
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
-webkit-tap-highlight-color: transparent; |
|
touch-action: manipulation; |
|
} |
|
|
|
body { |
|
background-color: var(--dark-bg); |
|
color: white; |
|
font-family: 'Rajdhani', sans-serif; |
|
overflow: hidden; |
|
height: 100vh; |
|
width: 100vw; |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
justify-content: center; |
|
position: relative; |
|
overflow: hidden; |
|
} |
|
|
|
body::before { |
|
content: ""; |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background-image: |
|
linear-gradient(var(--grid-color) 1px, transparent 1px), |
|
linear-gradient(90deg, var(--grid-color) 1px, transparent 1px); |
|
background-size: 20px 20px; |
|
opacity: 0.3; |
|
z-index: -1; |
|
} |
|
|
|
h1 { |
|
font-family: 'Orbitron', sans-serif; |
|
color: var(--neon-pink); |
|
text-shadow: 0 0 5px var(--neon-pink); |
|
margin-bottom: 10px; |
|
font-size: 1.8rem; |
|
letter-spacing: 2px; |
|
text-align: center; |
|
} |
|
|
|
.game-container { |
|
position: relative; |
|
width: 95vw; |
|
height: 60vh; |
|
max-width: 600px; |
|
max-height: 500px; |
|
border: 1px solid var(--neon-blue); |
|
box-shadow: 0 0 10px var(--neon-blue), inset 0 0 10px var(--neon-blue); |
|
overflow: hidden; |
|
background-color: rgba(10, 10, 26, 0.7); |
|
touch-action: none; |
|
} |
|
|
|
.paddle { |
|
position: absolute; |
|
width: 12px; |
|
height: 20vw; |
|
max-height: 100px; |
|
min-height: 80px; |
|
background: linear-gradient(to bottom, var(--neon-blue), var(--neon-purple)); |
|
border-radius: 3px; |
|
box-shadow: 0 0 10px var(--neon-blue); |
|
} |
|
|
|
#player { |
|
left: 10px; |
|
top: 50%; |
|
transform: translateY(-50%); |
|
} |
|
|
|
#computer { |
|
right: 10px; |
|
top: 50%; |
|
transform: translateY(-50%); |
|
} |
|
|
|
.ball { |
|
position: absolute; |
|
width: 15px; |
|
height: 15px; |
|
border-radius: 50%; |
|
box-shadow: 0 0 10px currentColor; |
|
} |
|
|
|
#ball1 { |
|
background-color: var(--neon-pink); |
|
color: var(--neon-pink); |
|
} |
|
|
|
#ball2 { |
|
background-color: var(--neon-green); |
|
color: var(--neon-green); |
|
} |
|
|
|
#ball3 { |
|
background-color: var(--neon-blue); |
|
color: var(--neon-blue); |
|
} |
|
|
|
.score { |
|
display: flex; |
|
justify-content: space-between; |
|
width: 95vw; |
|
max-width: 600px; |
|
margin-bottom: 10px; |
|
font-family: 'Orbitron', sans-serif; |
|
font-size: 1.2rem; |
|
} |
|
|
|
.player-score, .cpu-score { |
|
text-align: center; |
|
padding: 8px 15px; |
|
border: 1px solid var(--neon-purple); |
|
border-radius: 5px; |
|
background-color: rgba(0, 0, 0, 0.5); |
|
box-shadow: 0 0 8px var(--neon-purple); |
|
flex: 1; |
|
margin: 0 5px; |
|
} |
|
|
|
.player-score { |
|
color: var(--neon-blue); |
|
text-shadow: 0 0 5px var(--neon-blue); |
|
} |
|
|
|
.cpu-score { |
|
color: var(--neon-pink); |
|
text-shadow: 0 0 5px var(--neon-pink); |
|
} |
|
|
|
.controls { |
|
margin-top: 10px; |
|
text-align: center; |
|
color: var(--neon-green); |
|
font-size: 0.9rem; |
|
letter-spacing: 0.5px; |
|
padding: 0 10px; |
|
} |
|
|
|
.pulse { |
|
animation: pulse 1.5s infinite alternate; |
|
} |
|
|
|
@keyframes pulse { |
|
from { |
|
opacity: 0.7; |
|
text-shadow: 0 0 3px currentColor; |
|
} |
|
to { |
|
opacity: 1; |
|
text-shadow: 0 0 10px currentColor; |
|
} |
|
} |
|
|
|
.message { |
|
position: absolute; |
|
top: 50%; |
|
left: 50%; |
|
transform: translate(-50%, -50%); |
|
font-family: 'Orbitron', sans-serif; |
|
font-size: 1.5rem; |
|
text-align: center; |
|
opacity: 0; |
|
transition: opacity 0.5s; |
|
pointer-events: none; |
|
width: 100%; |
|
padding: 0 20px; |
|
} |
|
|
|
.sensor { |
|
position: absolute; |
|
bottom: 0; |
|
width: 100%; |
|
height: 1px; |
|
background-color: transparent; |
|
} |
|
|
|
.trail { |
|
position: absolute; |
|
width: 3px; |
|
height: 3px; |
|
border-radius: 50%; |
|
pointer-events: none; |
|
} |
|
|
|
.mobile-controls { |
|
display: none; |
|
width: 100%; |
|
position: absolute; |
|
bottom: 20px; |
|
justify-content: center; |
|
gap: 20px; |
|
z-index: 10; |
|
} |
|
|
|
.mobile-btn { |
|
width: 60px; |
|
height: 60px; |
|
background: rgba(148, 97, 251, 0.3); |
|
border: 1px solid var(--neon-purple); |
|
border-radius: 50%; |
|
display: flex; |
|
justify-content: center; |
|
align-items: center; |
|
color: var(--neon-blue); |
|
font-size: 1.5rem; |
|
box-shadow: 0 0 10px var(--neon-purple); |
|
user-select: none; |
|
} |
|
|
|
@media (max-width: 768px) and (hover: none) { |
|
.mobile-controls { |
|
display: flex; |
|
} |
|
|
|
.controls { |
|
display: none; |
|
} |
|
|
|
.game-container { |
|
height: 55vh; |
|
} |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<h1>CYBER<span class="pulse" style="color: var(--neon-blue)">PONG</span></h1> |
|
|
|
<div class="score"> |
|
<div class="player-score">PLAYER: <span id="player-score">0</span></div> |
|
<div class="cpu-score">CPU: <span id="cpu-score">0</span></div> |
|
</div> |
|
|
|
<div class="game-container"> |
|
<div id="player" class="paddle"></div> |
|
<div id="computer" class="paddle"></div> |
|
|
|
<div id="ball1" class="ball"></div> |
|
<div id="ball2" class="ball"></div> |
|
<div id="ball3" class="ball"></div> |
|
|
|
<div class="message" id="message"></div> |
|
<div class="sensor" id="sensor"></div> |
|
</div> |
|
|
|
<div class="controls"> |
|
Use <span style="color: var(--neon-blue)">W/S</span> or <span style="color: var(--neon-blue)">↑/↓</span> keys to move | First to <span style="color: var(--neon-pink)">10</span> wins |
|
</div> |
|
|
|
<div class="mobile-controls"> |
|
<div class="mobile-btn" id="up-btn">↑</div> |
|
<div class="mobile-btn" id="down-btn">↓</div> |
|
</div> |
|
|
|
<script> |
|
document.addEventListener('DOMContentLoaded', () => { |
|
|
|
const gameContainer = document.querySelector('.game-container'); |
|
const playerPaddle = document.getElementById('player'); |
|
const computerPaddle = document.getElementById('computer'); |
|
const balls = [document.getElementById('ball1'), document.getElementById('ball2'), document.getElementById('ball3')]; |
|
const playerScore = document.getElementById('player-score'); |
|
const cpuScore = document.getElementById('cpu-score'); |
|
const message = document.getElementById('message'); |
|
const sensor = document.getElementById('sensor'); |
|
const upBtn = document.getElementById('up-btn'); |
|
const downBtn = document.getElementById('down-btn'); |
|
|
|
|
|
const isMobile = /Mobi|Android|iPhone|iPad|iPod/i.test(navigator.userAgent); |
|
|
|
|
|
const state = { |
|
playerScore: 0, |
|
computerScore: 0, |
|
playerY: gameContainer.offsetHeight / 2 - playerPaddle.offsetHeight / 2, |
|
computerY: gameContainer.offsetHeight / 2 - computerPaddle.offsetHeight / 2, |
|
balls: [ |
|
{ |
|
x: gameContainer.offsetWidth / 2, |
|
y: gameContainer.offsetHeight / 2, |
|
dx: isMobile ? 3 : 4, |
|
dy: (isMobile ? 3 : 4) * (Math.random() * 0.6 + 0.7) * (Math.random() > 0.5 ? 1 : -1), |
|
speed: isMobile ? 3 : 4, |
|
active: true, |
|
lastHit: null |
|
}, |
|
{ |
|
x: gameContainer.offsetWidth / 2 - 30, |
|
y: gameContainer.offsetHeight / 2 + 30, |
|
dx: isMobile ? -3 : -4, |
|
dy: -(isMobile ? 3 : 4) * (Math.random() * 0.6 + 0.7) * (Math.random() > 0.5 ? 1 : -1), |
|
speed: isMobile ? 3 : 4, |
|
active: true, |
|
lastHit: null |
|
}, |
|
{ |
|
x: gameContainer.offsetWidth / 2 + 30, |
|
y: gameContainer.offsetHeight / 2 - 30, |
|
dx: isMobile ? -2 : -3, |
|
dy: (isMobile ? 2 : 3) * (Math.random() * 0.6 + 0.7) * (Math.random() > 0.5 ? 1 : -1), |
|
speed: isMobile ? 2 : 3, |
|
active: true, |
|
lastHit: null |
|
} |
|
], |
|
gameRunning: false, |
|
keys: {}, |
|
trailParticles: [], |
|
mobileTouch: { |
|
touchStartY: 0, |
|
paddleStartY: 0, |
|
isTouching: false |
|
} |
|
}; |
|
|
|
|
|
function init() { |
|
render(); |
|
addEventListeners(); |
|
displayMessage("CYBERPONG", 1500, () => { |
|
displayMessage("READY", 800, () => { |
|
displayMessage("GO!", 400, () => { |
|
state.gameRunning = true; |
|
gameLoop(); |
|
}); |
|
}); |
|
}); |
|
|
|
|
|
window.addEventListener('resize', handleResize); |
|
} |
|
|
|
|
|
function handleResize() { |
|
if (!state.gameRunning) return; |
|
|
|
|
|
state.playerY = gameContainer.offsetHeight / 2 - playerPaddle.offsetHeight / 2; |
|
state.computerY = gameContainer.offsetHeight / 2 - computerPaddle.offsetHeight / 2; |
|
|
|
|
|
state.balls.forEach(ball => { |
|
ball.x = gameContainer.offsetWidth / 2; |
|
ball.y = gameContainer.offsetHeight / 2; |
|
}); |
|
|
|
render(); |
|
} |
|
|
|
|
|
function gameLoop() { |
|
if (!state.gameRunning) return; |
|
|
|
movePlayer(); |
|
moveComputer(); |
|
|
|
state.balls.forEach((ball, index) => { |
|
if (ball.active) { |
|
moveBall(index); |
|
checkBallCollision(index); |
|
} |
|
}); |
|
|
|
updateTrails(); |
|
render(); |
|
|
|
if (state.playerScore >= 10 || state.computerScore >= 10) { |
|
endGame(); |
|
return; |
|
} |
|
|
|
requestAnimationFrame(gameLoop); |
|
} |
|
|
|
|
|
function movePlayer() { |
|
if (state.keys['ArrowUp'] || state.keys['w'] || state.keys['up']) { |
|
state.playerY = Math.max(0, state.playerY - (isMobile ? 6 : 8)); |
|
} |
|
if (state.keys['ArrowDown'] || state.keys['s'] || state.keys['down']) { |
|
state.playerY = Math.min( |
|
gameContainer.offsetHeight - playerPaddle.offsetHeight, |
|
state.playerY + (isMobile ? 6 : 8) |
|
); |
|
} |
|
} |
|
|
|
|
|
function moveComputer() { |
|
const activeBalls = state.balls.filter(ball => ball.active); |
|
|
|
if (activeBalls.length === 0) return; |
|
|
|
|
|
let targetBall = null; |
|
let minDistance = Infinity; |
|
|
|
activeBalls.forEach(ball => { |
|
if (ball.dx > 0) { |
|
const distance = gameContainer.offsetWidth - ball.x; |
|
if (distance < minDistance) { |
|
minDistance = distance; |
|
targetBall = ball; |
|
} |
|
} |
|
}); |
|
|
|
|
|
if (!targetBall) { |
|
activeBalls.forEach(ball => { |
|
const distance = gameContainer.offsetWidth - ball.x; |
|
if (distance < minDistance) { |
|
minDistance = distance; |
|
targetBall = ball; |
|
} |
|
}); |
|
} |
|
|
|
if (!targetBall) return; |
|
|
|
|
|
const predictedPosition = targetBall.y + (Math.random() * (isMobile ? 30 : 40) - (isMobile ? 15 : 20)); |
|
const paddleCenter = state.computerY + computerPaddle.offsetHeight / 2; |
|
|
|
if (paddleCenter < predictedPosition - 10) { |
|
state.computerY = Math.min( |
|
gameContainer.offsetHeight - computerPaddle.offsetHeight, |
|
state.computerY + (isMobile ? 3 : 5) * (Math.random() * 0.3 + 0.85) |
|
); |
|
} else if (paddleCenter > predictedPosition + 10) { |
|
state.computerY = Math.max( |
|
0, |
|
state.computerY - (isMobile ? 3 : 5) * (Math.random() * 0.3 + 0.85) |
|
); |
|
} |
|
} |
|
|
|
|
|
function moveBall(index) { |
|
const ball = state.balls[index]; |
|
ball.x += ball.dx; |
|
ball.y += ball.dy; |
|
|
|
|
|
if (Math.random() > (isMobile ? 0.8 : 0.7)) { |
|
state.trailParticles.push({ |
|
x: ball.x, |
|
y: ball.y, |
|
color: balls[index].style.backgroundColor, |
|
life: isMobile ? 15 : 20, |
|
size: Math.random() * (isMobile ? 2 : 3) + (isMobile ? 1 : 2) |
|
}); |
|
} |
|
} |
|
|
|
|
|
function checkBallCollision(index) { |
|
const ball = state.balls[index]; |
|
const ballElement = balls[index]; |
|
|
|
|
|
if (ball.y <= 0 || ball.y >= gameContainer.offsetHeight - ballElement.offsetHeight) { |
|
ball.dy = -ball.dy; |
|
|
|
|
|
for (let i = 0; i < (isMobile ? 3 : 5); i++) { |
|
state.trailParticles.push({ |
|
x: ball.x, |
|
y: ball.y, |
|
color: ballElement.style.backgroundColor, |
|
life: isMobile ? 8 : 10, |
|
size: Math.random() * (isMobile ? 2 : 3) + 1, |
|
dx: (Math.random() - 0.5) * (isMobile ? 2 : 3), |
|
dy: (Math.random() - 0.5) * (Math.random() > 0.5 ? 1 : -1) |
|
}); |
|
} |
|
|
|
return; |
|
} |
|
|
|
|
|
if ( |
|
ball.x <= playerPaddle.offsetWidth + 15 && |
|
ball.x >= 10 && |
|
ball.y + ballElement.offsetHeight >= state.playerY && |
|
ball.y <= state.playerY + playerPaddle.offsetHeight && |
|
ball.lastHit !== 'player' |
|
) { |
|
ball.dx = -ball.dx * (isMobile ? 1.03 : 1.05); |
|
|
|
|
|
const hitPosition = (ball.y + ballElement.offsetHeight/2 - state.playerY) / playerPaddle.offsetHeight; |
|
ball.dy = (hitPosition - 0.5) * (isMobile ? 6 : 8); |
|
|
|
ball.speed = Math.min(isMobile ? 8 : 10, ball.speed + 0.3); |
|
const speedRatio = ball.speed / (isMobile ? 3 : 4); |
|
ball.dx *= speedRatio; |
|
ball.dy *= speedRatio; |
|
|
|
ball.lastHit = 'player'; |
|
|
|
|
|
for (let i = 0; i < (isMobile ? 6 : 10); i++) { |
|
state.trailParticles.push({ |
|
x: 10 + playerPaddle.offsetWidth, |
|
y: ball.y, |
|
color: ballElement.style.backgroundColor, |
|
life: isMobile ? 10 : 15, |
|
size: Math.random() * (isMobile ? 3 : 4) + (isMobile ? 1 : 2), |
|
dx: (Math.random() - 0.5) * (isMobile ? 1.5 : 2), |
|
dy: (Math.random() - 0.5) * (Math.random() > 0.5 ? 1 : -1) |
|
}); |
|
} |
|
|
|
return; |
|
} |
|
|
|
|
|
if ( |
|
ball.x >= gameContainer.offsetWidth - computerPaddle.offsetWidth - 15 && |
|
ball.x <= gameContainer.offsetWidth - 10 && |
|
ball.y + ballElement.offsetHeight >= state.computerY && |
|
ball.y <= state.computerY + computerPaddle.offsetHeight && |
|
ball.lastHit !== 'computer' |
|
) { |
|
ball.dx = -ball.dx * (isMobile ? 1.03 : 1.05); |
|
|
|
|
|
const hitPosition = (ball.y + ballElement.offsetHeight/2 - state.computerY) / computerPaddle.offsetHeight; |
|
ball.dy = (hitPosition - 0.5) * (isMobile ? 6 : 8); |
|
|
|
ball.speed = Math.min(isMobile ? 8 : 10, ball.speed + 0.3); |
|
const speedRatio = ball.speed / (isMobile ? 3 : 4); |
|
ball.dx *= speedRatio; |
|
ball.dy *= speedRatio; |
|
|
|
ball.lastHit = 'computer'; |
|
|
|
|
|
for (let i = 0; i < (isMobile ? 6 : 10); i++) { |
|
state.trailParticles.push({ |
|
x: gameContainer.offsetWidth - 10 - computerPaddle.offsetWidth, |
|
y: ball.y, |
|
color: ballElement.style.backgroundColor, |
|
life: isMobile ? 10 : 15, |
|
size: Math.random() * (isMobile ? 3 : 4) + (isMobile ? 1 : 2), |
|
dx: (Math.random() - 0.5) * (isMobile ? 1.5 : 2), |
|
dy: (Math.random() - 0.5) * (Math.random() > 0.5 ? 1 : -1) |
|
}); |
|
} |
|
|
|
return; |
|
} |
|
|
|
|
|
if (ball.x > gameContainer.offsetWidth * 0.3 && ball.x < gameContainer.offsetWidth * 0.7) { |
|
ball.lastHit = null; |
|
} |
|
|
|
|
|
if (ball.x < 0) { |
|
state.computerScore++; |
|
cpuScore.textContent = state.computerScore; |
|
resetBall(index, 'player'); |
|
ballElement.style.display = 'none'; |
|
setTimeout(() => { |
|
ballElement.style.display = 'block'; |
|
}, 800); |
|
} else if (ball.x > gameContainer.offsetWidth) { |
|
state.playerScore++; |
|
playerScore.textContent = state.playerScore; |
|
resetBall(index, 'computer'); |
|
ballElement.style.display = 'none'; |
|
setTimeout(() => { |
|
ballElement.style.display = 'block'; |
|
}, 800); |
|
} |
|
} |
|
|
|
|
|
function resetBall(index, scorer) { |
|
const ball = state.balls[index]; |
|
ball.x = gameContainer.offsetWidth / 2; |
|
ball.y = gameContainer.offsetHeight / 2; |
|
|
|
if (scorer === 'player') { |
|
ball.dx = -Math.abs(ball.speed); |
|
} else { |
|
ball.dx = Math.abs(ball.speed); |
|
} |
|
|
|
ball.dy = ball.speed * (Math.random() * 0.6 + 0.7) * (Math.random() > 0.5 ? 1 : -1); |
|
ball.active = false; |
|
|
|
setTimeout(() => { |
|
ball.active = true; |
|
}, 800); |
|
} |
|
|
|
|
|
function updateTrails() { |
|
for (let i = state.trailParticles.length - 1; i >= 0; i--) { |
|
const particle = state.trailParticles[i]; |
|
particle.life--; |
|
|
|
if (particle.dx) particle.x += particle.dx; |
|
if (particle.dy) particle.y += particle.dy; |
|
|
|
if (particle.life <= 0 || |
|
particle.x < 0 || |
|
particle.x > gameContainer.offsetWidth || |
|
particle.y < 0 || |
|
particle.y > gameContainer.offsetHeight) { |
|
state.trailParticles.splice(i, 1); |
|
} |
|
} |
|
} |
|
|
|
|
|
function render() { |
|
playerPaddle.style.top = state.playerY + 'px'; |
|
computerPaddle.style.top = state.computerY + 'px'; |
|
|
|
state.balls.forEach((ball, index) => { |
|
if (ball.active) { |
|
balls[index].style.left = ball.x + 'px'; |
|
balls[index].style.top = ball.y + 'px'; |
|
} |
|
}); |
|
|
|
|
|
const oldTrails = document.querySelectorAll('.trail'); |
|
if (!isMobile || Date.now() % 3 === 0) { |
|
oldTrails.forEach(trail => trail.remove()); |
|
} |
|
|
|
|
|
state.trailParticles.forEach(particle => { |
|
const trail = document.createElement('div'); |
|
trail.className = 'trail'; |
|
trail.style.left = particle.x + 'px'; |
|
trail.style.top = particle.y + 'px'; |
|
trail.style.backgroundColor = particle.color; |
|
trail.style.width = particle.size + 'px'; |
|
trail.style.height = particle.size + 'px'; |
|
trail.style.opacity = particle.life / (isMobile ? 15 : 20); |
|
gameContainer.appendChild(trail); |
|
}); |
|
} |
|
|
|
|
|
function displayMessage(text, duration, callback) { |
|
message.textContent = text; |
|
message.style.opacity = 1; |
|
|
|
|
|
if (text === 'GO!') { |
|
message.style.color = 'var(--neon-green)'; |
|
message.style.textShadow = '0 0 10px var(--neon-green)'; |
|
} else if (text.includes('WINS')) { |
|
message.style.color = text.includes('PLAYER') ? 'var(--neon-blue)' : 'var(--neon-pink)'; |
|
message.style.textShadow = text.includes('PLAYER') ? '0 0 10px var(--neon-blue)' : '0 0 10px var(--neon-pink)'; |
|
} else { |
|
message.style.color = 'white'; |
|
message.style.textShadow = '0 0 5px white'; |
|
} |
|
|
|
setTimeout(() => { |
|
message.style.opacity = 0; |
|
if (callback) setTimeout(callback, 500); |
|
}, duration); |
|
} |
|
|
|
|
|
function endGame() { |
|
state.gameRunning = false; |
|
|
|
const winner = state.playerScore >= 10 ? 'PLAYER WINS!' : 'CPU WINS!'; |
|
displayMessage(winner, 1500, () => { |
|
|
|
state.playerScore = 0; |
|
state.computerScore = 0; |
|
playerScore.textContent = '0'; |
|
cpuScore.textContent = '0'; |
|
|
|
state.balls.forEach((ball, index) => { |
|
resetBall(index, index % 2 === 0 ? 'player' : 'computer'); |
|
}); |
|
|
|
displayMessage("READY", 800, () => { |
|
displayMessage("GO!", 400, () => { |
|
state.gameRunning = true; |
|
gameLoop(); |
|
}); |
|
}); |
|
}); |
|
} |
|
|
|
|
|
function addEventListeners() { |
|
|
|
document.addEventListener('keydown', (e) => { |
|
state.keys[e.key.toLowerCase()] = true; |
|
}); |
|
|
|
document.addEventListener('keyup', (e) => { |
|
state.keys[e.key.toLowerCase()] = false; |
|
}); |
|
|
|
|
|
gameContainer.addEventListener('mousemove', (e) => { |
|
const rect = gameContainer.getBoundingClientRect(); |
|
const relativeY = e.clientY - rect.top; |
|
state.playerY = Math.max( |
|
0, |
|
Math.min( |
|
gameContainer.offsetHeight - playerPaddle.offsetHeight, |
|
relativeY - playerPaddle.offsetHeight / 2 |
|
) |
|
); |
|
}); |
|
|
|
|
|
gameContainer.addEventListener('touchstart', (e) => { |
|
e.preventDefault(); |
|
const touch = e.touches[0]; |
|
const rect = gameContainer.getBoundingClientRect(); |
|
state.mobileTouch.touchStartY = touch.clientY; |
|
state.mobileTouch.paddleStartY = state.playerY; |
|
state.mobileTouch.isTouching = true; |
|
}, { passive: false }); |
|
|
|
gameContainer.addEventListener('touchmove', (e) => { |
|
if (!state.mobileTouch.isTouching) return; |
|
e.preventDefault(); |
|
const touch = e.touches[0]; |
|
const rect = gameContainer.getBoundingClientRect(); |
|
const deltaY = touch.clientY - state.mobileTouch.touchStartY; |
|
const newY = state.mobileTouch.paddleStartY + deltaY; |
|
|
|
state.playerY = Math.max( |
|
0, |
|
Math.min( |
|
gameContainer.offsetHeight - playerPaddle.offsetHeight, |
|
newY |
|
) |
|
); |
|
}, { passive: false }); |
|
|
|
gameContainer.addEventListener('touchend', () => { |
|
state.mobileTouch.isTouching = false; |
|
}, { passive: false }); |
|
|
|
|
|
upBtn.addEventListener('touchstart', () => { |
|
state.keys['up'] = true; |
|
}); |
|
|
|
upBtn.addEventListener('touchend', () => { |
|
state.keys['up'] = false; |
|
}); |
|
|
|
downBtn.addEventListener('touchstart', () => { |
|
state.keys['down'] = true; |
|
}); |
|
|
|
downBtn.addEventListener('touchend', () => { |
|
state.keys['down'] = false; |
|
}); |
|
|
|
|
|
document.addEventListener('contextmenu', (e) => { |
|
if (isMobile) e.preventDefault(); |
|
}); |
|
} |
|
|
|
|
|
init(); |
|
}); |
|
</script> |
|
</body> |
|
</html> |