pong-ai / index.html
Ravisil's picture
Add 2 files
d397566 verified
<!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;
}
/* Custom toggle switch */
.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 settings */
.difficulty-selector {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 20px;
}
.difficulty-option {
display: flex;
align-items: center;
gap: 10px;
}
/* Media queries for smaller screens */
@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>
// Game elements
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');
// Audio elements
const paddleHitSound = document.getElementById('paddle-hit');
const wallHitSound = document.getElementById('wall-hit');
const scoreSound = document.getElementById('score-sound');
// Game variables
let gameRunning = false;
let gamePaused = false;
let leftScore = 0;
let rightScore = 0;
const winningScore = 5;
let gameSpeed = 1;
let humanPlayer = false;
let aiStrength = 'medium';
// Resize canvas to container size
function resizeCanvas() {
const container = document.getElementById('game-container');
canvas.width = container.clientWidth;
canvas.height = container.clientHeight;
}
window.addEventListener('resize', () => {
resizeCanvas();
resetPositions();
});
// Initialize canvas size
resizeCanvas();
// Paddle variables
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
};
// Ball variables
let ballSize;
let ball = {
x: 0,
y: 0,
width: 0,
height: 0,
dx: 0,
dy: 0
};
function resetPositions() {
calculateDimensions();
// Set paddle positions
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;
// Set ball size based on canvas dimensions
ballSize = Math.max(8, Math.min(canvas.width * 0.015, canvas.height * 0.02));
ball.width = ballSize;
ball.height = ballSize;
resetBall();
}
// AI Personality profiles
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
}
};
// Trail effect variables
const trail = [];
const maxTrailLength = 10;
// Create particles for background
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();
// Draw functions
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);
// Move particles
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([]);
}
// AI decision making
function decideAIMovement(paddle, ball, isLeftPaddle, profile) {
const paddleCenter = paddle.y + paddle.height / 2;
const ballCenter = ball.y + ball.height / 2;
// Add some prediction based on ball direction
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);
}
}
// Keep prediction within bounds
predictedY = Math.max(0, Math.min(canvas.height, predictedY));
// Add some randomness to the AI's accuracy
const targetY = predictedY + (Math.random() - 0.5) * paddle.height * (1 - profile.accuracy);
// Move paddle towards the target
if (paddleCenter < targetY - 10) {
return paddleSpeed * profile.speedMultiplier;
} else if (paddleCenter > targetY + 10) {
return -paddleSpeed * profile.speedMultiplier;
} else {
return 0;
}
}
// Game logic
function update() {
if (gamePaused) return;
// AI movement
const difficulty = document.querySelector('input[name="difficulty"]:checked').value;
const profile = aiProfiles[difficulty];
// Left paddle AI (unless human player is enabled)
if (!humanPlayer || !gameRunning) {
leftPaddle.dy = decideAIMovement(leftPaddle, ball, true, profile);
}
// Right paddle AI (always controlled by AI in this mode)
rightPaddle.dy = decideAIMovement(rightPaddle, ball, false, profile);
// Move paddles
leftPaddle.y += leftPaddle.dy * gameSpeed;
rightPaddle.y += rightPaddle.dy * gameSpeed;
// Keep paddles within canvas
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;
}
// Move ball
ball.x += ball.dx * gameSpeed;
ball.y += ball.dy * gameSpeed;
// Add current ball position to trail
trail.push({ x: ball.x + ball.width / 2, y: ball.y + ball.height / 2 });
if (trail.length > maxTrailLength) {
trail.shift();
}
// Ball collision with top and bottom walls
if (ball.y < 0 || ball.y + ball.height > canvas.height) {
ball.dy = -ball.dy;
wallHitSound.currentTime = 0;
wallHitSound.play();
}
// Ball collision with paddles
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
) {
// Calculate angle based on where ball hits paddle
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); // More angle when human plays
ball.dx = Math.abs(ball.dx); // Ensure ball moves right
ball.dx *= humanPlayer ? 1.1 : 1.05; // Human gets slightly more speed
paddleHitSound.currentTime = 0;
paddleHitSound.play();
// Add some randomness
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
) {
// Calculate angle based on where ball hits paddle
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); // Ensure ball moves left
ball.dx *= 1.05;
paddleHitSound.currentTime = 0;
paddleHitSound.play();
// Add some randomness
ball.dy += (Math.random() * 2 - 1) * 0.5;
}
// Ball out of bounds (scoring)
if (ball.x + ball.width < 0) {
// Right AI scores
rightScore++;
rightScoreDisplay.textContent = `AI-2: ${rightScore}`;
resetBall();
scoreSound.currentTime = 0;
scoreSound.play();
if (rightScore >= winningScore) {
endGame(false);
}
}
if (ball.x > canvas.width) {
// Left AI scores
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;
// Random direction favoring the losing side (if human is playing)
if (humanPlayer && leftScore !== rightScore) {
const direction = leftScore < rightScore ? 1 : -1;
ball.dx = direction * 5;
} else {
// Random direction for AI vs AI
ball.dx = (Math.random() > 0.5 ? 1 : -1) * 5;
}
ball.dy = (Math.random() * 4 - 2);
// Clear trail
trail.length = 0;
}
function render() {
// Clear canvas
ctx.clearRect(0, 0, canvas.width, canvas.height);
// Draw background elements
drawParticles();
drawCenterLine();
// Draw game elements with different colors for human player
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);
}
}
// Game control functions
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`;
}
// Event listeners
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}`;
});
// Keyboard controls (only if human player is enabled)
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;
}
});
// Mouse controls (only if human player is enabled)
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;
}
});
// Initialize
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>