cybernetic-pong / index.html
DanPoliakov's picture
Add 2 files
4bbd9e8 verified
<!DOCTYPE html>
<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>