Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Human-Like AI Pong</title> | |
<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; | |
overflow: hidden; | |
font-family: 'Orbitron', sans-serif; | |
color: #00fffc; | |
height: 100vh; | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: center; | |
} | |
#game-container { | |
position: relative; | |
width: 800px; | |
height: 500px; | |
border: 3px solid #00fffc; | |
box-shadow: 0 0 20px #00fffc, inset 0 0 20px #00fffc; | |
border-radius: 5px; | |
overflow: hidden; | |
} | |
#game-board { | |
width: 100%; | |
height: 100%; | |
background-color: rgba(0, 10, 20, 0.7); | |
position: relative; | |
} | |
.paddle { | |
position: absolute; | |
width: 15px; | |
height: 100px; | |
background: linear-gradient(to bottom, #00fffc, #0066ff); | |
border-radius: 5px; | |
box-shadow: 0 0 10px #00fffc; | |
transition: top 0.1s ease-out; | |
} | |
#left-paddle { | |
left: 20px; | |
top: 200px; | |
} | |
#right-paddle { | |
right: 20px; | |
top: 200px; | |
} | |
#ball { | |
position: absolute; | |
width: 20px; | |
height: 20px; | |
background: radial-gradient(circle, #ff00ff, #ff0066); | |
border-radius: 50%; | |
box-shadow: 0 0 15px #ff00ff; | |
left: 390px; | |
top: 240px; | |
} | |
.score { | |
position: absolute; | |
font-size: 3rem; | |
top: 20px; | |
} | |
#left-score { | |
left: 100px; | |
} | |
#right-score { | |
right: 100px; | |
} | |
.center-line { | |
position: absolute; | |
width: 2px; | |
height: 100%; | |
background: linear-gradient(to bottom, | |
transparent 0%, | |
#00fffc 10%, | |
transparent 20%, | |
transparent 80%, | |
#00fffc 90%, | |
transparent 100%); | |
left: 50%; | |
transform: translateX(-50%); | |
} | |
.explosion { | |
position: absolute; | |
width: 40px; | |
height: 40px; | |
border-radius: 50%; | |
background: radial-gradient(circle, #ff6600, #ff0066); | |
transform: scale(0); | |
opacity: 0.8; | |
pointer-events: none; | |
} | |
#start-screen { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(0, 5, 15, 0.9); | |
display: flex; | |
flex-direction: column; | |
align-items: center; | |
justify-content: center; | |
z-index: 10; | |
} | |
#start-screen h1 { | |
font-size: 3rem; | |
margin-bottom: 30px; | |
text-shadow: 0 0 10px #00fffc; | |
animation: pulse 2s infinite; | |
} | |
#start-screen p { | |
margin-bottom: 30px; | |
text-align: center; | |
max-width: 80%; | |
} | |
#start-button { | |
background: linear-gradient(to right, #0066ff, #00fffc); | |
border: none; | |
color: #0a0a1a; | |
padding: 15px 30px; | |
font-size: 1.2rem; | |
font-family: 'Orbitron', sans-serif; | |
border-radius: 5px; | |
cursor: pointer; | |
box-shadow: 0 0 15px #00fffc; | |
transition: all 0.3s; | |
} | |
#start-button:hover { | |
transform: scale(1.05); | |
box-shadow: 0 0 20px #00fffc; | |
} | |
#game-over { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
background-color: rgba(0, 5, 15, 0.9); | |
display: none; | |
flex-direction: column; | |
align-items: center; | |
justify-content: center; | |
z-index: 10; | |
} | |
#game-over h1 { | |
font-size: 3rem; | |
margin-bottom: 20px; | |
color: #ff0066; | |
text-shadow: 0 0 10px #ff0066; | |
} | |
#final-score { | |
font-size: 2rem; | |
margin-bottom: 30px; | |
} | |
#restart-button { | |
background: linear-gradient(to right, #ff0066, #ff6600); | |
border: none; | |
color: #0a0a1a; | |
padding: 15px 30px; | |
font-size: 1.2rem; | |
font-family: 'Orbitron', sans-serif; | |
border-radius: 5px; | |
cursor: pointer; | |
box-shadow: 0 0 15px #ff0066; | |
transition: all 0.3s; | |
} | |
#restart-button:hover { | |
transform: scale(1.05); | |
box-shadow: 0 0 20px #ff0066; | |
} | |
.particle { | |
position: absolute; | |
width: 4px; | |
height: 4px; | |
border-radius: 50%; | |
background-color: #00fffc; | |
pointer-events: none; | |
} | |
@keyframes pulse { | |
0% { transform: scale(1); } | |
50% { transform: scale(1.05); } | |
100% { transform: scale(1); } | |
} | |
.cyber-grid { | |
position: absolute; | |
width: 100%; | |
height: 100%; | |
background-image: | |
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; | |
pointer-events: none; | |
} | |
#instructions { | |
position: absolute; | |
bottom: 10px; | |
font-size: 0.8rem; | |
color: #00fffc; | |
text-align: center; | |
width: 100%; | |
} | |
#game-mode { | |
margin-top: 20px; | |
display: flex; | |
gap: 15px; | |
} | |
.mode-button { | |
background: rgba(0, 255, 252, 0.2); | |
border: 1px solid #00fffc; | |
color: #00fffc; | |
padding: 10px 20px; | |
font-family: 'Orbitron', sans-serif; | |
border-radius: 5px; | |
cursor: pointer; | |
transition: all 0.3s; | |
} | |
.mode-button:hover { | |
background: rgba(0, 255, 252, 0.4); | |
} | |
.mode-button.active { | |
background: linear-gradient(to right, #0066ff, #00fffc); | |
color: #0a0a1a; | |
box-shadow: 0 0 10px #00fffc; | |
} | |
#difficulty { | |
margin-top: 15px; | |
display: flex; | |
gap: 10px; | |
} | |
.difficulty-button { | |
background: rgba(0, 255, 252, 0.1); | |
border: 1px solid #00fffc; | |
color: #00fffc; | |
padding: 8px 15px; | |
font-family: 'Orbitron', sans-serif; | |
font-size: 0.8rem; | |
border-radius: 5px; | |
cursor: pointer; | |
transition: all 0.3s; | |
} | |
.difficulty-button:hover { | |
background: rgba(0, 255, 252, 0.3); | |
} | |
.difficulty-button.active { | |
background: rgba(0, 255, 252, 0.5); | |
box-shadow: 0 0 5px #00fffc; | |
} | |
.reaction-delay { | |
position: absolute; | |
width: 100px; | |
height: 10px; | |
background: rgba(255, 255, 255, 0.2); | |
border-radius: 5px; | |
top: 10px; | |
left: 50%; | |
transform: translateX(-50%); | |
overflow: hidden; | |
display: none; | |
} | |
.reaction-progress { | |
height: 100%; | |
width: 0%; | |
background: linear-gradient(to right, #ff0066, #ff6600); | |
transition: width 0.1s linear; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="game-container"> | |
<div class="cyber-grid"></div> | |
<div id="game-board"> | |
<div class="center-line"></div> | |
<div id="left-paddle" class="paddle"></div> | |
<div id="right-paddle" class="paddle"></div> | |
<div id="ball"></div> | |
<div id="left-score" class="score">0</div> | |
<div id="right-score" class="score">0</div> | |
<div class="reaction-delay"> | |
<div class="reaction-progress"></div> | |
</div> | |
<div id="start-screen"> | |
<h1>HUMAN-LIKE PONG</h1> | |
<p>Experience a more realistic opponent with human-like reactions and imperfections.<br> | |
The AI will make mistakes, hesitate, and even show emotional responses!</p> | |
<button id="start-button">START MATCH</button> | |
<div id="game-mode"> | |
<button class="mode-button active" data-mode="single">SINGLE PLAYER</button> | |
<button class="mode-button" data-mode="multi">TWO PLAYERS</button> | |
</div> | |
<div id="difficulty"> | |
<button class="difficulty-button active" data-difficulty="medium">MEDIUM</button> | |
<button class="difficulty-button" data-difficulty="easy">EASY</button> | |
<button class="difficulty-button" data-difficulty="hard">HARD</button> | |
</div> | |
<div id="instructions"> | |
Controls: W/S keys for Player 1 | Up/Down arrows for Player 2 | |
</div> | |
</div> | |
<div id="game-over"> | |
<h1>GAME OVER</h1> | |
<div id="final-score"></div> | |
<button id="restart-button">REMATCH</button> | |
</div> | |
</div> | |
</div> | |
<script> | |
document.addEventListener('DOMContentLoaded', () => { | |
// Game elements | |
const gameBoard = document.getElementById('game-board'); | |
const leftPaddle = document.getElementById('left-paddle'); | |
const rightPaddle = document.getElementById('right-paddle'); | |
const ball = document.getElementById('ball'); | |
const leftScore = document.getElementById('left-score'); | |
const rightScore = document.getElementById('right-score'); | |
const startScreen = document.getElementById('start-screen'); | |
const gameOverScreen = document.getElementById('game-over'); | |
const finalScore = document.getElementById('final-score'); | |
const startButton = document.getElementById('start-button'); | |
const restartButton = document.getElementById('restart-button'); | |
const modeButtons = document.querySelectorAll('.mode-button'); | |
const difficultyButtons = document.querySelectorAll('.difficulty-button'); | |
const reactionDelay = document.querySelector('.reaction-delay'); | |
const reactionProgress = document.querySelector('.reaction-progress'); | |
// Game variables | |
let gameRunning = false; | |
let leftScoreValue = 0; | |
let rightScoreValue = 0; | |
const winningScore = 5; | |
let gameMode = 'single'; // 'single' or 'multi' | |
let difficulty = 'medium'; // 'easy', 'medium', 'hard' | |
// Ball variables | |
let ballX = 390; | |
let ballY = 240; | |
let ballSpeedX = 4; | |
let ballSpeedY = 4; | |
// Paddle variables | |
const paddleHeight = 100; | |
const paddleWidth = 15; | |
let leftPaddleY = 200; | |
let rightPaddleY = 200; | |
const paddleSpeed = 8; | |
// AI personality variables | |
let aiReactionTime = 300; // ms | |
let aiAccuracy = 0.8; // 0-1 | |
let aiMood = 'neutral'; // 'happy', 'neutral', 'angry' | |
let aiMistakeChance = 0.2; // 0-1 | |
let aiOverShootChance = 0.3; // 0-1 | |
let aiDecisionDelay = 0; | |
let aiLastDecisionTime = 0; | |
let aiTargetY = 200; | |
let aiIsMoving = false; | |
// Key states | |
const keys = { | |
w: false, | |
s: false, | |
ArrowUp: false, | |
ArrowDown: false | |
}; | |
// Event listeners | |
startButton.addEventListener('click', startGame); | |
restartButton.addEventListener('click', restartGame); | |
modeButtons.forEach(button => { | |
button.addEventListener('click', () => { | |
modeButtons.forEach(btn => btn.classList.remove('active')); | |
button.classList.add('active'); | |
gameMode = button.dataset.mode; | |
}); | |
}); | |
difficultyButtons.forEach(button => { | |
button.addEventListener('click', () => { | |
difficultyButtons.forEach(btn => btn.classList.remove('active')); | |
button.classList.add('active'); | |
difficulty = button.dataset.difficulty; | |
setDifficultyParameters(); | |
}); | |
}); | |
document.addEventListener('keydown', (e) => { | |
if (e.key in keys) { | |
keys[e.key] = true; | |
} | |
}); | |
document.addEventListener('keyup', (e) => { | |
if (e.key in keys) { | |
keys[e.key] = false; | |
} | |
}); | |
// Set AI parameters based on difficulty | |
function setDifficultyParameters() { | |
switch(difficulty) { | |
case 'easy': | |
aiReactionTime = 400; | |
aiAccuracy = 0.6; | |
aiMistakeChance = 0.4; | |
aiOverShootChance = 0.5; | |
break; | |
case 'medium': | |
aiReactionTime = 300; | |
aiAccuracy = 0.8; | |
aiMistakeChance = 0.2; | |
aiOverShootChance = 0.3; | |
break; | |
case 'hard': | |
aiReactionTime = 200; | |
aiAccuracy = 0.95; | |
aiMistakeChance = 0.1; | |
aiOverShootChance = 0.1; | |
break; | |
} | |
} | |
// Start game | |
function startGame() { | |
startScreen.style.display = 'none'; | |
gameRunning = true; | |
resetBall(); | |
gameLoop(); | |
} | |
// Restart game | |
function restartGame() { | |
gameOverScreen.style.display = 'none'; | |
leftScoreValue = 0; | |
rightScoreValue = 0; | |
leftScore.textContent = '0'; | |
rightScore.textContent = '0'; | |
resetBall(); | |
gameRunning = true; | |
gameLoop(); | |
} | |
// Game loop | |
function gameLoop() { | |
if (!gameRunning) return; | |
update(); | |
render(); | |
requestAnimationFrame(gameLoop); | |
} | |
// Update game state | |
function update() { | |
// Move player paddle | |
if (keys.w && leftPaddleY > 0) { | |
leftPaddleY -= paddleSpeed; | |
} | |
if (keys.s && leftPaddleY < gameBoard.clientHeight - paddleHeight) { | |
leftPaddleY += paddleSpeed; | |
} | |
// Move second player paddle in multiplayer mode | |
if (gameMode === 'multi') { | |
if (keys.ArrowUp && rightPaddleY > 0) { | |
rightPaddleY -= paddleSpeed; | |
} | |
if (keys.ArrowDown && rightPaddleY < gameBoard.clientHeight - paddleHeight) { | |
rightPaddleY += paddleSpeed; | |
} | |
} else { | |
// Single player mode - AI controls right paddle | |
moveHumanLikeAiPaddle(); | |
} | |
// Update paddle positions | |
leftPaddle.style.top = leftPaddleY + 'px'; | |
rightPaddle.style.top = rightPaddleY + 'px'; | |
// Move ball | |
ballX += ballSpeedX; | |
ballY += ballSpeedY; | |
// Ball collision with top and bottom | |
if (ballY <= 0 || ballY >= gameBoard.clientHeight - 20) { | |
ballSpeedY = -ballSpeedY; | |
createParticles(ballX, ballY, 10); | |
} | |
// Ball collision with paddles | |
if ( | |
ballX <= 35 && | |
ballX >= 20 && | |
ballY + 20 >= leftPaddleY && | |
ballY <= leftPaddleY + paddleHeight | |
) { | |
ballSpeedX = -ballSpeedX * 1.05; | |
ballSpeedY = (ballY - (leftPaddleY + paddleHeight / 2)) * 0.2; | |
createExplosion(ballX, ballY); | |
createParticles(ballX, ballY, 20); | |
} | |
if ( | |
ballX >= gameBoard.clientWidth - 35 && | |
ballX <= gameBoard.clientWidth - 20 && | |
ballY + 20 >= rightPaddleY && | |
ballY <= rightPaddleY + paddleHeight | |
) { | |
ballSpeedX = -ballSpeedX * 1.05; | |
ballSpeedY = (ballY - (rightPaddleY + paddleHeight / 2)) * 0.2; | |
createExplosion(ballX, ballY); | |
createParticles(ballX, ballY, 20); | |
} | |
// Ball out of bounds (score) | |
if (ballX < 0) { | |
rightScoreValue++; | |
rightScore.textContent = rightScoreValue; | |
updateAiMood(); | |
checkGameOver(); | |
resetBall(); | |
} | |
if (ballX > gameBoard.clientWidth) { | |
leftScoreValue++; | |
leftScore.textContent = leftScoreValue; | |
updateAiMood(); | |
checkGameOver(); | |
resetBall(); | |
} | |
} | |
// Human-like AI paddle movement | |
function moveHumanLikeAiPaddle() { | |
const now = Date.now(); | |
// Only make decisions when ball is coming towards AI | |
if (ballSpeedX > 0) { | |
// Calculate predicted ball position | |
const timeToReach = (gameBoard.clientWidth - ballX) / ballSpeedX; | |
const predictedY = ballY + (ballSpeedY * timeToReach); | |
// Add some prediction error based on accuracy | |
const predictionError = (1 - aiAccuracy) * 100; | |
const errorY = (Math.random() * predictionError * 2) - predictionError; | |
aiTargetY = Math.max(0, Math.min(gameBoard.clientHeight - paddleHeight, predictedY + errorY)); | |
// Sometimes overshoot the target | |
if (Math.random() < aiOverShootChance) { | |
aiTargetY += (Math.random() > 0.5 ? 1 : -1) * paddleHeight * 0.3; | |
} | |
// Sometimes make a mistake and move the wrong way | |
if (Math.random() < aiMistakeChance) { | |
aiTargetY = (Math.random() * (gameBoard.clientHeight - paddleHeight)); | |
} | |
// Set a random delay before reacting (human reaction time) | |
if (!aiIsMoving) { | |
aiDecisionDelay = Math.max(100, aiReactionTime * (0.5 + Math.random())); | |
aiLastDecisionTime = now; | |
aiIsMoving = true; | |
// Show reaction delay visualization | |
reactionDelay.style.display = 'block'; | |
reactionProgress.style.width = '0%'; | |
const reactionInterval = setInterval(() => { | |
const elapsed = Date.now() - aiLastDecisionTime; | |
const progress = Math.min(100, (elapsed / aiDecisionDelay) * 100); | |
reactionProgress.style.width = `${progress}%`; | |
if (progress >= 100) { | |
clearInterval(reactionInterval); | |
reactionDelay.style.display = 'none'; | |
} | |
}, 16); | |
} | |
} | |
// Move towards target after delay | |
if (aiIsMoving && now - aiLastDecisionTime > aiDecisionDelay) { | |
const paddleCenter = rightPaddleY + paddleHeight / 2; | |
const distanceToTarget = aiTargetY + paddleHeight / 2 - paddleCenter; | |
// Add some human-like variability to speed | |
const currentSpeed = paddleSpeed * (0.8 + Math.random() * 0.4); | |
if (Math.abs(distanceToTarget) > 5) { | |
if (distanceToTarget > 0 && rightPaddleY < gameBoard.clientHeight - paddleHeight) { | |
rightPaddleY += currentSpeed; | |
} else if (distanceToTarget < 0 && rightPaddleY > 0) { | |
rightPaddleY -= currentSpeed; | |
} | |
} else { | |
aiIsMoving = false; | |
// Sometimes pause before next movement | |
if (Math.random() < 0.3) { | |
aiDecisionDelay = Math.max(100, aiReactionTime * (0.5 + Math.random())); | |
aiLastDecisionTime = now; | |
} | |
} | |
} | |
} | |
// Update AI mood based on score | |
function updateAiMood() { | |
const scoreDiff = rightScoreValue - leftScoreValue; | |
if (scoreDiff >= 2) { | |
aiMood = 'happy'; | |
// When happy, AI might get overconfident and make more mistakes | |
aiMistakeChance = 0.3; | |
aiOverShootChance = 0.4; | |
} else if (scoreDiff <= -2) { | |
aiMood = 'angry'; | |
// When angry, AI might try too hard and overshoot more | |
aiMistakeChance = 0.1; | |
aiOverShootChance = 0.5; | |
} else { | |
aiMood = 'neutral'; | |
aiMistakeChance = 0.2; | |
aiOverShootChance = 0.3; | |
} | |
// Adjust based on difficulty | |
setDifficultyParameters(); | |
} | |
// Render game | |
function render() { | |
ball.style.left = ballX + 'px'; | |
ball.style.top = ballY + 'px'; | |
} | |
// Reset ball position | |
function resetBall() { | |
ballX = gameBoard.clientWidth / 2 - 10; | |
ballY = gameBoard.clientHeight / 2 - 10; | |
// Random direction | |
ballSpeedX = (Math.random() > 0.5 ? 1 : -1) * 4; | |
ballSpeedY = (Math.random() * 4 - 2); | |
// Reset AI movement state | |
aiIsMoving = false; | |
reactionDelay.style.display = 'none'; | |
// Small delay before ball starts moving | |
setTimeout(() => { | |
if (gameRunning) { | |
ball.style.display = 'block'; | |
} | |
}, 500); | |
} | |
// Check if game is over | |
function checkGameOver() { | |
if (leftScoreValue >= winningScore || rightScoreValue >= winningScore) { | |
gameRunning = false; | |
const winner = leftScoreValue >= winningScore ? 'Player 1' : (gameMode === 'multi' ? 'Player 2' : 'AI'); | |
finalScore.textContent = `${winner} wins! (${leftScoreValue}-${rightScoreValue})`; | |
gameOverScreen.style.display = 'flex'; | |
// Create celebration explosions | |
for (let i = 0; i < 10; i++) { | |
setTimeout(() => { | |
const x = Math.random() * gameBoard.clientWidth; | |
const y = Math.random() * gameBoard.clientHeight; | |
createExplosion(x, y); | |
}, i * 200); | |
} | |
} | |
} | |
// Create explosion effect | |
function createExplosion(x, y) { | |
const explosion = document.createElement('div'); | |
explosion.className = 'explosion'; | |
explosion.style.left = x + 'px'; | |
explosion.style.top = y + 'px'; | |
gameBoard.appendChild(explosion); | |
// Animate explosion | |
const animation = explosion.animate([ | |
{ transform: 'scale(0)', opacity: 0.8 }, | |
{ transform: 'scale(3)', opacity: 0 } | |
], { | |
duration: 500, | |
easing: 'cubic-bezier(0.1, 0.8, 0.2, 1)' | |
}); | |
animation.onfinish = () => { | |
gameBoard.removeChild(explosion); | |
}; | |
} | |
// Create particle effect | |
function createParticles(x, y, count) { | |
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'; | |
// Random color | |
const colors = ['#00fffc', '#ff00ff', '#ff6600', '#0066ff']; | |
const randomColor = colors[Math.floor(Math.random() * colors.length)]; | |
particle.style.backgroundColor = randomColor; | |
gameBoard.appendChild(particle); | |
// Random direction and speed | |
const angle = Math.random() * Math.PI * 2; | |
const speed = Math.random() * 5 + 2; | |
const animation = particle.animate([ | |
{ | |
transform: 'translate(0, 0) scale(1)', | |
opacity: 1 | |
}, | |
{ | |
transform: `translate(${Math.cos(angle) * speed * 10}px, ${Math.sin(angle) * speed * 10}px) scale(0)`, | |
opacity: 0 | |
} | |
], { | |
duration: 1000, | |
easing: 'cubic-bezier(0.1, 0.8, 0.2, 1)' | |
}); | |
animation.onfinish = () => { | |
gameBoard.removeChild(particle); | |
}; | |
} | |
} | |
}); | |
</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> |