|
<!DOCTYPE html> |
|
<html lang="en"> |
|
<head> |
|
<meta charset="UTF-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<title>AI PONG SHOWDOWN</title> |
|
<script src="https://cdn.tailwindcss.com"></script> |
|
<style> |
|
@import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap'); |
|
|
|
* { |
|
margin: 0; |
|
padding: 0; |
|
box-sizing: border-box; |
|
} |
|
|
|
body { |
|
background-color: #0a0a1a; |
|
font-family: 'Orbitron', sans-serif; |
|
color: #00fffc; |
|
overflow: hidden; |
|
height: 100vh; |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
justify-content: center; |
|
} |
|
|
|
#game-container { |
|
position: relative; |
|
width: 100%; |
|
max-width: 800px; |
|
height: auto; |
|
aspect-ratio: 8/5; |
|
border: 2px solid #00fffc; |
|
box-shadow: 0 0 20px #00fffc, inset 0 0 20px #00fffc; |
|
overflow: hidden; |
|
background-color: rgba(0, 0, 20, 0.7); |
|
} |
|
|
|
#game-canvas { |
|
width: 100%; |
|
height: 100%; |
|
background-color: transparent; |
|
display: block; |
|
} |
|
|
|
#score { |
|
display: flex; |
|
justify-content: space-between; |
|
width: 100%; |
|
max-width: 800px; |
|
margin-bottom: 20px; |
|
font-size: clamp(16px, 3vw, 24px); |
|
text-shadow: 0 0 10px #00fffc; |
|
} |
|
|
|
.score-display { |
|
padding: clamp(5px, 1.5vw, 10px) clamp(10px, 4vw, 30px); |
|
background-color: rgba(0, 255, 252, 0.1); |
|
border: 1px solid #00fffc; |
|
border-radius: 5px; |
|
} |
|
|
|
#settings-panel { |
|
position: absolute; |
|
top: 10px; |
|
right: 10px; |
|
z-index: 20; |
|
display: flex; |
|
flex-direction: column; |
|
gap: 5px; |
|
} |
|
|
|
#start-screen, #game-over { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
display: flex; |
|
flex-direction: column; |
|
align-items: center; |
|
justify-content: center; |
|
background-color: rgba(0, 0, 20, 0.8); |
|
z-index: 10; |
|
} |
|
|
|
h1 { |
|
font-size: clamp(24px, 6vw, 48px); |
|
margin-bottom: clamp(15px, 4vw, 30px); |
|
text-shadow: 0 0 15px #00fffc; |
|
letter-spacing: 3px; |
|
text-align: center; |
|
} |
|
|
|
button { |
|
background-color: transparent; |
|
color: #00fffc; |
|
border: 2px solid #00fffc; |
|
padding: clamp(10px, 2.5vw, 15px) clamp(15px, 4vw, 30px); |
|
font-family: 'Orbitron', sans-serif; |
|
font-size: clamp(14px, 3vw, 18px); |
|
cursor: pointer; |
|
margin: clamp(5px, 1.5vw, 10px); |
|
transition: all 0.3s; |
|
text-shadow: 0 0 5px #00fffc; |
|
box-shadow: 0 0 10px #00fffc; |
|
} |
|
|
|
button:hover { |
|
background-color: rgba(0, 255, 252, 0.2); |
|
transform: scale(1.05); |
|
} |
|
|
|
.sm-btn { |
|
padding: 5px 10px; |
|
font-size: 12px; |
|
margin: 0; |
|
} |
|
|
|
#game-over { |
|
display: none; |
|
} |
|
|
|
#grid { |
|
position: absolute; |
|
top: 0; |
|
left: 0; |
|
width: 100%; |
|
height: 100%; |
|
background: |
|
linear-gradient(rgba(0, 255, 252, 0.05) 1px, transparent 1px), |
|
linear-gradient(90deg, rgba(0, 255, 252, 0.05) 1px, transparent 1px); |
|
background-size: 20px 20px; |
|
z-index: -1; |
|
} |
|
|
|
.particle { |
|
position: absolute; |
|
width: 2px; |
|
height: 2px; |
|
background-color: #00fffc; |
|
border-radius: 50%; |
|
pointer-events: none; |
|
z-index: -1; |
|
} |
|
|
|
#instructions { |
|
margin-top: clamp(10px, 3vw, 20px); |
|
font-size: clamp(10px, 2vw, 14px); |
|
color: rgba(0, 255, 252, 0.7); |
|
text-align: center; |
|
padding: 0 10px; |
|
} |
|
|
|
.ai-selector { |
|
display: flex; |
|
flex-direction: column; |
|
gap: 10px; |
|
margin-bottom: 20px; |
|
} |
|
|
|
.ai-option { |
|
display: flex; |
|
align-items: center; |
|
gap: 10px; |
|
} |
|
|
|
.ai-option input { |
|
accent-color: #00fffc; |
|
} |
|
|
|
|
|
.switch { |
|
position: relative; |
|
display: inline-block; |
|
width: 50px; |
|
height: 24px; |
|
} |
|
|
|
.switch input { |
|
opacity: 0; |
|
width: 0; |
|
height: 0; |
|
} |
|
|
|
.slider { |
|
position: absolute; |
|
cursor: pointer; |
|
top: 0; |
|
left: 0; |
|
right: 0; |
|
bottom: 0; |
|
background-color: rgba(0, 255, 252, 0.2); |
|
transition: .4s; |
|
border: 1px solid #00fffc; |
|
} |
|
|
|
.slider:before { |
|
position: absolute; |
|
content: ""; |
|
height: 16px; |
|
width: 16px; |
|
left: 4px; |
|
bottom: 3px; |
|
background-color: #00fffc; |
|
transition: .4s; |
|
} |
|
|
|
input:checked + .slider { |
|
background-color: rgba(0, 255, 252, 0.4); |
|
} |
|
|
|
input:checked + .slider:before { |
|
transform: translateX(26px); |
|
} |
|
|
|
.slider.round { |
|
border-radius: 24px; |
|
} |
|
|
|
.slider.round:before { |
|
border-radius: 50%; |
|
} |
|
|
|
|
|
.difficulty-selector { |
|
display: flex; |
|
flex-direction: column; |
|
gap: 10px; |
|
margin-bottom: 20px; |
|
} |
|
|
|
.difficulty-option { |
|
display: flex; |
|
align-items: center; |
|
gap: 10px; |
|
} |
|
|
|
|
|
@media (max-width: 600px) { |
|
#settings-panel { |
|
top: 5px; |
|
right: 5px; |
|
} |
|
|
|
button.sm-btn { |
|
font-size: 10px; |
|
padding: 3px 6px; |
|
} |
|
} |
|
</style> |
|
</head> |
|
<body> |
|
<div id="score"> |
|
<div id="left-ai-score" class="score-display">AI-1: 0</div> |
|
<div id="right-ai-score" class="score-display">AI-2: 0</div> |
|
</div> |
|
|
|
<div id="game-container"> |
|
<div id="grid"></div> |
|
<canvas id="game-canvas"></canvas> |
|
|
|
<div id="settings-panel"> |
|
<button id="pause-btn" class="sm-btn">⏸ PAUSE</button> |
|
<button id="speed-btn" class="sm-btn">⚡ 1X</button> |
|
</div> |
|
|
|
<div id="start-screen"> |
|
<h1>AI PONG SHOWDOWN</h1> |
|
|
|
<div class="ai-selector"> |
|
<div class="ai-option"> |
|
<label class="switch"> |
|
<input type="checkbox" id="human-player-toggle"> |
|
<span class="slider round"></span> |
|
</label> |
|
<span>Human Player?</span> |
|
</div> |
|
</div> |
|
|
|
<div class="difficulty-selector"> |
|
<h3>AI DIFFICULTY:</h3> |
|
<div class="difficulty-option"> |
|
<input type="radio" id="easy" name="difficulty" value="easy" checked> |
|
<label for="easy">Easy</label> |
|
</div> |
|
<div class="difficulty-option"> |
|
<input type="radio" id="medium" name="difficulty" value="medium"> |
|
<label for="medium">Medium</label> |
|
</div> |
|
<div class="difficulty-option"> |
|
<input type="radio" id="hard" name="difficulty" value="hard"> |
|
<label for="hard">Hard</label> |
|
</div> |
|
</div> |
|
|
|
<button id="start-button">START SHOWDOWN</button> |
|
<div id="instructions"> |
|
OBSERVE THE AI BATTLE OR PLAY AGAINST THEM<br> |
|
FIRST TO SCORE 5 WINS |
|
</div> |
|
</div> |
|
|
|
<div id="game-over"> |
|
<h1 id="result-text">AI-1 VICTORY!</h1> |
|
<button id="restart-button">NEW SHOWDOWN</button> |
|
</div> |
|
</div> |
|
|
|
<audio id="paddle-hit" src="data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU..."></audio> |
|
<audio id="wall-hit" src="data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU..."></audio> |
|
<audio id="score-sound" src="data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU..."></audio> |
|
|
|
<script> |
|
|
|
const canvas = document.getElementById('game-canvas'); |
|
const ctx = canvas.getContext('2d'); |
|
const startScreen = document.getElementById('start-screen'); |
|
const gameOverScreen = document.getElementById('game-over'); |
|
const resultText = document.getElementById('result-text'); |
|
const startButton = document.getElementById('start-button'); |
|
const restartButton = document.getElementById('restart-button'); |
|
const leftScoreDisplay = document.getElementById('left-ai-score'); |
|
const rightScoreDisplay = document.getElementById('right-ai-score'); |
|
const pauseBtn = document.getElementById('pause-btn'); |
|
const speedBtn = document.getElementById('speed-btn'); |
|
const humanPlayerToggle = document.getElementById('human-player-toggle'); |
|
|
|
|
|
const paddleHitSound = document.getElementById('paddle-hit'); |
|
const wallHitSound = document.getElementById('wall-hit'); |
|
const scoreSound = document.getElementById('score-sound'); |
|
|
|
|
|
let gameRunning = false; |
|
let gamePaused = false; |
|
let leftScore = 0; |
|
let rightScore = 0; |
|
const winningScore = 5; |
|
let gameSpeed = 1; |
|
let humanPlayer = false; |
|
let aiStrength = 'medium'; |
|
|
|
|
|
function resizeCanvas() { |
|
const container = document.getElementById('game-container'); |
|
canvas.width = container.clientWidth; |
|
canvas.height = container.clientHeight; |
|
} |
|
|
|
window.addEventListener('resize', () => { |
|
resizeCanvas(); |
|
resetPositions(); |
|
}); |
|
|
|
|
|
resizeCanvas(); |
|
|
|
|
|
let paddleWidth, paddleHeight, paddleSpeed; |
|
|
|
function calculateDimensions() { |
|
paddleWidth = Math.max(10, canvas.width * 0.015); |
|
paddleHeight = Math.max(60, canvas.height * 0.2); |
|
paddleSpeed = canvas.height * 0.012; |
|
} |
|
|
|
const leftPaddle = { |
|
x: 30, |
|
y: 0, |
|
width: 0, |
|
height: 0, |
|
dy: 0 |
|
}; |
|
|
|
const rightPaddle = { |
|
x: 0, |
|
y: 0, |
|
width: 0, |
|
height: 0, |
|
dy: 0 |
|
}; |
|
|
|
|
|
let ballSize; |
|
let ball = { |
|
x: 0, |
|
y: 0, |
|
width: 0, |
|
height: 0, |
|
dx: 0, |
|
dy: 0 |
|
}; |
|
|
|
function resetPositions() { |
|
calculateDimensions(); |
|
|
|
|
|
leftPaddle.width = paddleWidth; |
|
leftPaddle.height = paddleHeight; |
|
leftPaddle.y = canvas.height / 2 - paddleHeight / 2; |
|
leftPaddle.x = canvas.width * 0.03; |
|
|
|
rightPaddle.width = paddleWidth; |
|
rightPaddle.height = paddleHeight; |
|
rightPaddle.y = canvas.height / 2 - paddleHeight / 2; |
|
rightPaddle.x = canvas.width - (canvas.width * 0.03) - paddleWidth; |
|
|
|
|
|
ballSize = Math.max(8, Math.min(canvas.width * 0.015, canvas.height * 0.02)); |
|
ball.width = ballSize; |
|
ball.height = ballSize; |
|
|
|
resetBall(); |
|
} |
|
|
|
|
|
const aiProfiles = { |
|
easy: { |
|
reactionDelay: 0.5, |
|
accuracy: 0.7, |
|
prediction: 0.4, |
|
speedMultiplier: 0.7 |
|
}, |
|
medium: { |
|
reactionDelay: 0.3, |
|
accuracy: 0.85, |
|
prediction: 0.6, |
|
speedMultiplier: 0.85 |
|
}, |
|
hard: { |
|
reactionDelay: 0.1, |
|
accuracy: 0.95, |
|
prediction: 0.8, |
|
speedMultiplier: 1.0 |
|
} |
|
}; |
|
|
|
|
|
const trail = []; |
|
const maxTrailLength = 10; |
|
|
|
|
|
function createParticles() { |
|
const particles = []; |
|
const particleCount = Math.floor(canvas.width * canvas.height / 2000); |
|
|
|
for (let i = 0; i < particleCount; i++) { |
|
particles.push({ |
|
x: Math.random() * canvas.width, |
|
y: Math.random() * canvas.height, |
|
size: Math.random() * 2 + 1, |
|
speed: Math.random() * 2 + 1 |
|
}); |
|
} |
|
|
|
return particles; |
|
} |
|
|
|
let particles = createParticles(); |
|
|
|
|
|
function drawPaddle(x, y, width, height, color = '#00fffc') { |
|
ctx.fillStyle = color; |
|
ctx.shadowBlur = 15; |
|
ctx.shadowColor = color; |
|
ctx.fillRect(x, y, width, height); |
|
ctx.shadowBlur = 0; |
|
} |
|
|
|
function drawBall(x, y, width, height, color = '#ff00f7') { |
|
ctx.fillStyle = color; |
|
ctx.shadowBlur = 15; |
|
ctx.shadowColor = color; |
|
ctx.beginPath(); |
|
ctx.arc(x + width / 2, y + height / 2, width / 2, 0, Math.PI * 2); |
|
ctx.fill(); |
|
ctx.shadowBlur = 0; |
|
} |
|
|
|
function drawTrail() { |
|
for (let i = 0; i < trail.length; i++) { |
|
const opacity = i / trail.length; |
|
ctx.fillStyle = `rgba(255, 0, 247, ${opacity})`; |
|
ctx.beginPath(); |
|
ctx.arc(trail[i].x, trail[i].y, ballSize / 2 * opacity, 0, Math.PI * 2); |
|
ctx.fill(); |
|
} |
|
} |
|
|
|
function drawParticles() { |
|
particles.forEach(particle => { |
|
ctx.fillStyle = `rgba(0, 255, 252, ${Math.random() * 0.5})`; |
|
ctx.fillRect(particle.x, particle.y, particle.size, particle.size); |
|
|
|
|
|
particle.y += particle.speed; |
|
if (particle.y > canvas.height) { |
|
particle.y = 0; |
|
particle.x = Math.random() * canvas.width; |
|
} |
|
}); |
|
} |
|
|
|
function drawCenterLine() { |
|
ctx.strokeStyle = 'rgba(0, 255, 252, 0.2)'; |
|
ctx.lineWidth = 2; |
|
ctx.setLineDash([10, 10]); |
|
ctx.beginPath(); |
|
ctx.moveTo(canvas.width / 2, 0); |
|
ctx.lineTo(canvas.width / 2, canvas.height); |
|
ctx.stroke(); |
|
ctx.setLineDash([]); |
|
} |
|
|
|
|
|
function decideAIMovement(paddle, ball, isLeftPaddle, profile) { |
|
const paddleCenter = paddle.y + paddle.height / 2; |
|
const ballCenter = ball.y + ball.height / 2; |
|
|
|
|
|
let predictedY = ballCenter; |
|
if (profile.prediction > 0) { |
|
if (ball.dx !== 0) { |
|
const framesToContact = Math.abs((paddle.x - ball.x) / ball.dx); |
|
predictedY = ballCenter + (ball.dy * framesToContact * profile.prediction); |
|
} |
|
} |
|
|
|
|
|
predictedY = Math.max(0, Math.min(canvas.height, predictedY)); |
|
|
|
|
|
const targetY = predictedY + (Math.random() - 0.5) * paddle.height * (1 - profile.accuracy); |
|
|
|
|
|
if (paddleCenter < targetY - 10) { |
|
return paddleSpeed * profile.speedMultiplier; |
|
} else if (paddleCenter > targetY + 10) { |
|
return -paddleSpeed * profile.speedMultiplier; |
|
} else { |
|
return 0; |
|
} |
|
} |
|
|
|
|
|
function update() { |
|
if (gamePaused) return; |
|
|
|
|
|
const difficulty = document.querySelector('input[name="difficulty"]:checked').value; |
|
const profile = aiProfiles[difficulty]; |
|
|
|
|
|
if (!humanPlayer || !gameRunning) { |
|
leftPaddle.dy = decideAIMovement(leftPaddle, ball, true, profile); |
|
} |
|
|
|
|
|
rightPaddle.dy = decideAIMovement(rightPaddle, ball, false, profile); |
|
|
|
|
|
leftPaddle.y += leftPaddle.dy * gameSpeed; |
|
rightPaddle.y += rightPaddle.dy * gameSpeed; |
|
|
|
|
|
if (leftPaddle.y < 0) leftPaddle.y = 0; |
|
if (leftPaddle.y + leftPaddle.height > canvas.height) { |
|
leftPaddle.y = canvas.height - leftPaddle.height; |
|
} |
|
|
|
if (rightPaddle.y < 0) rightPaddle.y = 0; |
|
if (rightPaddle.y + rightPaddle.height > canvas.height) { |
|
rightPaddle.y = canvas.height - rightPaddle.height; |
|
} |
|
|
|
|
|
ball.x += ball.dx * gameSpeed; |
|
ball.y += ball.dy * gameSpeed; |
|
|
|
|
|
trail.push({ x: ball.x + ball.width / 2, y: ball.y + ball.height / 2 }); |
|
if (trail.length > maxTrailLength) { |
|
trail.shift(); |
|
} |
|
|
|
|
|
if (ball.y < 0 || ball.y + ball.height > canvas.height) { |
|
ball.dy = -ball.dy; |
|
wallHitSound.currentTime = 0; |
|
wallHitSound.play(); |
|
} |
|
|
|
|
|
if ( |
|
ball.x < leftPaddle.x + leftPaddle.width && |
|
ball.x + ball.width > leftPaddle.x && |
|
ball.y < leftPaddle.y + leftPaddle.height && |
|
ball.y + ball.height > leftPaddle.y |
|
) { |
|
|
|
const hitPosition = (ball.y + ball.height / 2) - (leftPaddle.y + leftPaddle.height / 2); |
|
const normalizedHit = hitPosition / (leftPaddle.height / 2); |
|
ball.dy = normalizedHit * 5 * (humanPlayer ? 1.2 : 1); |
|
|
|
ball.dx = Math.abs(ball.dx); |
|
ball.dx *= humanPlayer ? 1.1 : 1.05; |
|
|
|
paddleHitSound.currentTime = 0; |
|
paddleHitSound.play(); |
|
|
|
|
|
ball.dy += (Math.random() * 2 - 1) * 0.5; |
|
} |
|
|
|
if ( |
|
ball.x < rightPaddle.x + rightPaddle.width && |
|
ball.x + ball.width > rightPaddle.x && |
|
ball.y < rightPaddle.y + rightPaddle.height && |
|
ball.y + ball.height > rightPaddle.y |
|
) { |
|
|
|
const hitPosition = (ball.y + ball.height / 2) - (rightPaddle.y + rightPaddle.height / 2); |
|
const normalizedHit = hitPosition / (rightPaddle.height / 2); |
|
ball.dy = normalizedHit * 5; |
|
|
|
ball.dx = -Math.abs(ball.dx); |
|
ball.dx *= 1.05; |
|
|
|
paddleHitSound.currentTime = 0; |
|
paddleHitSound.play(); |
|
|
|
|
|
ball.dy += (Math.random() * 2 - 1) * 0.5; |
|
} |
|
|
|
|
|
if (ball.x + ball.width < 0) { |
|
|
|
rightScore++; |
|
rightScoreDisplay.textContent = `AI-2: ${rightScore}`; |
|
resetBall(); |
|
scoreSound.currentTime = 0; |
|
scoreSound.play(); |
|
|
|
if (rightScore >= winningScore) { |
|
endGame(false); |
|
} |
|
} |
|
|
|
if (ball.x > canvas.width) { |
|
|
|
leftScore++; |
|
leftScoreDisplay.textContent = `AI-1: ${leftScore}`; |
|
resetBall(); |
|
scoreSound.currentTime = 0; |
|
scoreSound.play(); |
|
|
|
if (leftScore >= winningScore) { |
|
endGame(true); |
|
} |
|
} |
|
} |
|
|
|
function resetBall() { |
|
ball.x = canvas.width / 2 - ball.width / 2; |
|
ball.y = canvas.height / 2 - ball.height / 2; |
|
|
|
|
|
if (humanPlayer && leftScore !== rightScore) { |
|
const direction = leftScore < rightScore ? 1 : -1; |
|
ball.dx = direction * 5; |
|
} else { |
|
|
|
ball.dx = (Math.random() > 0.5 ? 1 : -1) * 5; |
|
} |
|
|
|
ball.dy = (Math.random() * 4 - 2); |
|
|
|
|
|
trail.length = 0; |
|
} |
|
|
|
function render() { |
|
|
|
ctx.clearRect(0, 0, canvas.width, canvas.height); |
|
|
|
|
|
drawParticles(); |
|
drawCenterLine(); |
|
|
|
|
|
drawTrail(); |
|
drawPaddle( |
|
leftPaddle.x, |
|
leftPaddle.y, |
|
leftPaddle.width, |
|
leftPaddle.height, |
|
humanPlayer ? '#00ff9d' : '#00fffc' |
|
); |
|
drawPaddle( |
|
rightPaddle.x, |
|
rightPaddle.y, |
|
rightPaddle.width, |
|
rightPaddle.height, |
|
'#ff00f7' |
|
); |
|
drawBall(ball.x, ball.y, ball.width, ball.height); |
|
} |
|
|
|
function gameLoop() { |
|
if (gameRunning && !gamePaused) { |
|
update(); |
|
render(); |
|
requestAnimationFrame(gameLoop); |
|
} |
|
} |
|
|
|
|
|
function startGame() { |
|
humanPlayer = humanPlayerToggle.checked; |
|
leftScore = 0; |
|
rightScore = 0; |
|
leftScoreDisplay.textContent = `AI-1: ${leftScore}`; |
|
rightScoreDisplay.textContent = humanPlayer ? `YOU: ${rightScore}` : `AI-2: ${rightScore}`; |
|
|
|
resetPositions(); |
|
startScreen.style.display = 'none'; |
|
gameOverScreen.style.display = 'none'; |
|
gameRunning = true; |
|
gamePaused = false; |
|
gameLoop(); |
|
} |
|
|
|
function endGame(leftWon) { |
|
gameRunning = false; |
|
gameOverScreen.style.display = 'flex'; |
|
|
|
if (humanPlayer) { |
|
resultText.textContent = leftWon ? "AI DEFEATED YOU!" : "YOU DEFEATED AI!"; |
|
} else { |
|
resultText.textContent = leftWon ? "AI-1 IS VICTORIOUS!" : "AI-2 IS VICTORIOUS!"; |
|
} |
|
} |
|
|
|
function togglePause() { |
|
gamePaused = !gamePaused; |
|
pauseBtn.textContent = gamePaused ? "▶ RESUME" : "⏸ PAUSE"; |
|
if (gameRunning && !gamePaused) { |
|
gameLoop(); |
|
} |
|
} |
|
|
|
function cycleGameSpeed() { |
|
const speeds = [1, 1.5, 2, 3]; |
|
const currentIndex = speeds.indexOf(gameSpeed); |
|
const nextIndex = (currentIndex + 1) % speeds.length; |
|
gameSpeed = speeds[nextIndex]; |
|
speedBtn.textContent = `⚡ ${gameSpeed}X`; |
|
} |
|
|
|
|
|
startButton.addEventListener('click', startGame); |
|
restartButton.addEventListener('click', startGame); |
|
pauseBtn.addEventListener('click', togglePause); |
|
speedBtn.addEventListener('click', cycleGameSpeed); |
|
|
|
humanPlayerToggle.addEventListener('change', function() { |
|
rightScoreDisplay.textContent = this.checked ? `YOU: ${rightScore}` : `AI-2: ${rightScore}`; |
|
}); |
|
|
|
|
|
document.addEventListener('keydown', (e) => { |
|
if (!humanPlayer || !gameRunning) return; |
|
|
|
if (e.key === 'ArrowUp') { |
|
leftPaddle.dy = -paddleSpeed * gameSpeed; |
|
} else if (e.key === 'ArrowDown') { |
|
leftPaddle.dy = paddleSpeed * gameSpeed; |
|
} |
|
}); |
|
|
|
document.addEventListener('keyup', (e) => { |
|
if (!humanPlayer || !gameRunning) return; |
|
|
|
if (e.key === 'ArrowUp' || e.key === 'ArrowDown') { |
|
leftPaddle.dy = 0; |
|
} |
|
}); |
|
|
|
|
|
canvas.addEventListener('mousemove', (e) => { |
|
if (!humanPlayer || !gameRunning) return; |
|
|
|
const rect = canvas.getBoundingClientRect(); |
|
const mouseY = e.clientY - rect.top - leftPaddle.height / 2; |
|
|
|
if (mouseY >= 0 && mouseY <= canvas.height - leftPaddle.height) { |
|
leftPaddle.y = mouseY; |
|
} |
|
}); |
|
|
|
|
|
resetPositions(); |
|
particles = createParticles(); |
|
</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 <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=Ravisil/pong-ai" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body> |
|
</html> |