Ravisil commited on
Commit
d397566
·
verified ·
1 Parent(s): 7da1bc2

Add 2 files

Browse files
Files changed (2) hide show
  1. README.md +7 -5
  2. index.html +810 -19
README.md CHANGED
@@ -1,10 +1,12 @@
1
  ---
2
- title: Pong Ai
3
- emoji: 🚀
4
- colorFrom: green
5
- colorTo: indigo
6
  sdk: static
7
  pinned: false
 
 
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: pong-ai
3
+ emoji: 🐳
4
+ colorFrom: purple
5
+ colorTo: purple
6
  sdk: static
7
  pinned: false
8
+ tags:
9
+ - deepsite
10
  ---
11
 
12
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html CHANGED
@@ -1,19 +1,810 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI PONG SHOWDOWN</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <style>
9
+ @import url('https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&display=swap');
10
+
11
+ * {
12
+ margin: 0;
13
+ padding: 0;
14
+ box-sizing: border-box;
15
+ }
16
+
17
+ body {
18
+ background-color: #0a0a1a;
19
+ font-family: 'Orbitron', sans-serif;
20
+ color: #00fffc;
21
+ overflow: hidden;
22
+ height: 100vh;
23
+ display: flex;
24
+ flex-direction: column;
25
+ align-items: center;
26
+ justify-content: center;
27
+ }
28
+
29
+ #game-container {
30
+ position: relative;
31
+ width: 100%;
32
+ max-width: 800px;
33
+ height: auto;
34
+ aspect-ratio: 8/5;
35
+ border: 2px solid #00fffc;
36
+ box-shadow: 0 0 20px #00fffc, inset 0 0 20px #00fffc;
37
+ overflow: hidden;
38
+ background-color: rgba(0, 0, 20, 0.7);
39
+ }
40
+
41
+ #game-canvas {
42
+ width: 100%;
43
+ height: 100%;
44
+ background-color: transparent;
45
+ display: block;
46
+ }
47
+
48
+ #score {
49
+ display: flex;
50
+ justify-content: space-between;
51
+ width: 100%;
52
+ max-width: 800px;
53
+ margin-bottom: 20px;
54
+ font-size: clamp(16px, 3vw, 24px);
55
+ text-shadow: 0 0 10px #00fffc;
56
+ }
57
+
58
+ .score-display {
59
+ padding: clamp(5px, 1.5vw, 10px) clamp(10px, 4vw, 30px);
60
+ background-color: rgba(0, 255, 252, 0.1);
61
+ border: 1px solid #00fffc;
62
+ border-radius: 5px;
63
+ }
64
+
65
+ #settings-panel {
66
+ position: absolute;
67
+ top: 10px;
68
+ right: 10px;
69
+ z-index: 20;
70
+ display: flex;
71
+ flex-direction: column;
72
+ gap: 5px;
73
+ }
74
+
75
+ #start-screen, #game-over {
76
+ position: absolute;
77
+ top: 0;
78
+ left: 0;
79
+ width: 100%;
80
+ height: 100%;
81
+ display: flex;
82
+ flex-direction: column;
83
+ align-items: center;
84
+ justify-content: center;
85
+ background-color: rgba(0, 0, 20, 0.8);
86
+ z-index: 10;
87
+ }
88
+
89
+ h1 {
90
+ font-size: clamp(24px, 6vw, 48px);
91
+ margin-bottom: clamp(15px, 4vw, 30px);
92
+ text-shadow: 0 0 15px #00fffc;
93
+ letter-spacing: 3px;
94
+ text-align: center;
95
+ }
96
+
97
+ button {
98
+ background-color: transparent;
99
+ color: #00fffc;
100
+ border: 2px solid #00fffc;
101
+ padding: clamp(10px, 2.5vw, 15px) clamp(15px, 4vw, 30px);
102
+ font-family: 'Orbitron', sans-serif;
103
+ font-size: clamp(14px, 3vw, 18px);
104
+ cursor: pointer;
105
+ margin: clamp(5px, 1.5vw, 10px);
106
+ transition: all 0.3s;
107
+ text-shadow: 0 0 5px #00fffc;
108
+ box-shadow: 0 0 10px #00fffc;
109
+ }
110
+
111
+ button:hover {
112
+ background-color: rgba(0, 255, 252, 0.2);
113
+ transform: scale(1.05);
114
+ }
115
+
116
+ .sm-btn {
117
+ padding: 5px 10px;
118
+ font-size: 12px;
119
+ margin: 0;
120
+ }
121
+
122
+ #game-over {
123
+ display: none;
124
+ }
125
+
126
+ #grid {
127
+ position: absolute;
128
+ top: 0;
129
+ left: 0;
130
+ width: 100%;
131
+ height: 100%;
132
+ background:
133
+ linear-gradient(rgba(0, 255, 252, 0.05) 1px, transparent 1px),
134
+ linear-gradient(90deg, rgba(0, 255, 252, 0.05) 1px, transparent 1px);
135
+ background-size: 20px 20px;
136
+ z-index: -1;
137
+ }
138
+
139
+ .particle {
140
+ position: absolute;
141
+ width: 2px;
142
+ height: 2px;
143
+ background-color: #00fffc;
144
+ border-radius: 50%;
145
+ pointer-events: none;
146
+ z-index: -1;
147
+ }
148
+
149
+ #instructions {
150
+ margin-top: clamp(10px, 3vw, 20px);
151
+ font-size: clamp(10px, 2vw, 14px);
152
+ color: rgba(0, 255, 252, 0.7);
153
+ text-align: center;
154
+ padding: 0 10px;
155
+ }
156
+
157
+ .ai-selector {
158
+ display: flex;
159
+ flex-direction: column;
160
+ gap: 10px;
161
+ margin-bottom: 20px;
162
+ }
163
+
164
+ .ai-option {
165
+ display: flex;
166
+ align-items: center;
167
+ gap: 10px;
168
+ }
169
+
170
+ .ai-option input {
171
+ accent-color: #00fffc;
172
+ }
173
+
174
+ /* Custom toggle switch */
175
+ .switch {
176
+ position: relative;
177
+ display: inline-block;
178
+ width: 50px;
179
+ height: 24px;
180
+ }
181
+
182
+ .switch input {
183
+ opacity: 0;
184
+ width: 0;
185
+ height: 0;
186
+ }
187
+
188
+ .slider {
189
+ position: absolute;
190
+ cursor: pointer;
191
+ top: 0;
192
+ left: 0;
193
+ right: 0;
194
+ bottom: 0;
195
+ background-color: rgba(0, 255, 252, 0.2);
196
+ transition: .4s;
197
+ border: 1px solid #00fffc;
198
+ }
199
+
200
+ .slider:before {
201
+ position: absolute;
202
+ content: "";
203
+ height: 16px;
204
+ width: 16px;
205
+ left: 4px;
206
+ bottom: 3px;
207
+ background-color: #00fffc;
208
+ transition: .4s;
209
+ }
210
+
211
+ input:checked + .slider {
212
+ background-color: rgba(0, 255, 252, 0.4);
213
+ }
214
+
215
+ input:checked + .slider:before {
216
+ transform: translateX(26px);
217
+ }
218
+
219
+ .slider.round {
220
+ border-radius: 24px;
221
+ }
222
+
223
+ .slider.round:before {
224
+ border-radius: 50%;
225
+ }
226
+
227
+ /* Difficulty settings */
228
+ .difficulty-selector {
229
+ display: flex;
230
+ flex-direction: column;
231
+ gap: 10px;
232
+ margin-bottom: 20px;
233
+ }
234
+
235
+ .difficulty-option {
236
+ display: flex;
237
+ align-items: center;
238
+ gap: 10px;
239
+ }
240
+
241
+ /* Media queries for smaller screens */
242
+ @media (max-width: 600px) {
243
+ #settings-panel {
244
+ top: 5px;
245
+ right: 5px;
246
+ }
247
+
248
+ button.sm-btn {
249
+ font-size: 10px;
250
+ padding: 3px 6px;
251
+ }
252
+ }
253
+ </style>
254
+ </head>
255
+ <body>
256
+ <div id="score">
257
+ <div id="left-ai-score" class="score-display">AI-1: 0</div>
258
+ <div id="right-ai-score" class="score-display">AI-2: 0</div>
259
+ </div>
260
+
261
+ <div id="game-container">
262
+ <div id="grid"></div>
263
+ <canvas id="game-canvas"></canvas>
264
+
265
+ <div id="settings-panel">
266
+ <button id="pause-btn" class="sm-btn">⏸ PAUSE</button>
267
+ <button id="speed-btn" class="sm-btn">⚡ 1X</button>
268
+ </div>
269
+
270
+ <div id="start-screen">
271
+ <h1>AI PONG SHOWDOWN</h1>
272
+
273
+ <div class="ai-selector">
274
+ <div class="ai-option">
275
+ <label class="switch">
276
+ <input type="checkbox" id="human-player-toggle">
277
+ <span class="slider round"></span>
278
+ </label>
279
+ <span>Human Player?</span>
280
+ </div>
281
+ </div>
282
+
283
+ <div class="difficulty-selector">
284
+ <h3>AI DIFFICULTY:</h3>
285
+ <div class="difficulty-option">
286
+ <input type="radio" id="easy" name="difficulty" value="easy" checked>
287
+ <label for="easy">Easy</label>
288
+ </div>
289
+ <div class="difficulty-option">
290
+ <input type="radio" id="medium" name="difficulty" value="medium">
291
+ <label for="medium">Medium</label>
292
+ </div>
293
+ <div class="difficulty-option">
294
+ <input type="radio" id="hard" name="difficulty" value="hard">
295
+ <label for="hard">Hard</label>
296
+ </div>
297
+ </div>
298
+
299
+ <button id="start-button">START SHOWDOWN</button>
300
+ <div id="instructions">
301
+ OBSERVE THE AI BATTLE OR PLAY AGAINST THEM<br>
302
+ FIRST TO SCORE 5 WINS
303
+ </div>
304
+ </div>
305
+
306
+ <div id="game-over">
307
+ <h1 id="result-text">AI-1 VICTORY!</h1>
308
+ <button id="restart-button">NEW SHOWDOWN</button>
309
+ </div>
310
+ </div>
311
+
312
+ <audio id="paddle-hit" src="data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU..."></audio>
313
+ <audio id="wall-hit" src="data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU..."></audio>
314
+ <audio id="score-sound" src="data:audio/wav;base64,UklGRl9vT19XQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YU..."></audio>
315
+
316
+ <script>
317
+ // Game elements
318
+ const canvas = document.getElementById('game-canvas');
319
+ const ctx = canvas.getContext('2d');
320
+ const startScreen = document.getElementById('start-screen');
321
+ const gameOverScreen = document.getElementById('game-over');
322
+ const resultText = document.getElementById('result-text');
323
+ const startButton = document.getElementById('start-button');
324
+ const restartButton = document.getElementById('restart-button');
325
+ const leftScoreDisplay = document.getElementById('left-ai-score');
326
+ const rightScoreDisplay = document.getElementById('right-ai-score');
327
+ const pauseBtn = document.getElementById('pause-btn');
328
+ const speedBtn = document.getElementById('speed-btn');
329
+ const humanPlayerToggle = document.getElementById('human-player-toggle');
330
+
331
+ // Audio elements
332
+ const paddleHitSound = document.getElementById('paddle-hit');
333
+ const wallHitSound = document.getElementById('wall-hit');
334
+ const scoreSound = document.getElementById('score-sound');
335
+
336
+ // Game variables
337
+ let gameRunning = false;
338
+ let gamePaused = false;
339
+ let leftScore = 0;
340
+ let rightScore = 0;
341
+ const winningScore = 5;
342
+ let gameSpeed = 1;
343
+ let humanPlayer = false;
344
+ let aiStrength = 'medium';
345
+
346
+ // Resize canvas to container size
347
+ function resizeCanvas() {
348
+ const container = document.getElementById('game-container');
349
+ canvas.width = container.clientWidth;
350
+ canvas.height = container.clientHeight;
351
+ }
352
+
353
+ window.addEventListener('resize', () => {
354
+ resizeCanvas();
355
+ resetPositions();
356
+ });
357
+
358
+ // Initialize canvas size
359
+ resizeCanvas();
360
+
361
+ // Paddle variables
362
+ let paddleWidth, paddleHeight, paddleSpeed;
363
+
364
+ function calculateDimensions() {
365
+ paddleWidth = Math.max(10, canvas.width * 0.015);
366
+ paddleHeight = Math.max(60, canvas.height * 0.2);
367
+ paddleSpeed = canvas.height * 0.012;
368
+ }
369
+
370
+ const leftPaddle = {
371
+ x: 30,
372
+ y: 0,
373
+ width: 0,
374
+ height: 0,
375
+ dy: 0
376
+ };
377
+
378
+ const rightPaddle = {
379
+ x: 0,
380
+ y: 0,
381
+ width: 0,
382
+ height: 0,
383
+ dy: 0
384
+ };
385
+
386
+ // Ball variables
387
+ let ballSize;
388
+ let ball = {
389
+ x: 0,
390
+ y: 0,
391
+ width: 0,
392
+ height: 0,
393
+ dx: 0,
394
+ dy: 0
395
+ };
396
+
397
+ function resetPositions() {
398
+ calculateDimensions();
399
+
400
+ // Set paddle positions
401
+ leftPaddle.width = paddleWidth;
402
+ leftPaddle.height = paddleHeight;
403
+ leftPaddle.y = canvas.height / 2 - paddleHeight / 2;
404
+ leftPaddle.x = canvas.width * 0.03;
405
+
406
+ rightPaddle.width = paddleWidth;
407
+ rightPaddle.height = paddleHeight;
408
+ rightPaddle.y = canvas.height / 2 - paddleHeight / 2;
409
+ rightPaddle.x = canvas.width - (canvas.width * 0.03) - paddleWidth;
410
+
411
+ // Set ball size based on canvas dimensions
412
+ ballSize = Math.max(8, Math.min(canvas.width * 0.015, canvas.height * 0.02));
413
+ ball.width = ballSize;
414
+ ball.height = ballSize;
415
+
416
+ resetBall();
417
+ }
418
+
419
+ // AI Personality profiles
420
+ const aiProfiles = {
421
+ easy: {
422
+ reactionDelay: 0.5,
423
+ accuracy: 0.7,
424
+ prediction: 0.4,
425
+ speedMultiplier: 0.7
426
+ },
427
+ medium: {
428
+ reactionDelay: 0.3,
429
+ accuracy: 0.85,
430
+ prediction: 0.6,
431
+ speedMultiplier: 0.85
432
+ },
433
+ hard: {
434
+ reactionDelay: 0.1,
435
+ accuracy: 0.95,
436
+ prediction: 0.8,
437
+ speedMultiplier: 1.0
438
+ }
439
+ };
440
+
441
+ // Trail effect variables
442
+ const trail = [];
443
+ const maxTrailLength = 10;
444
+
445
+ // Create particles for background
446
+ function createParticles() {
447
+ const particles = [];
448
+ const particleCount = Math.floor(canvas.width * canvas.height / 2000);
449
+
450
+ for (let i = 0; i < particleCount; i++) {
451
+ particles.push({
452
+ x: Math.random() * canvas.width,
453
+ y: Math.random() * canvas.height,
454
+ size: Math.random() * 2 + 1,
455
+ speed: Math.random() * 2 + 1
456
+ });
457
+ }
458
+
459
+ return particles;
460
+ }
461
+
462
+ let particles = createParticles();
463
+
464
+ // Draw functions
465
+ function drawPaddle(x, y, width, height, color = '#00fffc') {
466
+ ctx.fillStyle = color;
467
+ ctx.shadowBlur = 15;
468
+ ctx.shadowColor = color;
469
+ ctx.fillRect(x, y, width, height);
470
+ ctx.shadowBlur = 0;
471
+ }
472
+
473
+ function drawBall(x, y, width, height, color = '#ff00f7') {
474
+ ctx.fillStyle = color;
475
+ ctx.shadowBlur = 15;
476
+ ctx.shadowColor = color;
477
+ ctx.beginPath();
478
+ ctx.arc(x + width / 2, y + height / 2, width / 2, 0, Math.PI * 2);
479
+ ctx.fill();
480
+ ctx.shadowBlur = 0;
481
+ }
482
+
483
+ function drawTrail() {
484
+ for (let i = 0; i < trail.length; i++) {
485
+ const opacity = i / trail.length;
486
+ ctx.fillStyle = `rgba(255, 0, 247, ${opacity})`;
487
+ ctx.beginPath();
488
+ ctx.arc(trail[i].x, trail[i].y, ballSize / 2 * opacity, 0, Math.PI * 2);
489
+ ctx.fill();
490
+ }
491
+ }
492
+
493
+ function drawParticles() {
494
+ particles.forEach(particle => {
495
+ ctx.fillStyle = `rgba(0, 255, 252, ${Math.random() * 0.5})`;
496
+ ctx.fillRect(particle.x, particle.y, particle.size, particle.size);
497
+
498
+ // Move particles
499
+ particle.y += particle.speed;
500
+ if (particle.y > canvas.height) {
501
+ particle.y = 0;
502
+ particle.x = Math.random() * canvas.width;
503
+ }
504
+ });
505
+ }
506
+
507
+ function drawCenterLine() {
508
+ ctx.strokeStyle = 'rgba(0, 255, 252, 0.2)';
509
+ ctx.lineWidth = 2;
510
+ ctx.setLineDash([10, 10]);
511
+ ctx.beginPath();
512
+ ctx.moveTo(canvas.width / 2, 0);
513
+ ctx.lineTo(canvas.width / 2, canvas.height);
514
+ ctx.stroke();
515
+ ctx.setLineDash([]);
516
+ }
517
+
518
+ // AI decision making
519
+ function decideAIMovement(paddle, ball, isLeftPaddle, profile) {
520
+ const paddleCenter = paddle.y + paddle.height / 2;
521
+ const ballCenter = ball.y + ball.height / 2;
522
+
523
+ // Add some prediction based on ball direction
524
+ let predictedY = ballCenter;
525
+ if (profile.prediction > 0) {
526
+ if (ball.dx !== 0) {
527
+ const framesToContact = Math.abs((paddle.x - ball.x) / ball.dx);
528
+ predictedY = ballCenter + (ball.dy * framesToContact * profile.prediction);
529
+ }
530
+ }
531
+
532
+ // Keep prediction within bounds
533
+ predictedY = Math.max(0, Math.min(canvas.height, predictedY));
534
+
535
+ // Add some randomness to the AI's accuracy
536
+ const targetY = predictedY + (Math.random() - 0.5) * paddle.height * (1 - profile.accuracy);
537
+
538
+ // Move paddle towards the target
539
+ if (paddleCenter < targetY - 10) {
540
+ return paddleSpeed * profile.speedMultiplier;
541
+ } else if (paddleCenter > targetY + 10) {
542
+ return -paddleSpeed * profile.speedMultiplier;
543
+ } else {
544
+ return 0;
545
+ }
546
+ }
547
+
548
+ // Game logic
549
+ function update() {
550
+ if (gamePaused) return;
551
+
552
+ // AI movement
553
+ const difficulty = document.querySelector('input[name="difficulty"]:checked').value;
554
+ const profile = aiProfiles[difficulty];
555
+
556
+ // Left paddle AI (unless human player is enabled)
557
+ if (!humanPlayer || !gameRunning) {
558
+ leftPaddle.dy = decideAIMovement(leftPaddle, ball, true, profile);
559
+ }
560
+
561
+ // Right paddle AI (always controlled by AI in this mode)
562
+ rightPaddle.dy = decideAIMovement(rightPaddle, ball, false, profile);
563
+
564
+ // Move paddles
565
+ leftPaddle.y += leftPaddle.dy * gameSpeed;
566
+ rightPaddle.y += rightPaddle.dy * gameSpeed;
567
+
568
+ // Keep paddles within canvas
569
+ if (leftPaddle.y < 0) leftPaddle.y = 0;
570
+ if (leftPaddle.y + leftPaddle.height > canvas.height) {
571
+ leftPaddle.y = canvas.height - leftPaddle.height;
572
+ }
573
+
574
+ if (rightPaddle.y < 0) rightPaddle.y = 0;
575
+ if (rightPaddle.y + rightPaddle.height > canvas.height) {
576
+ rightPaddle.y = canvas.height - rightPaddle.height;
577
+ }
578
+
579
+ // Move ball
580
+ ball.x += ball.dx * gameSpeed;
581
+ ball.y += ball.dy * gameSpeed;
582
+
583
+ // Add current ball position to trail
584
+ trail.push({ x: ball.x + ball.width / 2, y: ball.y + ball.height / 2 });
585
+ if (trail.length > maxTrailLength) {
586
+ trail.shift();
587
+ }
588
+
589
+ // Ball collision with top and bottom walls
590
+ if (ball.y < 0 || ball.y + ball.height > canvas.height) {
591
+ ball.dy = -ball.dy;
592
+ wallHitSound.currentTime = 0;
593
+ wallHitSound.play();
594
+ }
595
+
596
+ // Ball collision with paddles
597
+ if (
598
+ ball.x < leftPaddle.x + leftPaddle.width &&
599
+ ball.x + ball.width > leftPaddle.x &&
600
+ ball.y < leftPaddle.y + leftPaddle.height &&
601
+ ball.y + ball.height > leftPaddle.y
602
+ ) {
603
+ // Calculate angle based on where ball hits paddle
604
+ const hitPosition = (ball.y + ball.height / 2) - (leftPaddle.y + leftPaddle.height / 2);
605
+ const normalizedHit = hitPosition / (leftPaddle.height / 2);
606
+ ball.dy = normalizedHit * 5 * (humanPlayer ? 1.2 : 1); // More angle when human plays
607
+
608
+ ball.dx = Math.abs(ball.dx); // Ensure ball moves right
609
+ ball.dx *= humanPlayer ? 1.1 : 1.05; // Human gets slightly more speed
610
+
611
+ paddleHitSound.currentTime = 0;
612
+ paddleHitSound.play();
613
+
614
+ // Add some randomness
615
+ ball.dy += (Math.random() * 2 - 1) * 0.5;
616
+ }
617
+
618
+ if (
619
+ ball.x < rightPaddle.x + rightPaddle.width &&
620
+ ball.x + ball.width > rightPaddle.x &&
621
+ ball.y < rightPaddle.y + rightPaddle.height &&
622
+ ball.y + ball.height > rightPaddle.y
623
+ ) {
624
+ // Calculate angle based on where ball hits paddle
625
+ const hitPosition = (ball.y + ball.height / 2) - (rightPaddle.y + rightPaddle.height / 2);
626
+ const normalizedHit = hitPosition / (rightPaddle.height / 2);
627
+ ball.dy = normalizedHit * 5;
628
+
629
+ ball.dx = -Math.abs(ball.dx); // Ensure ball moves left
630
+ ball.dx *= 1.05;
631
+
632
+ paddleHitSound.currentTime = 0;
633
+ paddleHitSound.play();
634
+
635
+ // Add some randomness
636
+ ball.dy += (Math.random() * 2 - 1) * 0.5;
637
+ }
638
+
639
+ // Ball out of bounds (scoring)
640
+ if (ball.x + ball.width < 0) {
641
+ // Right AI scores
642
+ rightScore++;
643
+ rightScoreDisplay.textContent = `AI-2: ${rightScore}`;
644
+ resetBall();
645
+ scoreSound.currentTime = 0;
646
+ scoreSound.play();
647
+
648
+ if (rightScore >= winningScore) {
649
+ endGame(false);
650
+ }
651
+ }
652
+
653
+ if (ball.x > canvas.width) {
654
+ // Left AI scores
655
+ leftScore++;
656
+ leftScoreDisplay.textContent = `AI-1: ${leftScore}`;
657
+ resetBall();
658
+ scoreSound.currentTime = 0;
659
+ scoreSound.play();
660
+
661
+ if (leftScore >= winningScore) {
662
+ endGame(true);
663
+ }
664
+ }
665
+ }
666
+
667
+ function resetBall() {
668
+ ball.x = canvas.width / 2 - ball.width / 2;
669
+ ball.y = canvas.height / 2 - ball.height / 2;
670
+
671
+ // Random direction favoring the losing side (if human is playing)
672
+ if (humanPlayer && leftScore !== rightScore) {
673
+ const direction = leftScore < rightScore ? 1 : -1;
674
+ ball.dx = direction * 5;
675
+ } else {
676
+ // Random direction for AI vs AI
677
+ ball.dx = (Math.random() > 0.5 ? 1 : -1) * 5;
678
+ }
679
+
680
+ ball.dy = (Math.random() * 4 - 2);
681
+
682
+ // Clear trail
683
+ trail.length = 0;
684
+ }
685
+
686
+ function render() {
687
+ // Clear canvas
688
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
689
+
690
+ // Draw background elements
691
+ drawParticles();
692
+ drawCenterLine();
693
+
694
+ // Draw game elements with different colors for human player
695
+ drawTrail();
696
+ drawPaddle(
697
+ leftPaddle.x,
698
+ leftPaddle.y,
699
+ leftPaddle.width,
700
+ leftPaddle.height,
701
+ humanPlayer ? '#00ff9d' : '#00fffc'
702
+ );
703
+ drawPaddle(
704
+ rightPaddle.x,
705
+ rightPaddle.y,
706
+ rightPaddle.width,
707
+ rightPaddle.height,
708
+ '#ff00f7'
709
+ );
710
+ drawBall(ball.x, ball.y, ball.width, ball.height);
711
+ }
712
+
713
+ function gameLoop() {
714
+ if (gameRunning && !gamePaused) {
715
+ update();
716
+ render();
717
+ requestAnimationFrame(gameLoop);
718
+ }
719
+ }
720
+
721
+ // Game control functions
722
+ function startGame() {
723
+ humanPlayer = humanPlayerToggle.checked;
724
+ leftScore = 0;
725
+ rightScore = 0;
726
+ leftScoreDisplay.textContent = `AI-1: ${leftScore}`;
727
+ rightScoreDisplay.textContent = humanPlayer ? `YOU: ${rightScore}` : `AI-2: ${rightScore}`;
728
+
729
+ resetPositions();
730
+ startScreen.style.display = 'none';
731
+ gameOverScreen.style.display = 'none';
732
+ gameRunning = true;
733
+ gamePaused = false;
734
+ gameLoop();
735
+ }
736
+
737
+ function endGame(leftWon) {
738
+ gameRunning = false;
739
+ gameOverScreen.style.display = 'flex';
740
+
741
+ if (humanPlayer) {
742
+ resultText.textContent = leftWon ? "AI DEFEATED YOU!" : "YOU DEFEATED AI!";
743
+ } else {
744
+ resultText.textContent = leftWon ? "AI-1 IS VICTORIOUS!" : "AI-2 IS VICTORIOUS!";
745
+ }
746
+ }
747
+
748
+ function togglePause() {
749
+ gamePaused = !gamePaused;
750
+ pauseBtn.textContent = gamePaused ? "▶ RESUME" : "⏸ PAUSE";
751
+ if (gameRunning && !gamePaused) {
752
+ gameLoop();
753
+ }
754
+ }
755
+
756
+ function cycleGameSpeed() {
757
+ const speeds = [1, 1.5, 2, 3];
758
+ const currentIndex = speeds.indexOf(gameSpeed);
759
+ const nextIndex = (currentIndex + 1) % speeds.length;
760
+ gameSpeed = speeds[nextIndex];
761
+ speedBtn.textContent = `⚡ ${gameSpeed}X`;
762
+ }
763
+
764
+ // Event listeners
765
+ startButton.addEventListener('click', startGame);
766
+ restartButton.addEventListener('click', startGame);
767
+ pauseBtn.addEventListener('click', togglePause);
768
+ speedBtn.addEventListener('click', cycleGameSpeed);
769
+
770
+ humanPlayerToggle.addEventListener('change', function() {
771
+ rightScoreDisplay.textContent = this.checked ? `YOU: ${rightScore}` : `AI-2: ${rightScore}`;
772
+ });
773
+
774
+ // Keyboard controls (only if human player is enabled)
775
+ document.addEventListener('keydown', (e) => {
776
+ if (!humanPlayer || !gameRunning) return;
777
+
778
+ if (e.key === 'ArrowUp') {
779
+ leftPaddle.dy = -paddleSpeed * gameSpeed;
780
+ } else if (e.key === 'ArrowDown') {
781
+ leftPaddle.dy = paddleSpeed * gameSpeed;
782
+ }
783
+ });
784
+
785
+ document.addEventListener('keyup', (e) => {
786
+ if (!humanPlayer || !gameRunning) return;
787
+
788
+ if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
789
+ leftPaddle.dy = 0;
790
+ }
791
+ });
792
+
793
+ // Mouse controls (only if human player is enabled)
794
+ canvas.addEventListener('mousemove', (e) => {
795
+ if (!humanPlayer || !gameRunning) return;
796
+
797
+ const rect = canvas.getBoundingClientRect();
798
+ const mouseY = e.clientY - rect.top - leftPaddle.height / 2;
799
+
800
+ if (mouseY >= 0 && mouseY <= canvas.height - leftPaddle.height) {
801
+ leftPaddle.y = mouseY;
802
+ }
803
+ });
804
+
805
+ // Initialize
806
+ resetPositions();
807
+ particles = createParticles();
808
+ </script>
809
+ <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>
810
+ </html>