victor HF Staff commited on
Commit
5898d93
·
verified ·
1 Parent(s): b5af9f5

Add index.html

Browse files
Files changed (1) hide show
  1. index.html +838 -18
index.html CHANGED
@@ -1,19 +1,839 @@
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>Neon Pong AI Challenge</title>
7
+ <link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700&family=Press+Start+2P&display=swap" rel="stylesheet">
8
+ <style>
9
+ :root {
10
+ --neon-blue: #08f;
11
+ --neon-pink: #f0f;
12
+ --neon-purple: #90f;
13
+ --bg-dark: #111;
14
+ --bg-darker: #070710;
15
+ --text: #fff;
16
+ --glow: 0 0 10px;
17
+ }
18
+
19
+ * {
20
+ margin: 0;
21
+ padding: 0;
22
+ box-sizing: border-box;
23
+ }
24
+
25
+ body {
26
+ font-family: 'Orbitron', sans-serif;
27
+ background: var(--bg-darker);
28
+ color: var(--text);
29
+ min-height: 100vh;
30
+ display: flex;
31
+ flex-direction: column;
32
+ align-items: center;
33
+ justify-content: center;
34
+ overflow: hidden;
35
+ position: relative;
36
+ }
37
+
38
+ body::before {
39
+ content: "";
40
+ position: absolute;
41
+ top: 0;
42
+ left: 0;
43
+ width: 100%;
44
+ height: 100%;
45
+ background: radial-gradient(circle at center, transparent 0%, var(--bg-dark) 80%);
46
+ pointer-events: none;
47
+ z-index: -2;
48
+ }
49
+
50
+ .game-title {
51
+ font-family: 'Press Start 2P', cursive;
52
+ font-size: 2.5rem;
53
+ margin-bottom: 20px;
54
+ text-align: center;
55
+ color: var(--neon-blue);
56
+ text-shadow: var(--glow) var(--neon-blue), 0 0 20px var(--neon-blue);
57
+ animation: pulse 2s infinite alternate;
58
+ }
59
+
60
+ @keyframes pulse {
61
+ from {
62
+ opacity: 1;
63
+ }
64
+ to {
65
+ opacity: 0.7;
66
+ }
67
+ }
68
+
69
+ .game-container {
70
+ position: relative;
71
+ width: 800px;
72
+ height: 500px;
73
+ margin: 20px auto;
74
+ border: 3px solid var(--neon-purple);
75
+ box-shadow: var(--glow) var(--neon-purple), inset var(--glow) var(--neon-purple);
76
+ border-radius: 10px;
77
+ overflow: hidden;
78
+ background: var(--bg-dark);
79
+ }
80
+
81
+ #gameCanvas {
82
+ display: block;
83
+ width: 100%;
84
+ height: 100%;
85
+ }
86
+
87
+ .center-line {
88
+ position: absolute;
89
+ top: 0;
90
+ left: 50%;
91
+ width: 2px;
92
+ height: 100%;
93
+ background: linear-gradient(transparent 0%, var(--neon-purple) 50%, transparent 100%);
94
+ opacity: 0.5;
95
+ }
96
+
97
+ .score-container {
98
+ display: flex;
99
+ justify-content: space-between;
100
+ width: 800px;
101
+ margin-bottom: 20px;
102
+ }
103
+
104
+ .score-box {
105
+ background: rgba(0, 0, 0, 0.5);
106
+ border: 2px solid var(--neon-blue);
107
+ border-radius: 10px;
108
+ padding: 15px 30px;
109
+ text-align: center;
110
+ box-shadow: var(--glow) var(--neon-blue);
111
+ }
112
+
113
+ .score-title {
114
+ font-size: 1rem;
115
+ margin-bottom: 5px;
116
+ color: var(--neon-blue);
117
+ }
118
+
119
+ .score {
120
+ font-size: 2rem;
121
+ font-family: 'Press Start 2P', cursive;
122
+ }
123
+
124
+ .controls {
125
+ margin-top: 20px;
126
+ display: flex;
127
+ gap: 15px;
128
+ }
129
+
130
+ .btn {
131
+ background: transparent;
132
+ color: var(--text);
133
+ border: 2px solid var(--neon-pink);
134
+ padding: 10px 20px;
135
+ font-family: 'Orbitron', sans-serif;
136
+ font-size: 1rem;
137
+ border-radius: 5px;
138
+ cursor: pointer;
139
+ transition: all 0.3s;
140
+ box-shadow: 0 0 5px var(--neon-pink);
141
+ }
142
+
143
+ .btn:hover {
144
+ background: var(--neon-pink);
145
+ color: var(--bg-dark);
146
+ box-shadow: 0 0 15px var(--neon-pink);
147
+ transform: translateY(-2px);
148
+ }
149
+
150
+ .btn:active {
151
+ transform: translateY(0);
152
+ }
153
+
154
+ .btn-start {
155
+ border-color: var(--neon-blue);
156
+ box-shadow: 0 0 5px var(--neon-blue);
157
+ }
158
+
159
+ .btn-start:hover {
160
+ background: var(--neon-blue);
161
+ color: var(--bg-dark);
162
+ box-shadow: 0 0 15px var(--neon-blue);
163
+ }
164
+
165
+ .settings {
166
+ margin-top: 30px;
167
+ width: 800px;
168
+ background: rgba(0, 0, 0, 0.3);
169
+ border: 1px solid var(--neon-purple);
170
+ border-radius: 10px;
171
+ padding: 15px;
172
+ }
173
+
174
+ .settings-title {
175
+ color: var(--neon-purple);
176
+ margin-bottom: 10px;
177
+ font-size: 1.2rem;
178
+ text-shadow: 0 0 5px var(--neon-purple);
179
+ }
180
+
181
+ .slider-container {
182
+ margin-bottom: 10px;
183
+ }
184
+
185
+ .slider-label {
186
+ display: flex;
187
+ justify-content: space-between;
188
+ margin-bottom: 5px;
189
+ }
190
+
191
+ .slider {
192
+ width: 100%;
193
+ -webkit-appearance: none;
194
+ height: 8px;
195
+ background: rgba(255, 255, 255, 0.1);
196
+ outline: none;
197
+ border-radius: 4px;
198
+ }
199
+
200
+ .slider::-webkit-slider-thumb {
201
+ -webkit-appearance: none;
202
+ appearance: none;
203
+ width: 18px;
204
+ height: 18px;
205
+ border-radius: 50%;
206
+ background: var(--neon-blue);
207
+ cursor: pointer;
208
+ box-shadow: 0 0 5px var(--neon-blue);
209
+ }
210
+
211
+ .match-history {
212
+ margin-top: 30px;
213
+ width: 800px;
214
+ background: rgba(0, 0, 0, 0.3);
215
+ border: 1px solid var(--neon-pink);
216
+ border-radius: 10px;
217
+ padding: 15px;
218
+ max-height: 200px;
219
+ overflow-y: auto;
220
+ }
221
+
222
+ .history-title {
223
+ color: var(--neon-pink);
224
+ margin-bottom: 10px;
225
+ font-size: 1.2rem;
226
+ text-shadow: 0 0 5px var(--neon-pink);
227
+ }
228
+
229
+ .history-item {
230
+ padding: 8px 0;
231
+ border-bottom: 1px dashed var(--neon-pink);
232
+ opacity: 0;
233
+ animation: fadeIn 0.5s forwards;
234
+ }
235
+
236
+ @keyframes fadeIn {
237
+ to {
238
+ opacity: 1;
239
+ }
240
+ }
241
+
242
+ .particle {
243
+ position: absolute;
244
+ border-radius: 50%;
245
+ pointer-events: none;
246
+ z-index: -1;
247
+ }
248
+
249
+ .mobile-controls {
250
+ display: none;
251
+ width: 100%;
252
+ padding: 20px;
253
+ justify-content: space-between;
254
+ }
255
+
256
+ .mobile-btn {
257
+ width: 100px;
258
+ height: 60px;
259
+ background: rgba(144, 0, 255, 0.2);
260
+ border: 2px solid var(--neon-purple);
261
+ color: white;
262
+ font-family: 'Orbitron', sans-serif;
263
+ border-radius: 10px;
264
+ box-shadow: 0 0 10px var(--neon-purple);
265
+ }
266
+
267
+ @media (max-width: 850px) {
268
+ .game-container, .score-container, .settings, .match-history {
269
+ width: 95%;
270
+ max-width: 400px;
271
+ }
272
+
273
+ .game-title {
274
+ font-size: 1.5rem;
275
+ }
276
+
277
+ .mobile-controls {
278
+ display: flex;
279
+ }
280
+ }
281
+
282
+ @media (max-width: 500px) {
283
+ .score-box {
284
+ padding: 10px 15px;
285
+ }
286
+
287
+ .score-title {
288
+ font-size: 0.8rem;
289
+ }
290
+
291
+ .score {
292
+ font-size: 1.5rem;
293
+ }
294
+ }
295
+
296
+ .game-message {
297
+ position: absolute;
298
+ top: 50%;
299
+ left: 50%;
300
+ transform: translate(-50%, -50%);
301
+ background: rgba(0, 0, 0, 0.8);
302
+ color: var(--neon-blue);
303
+ padding: 20px 40px;
304
+ border-radius: 10px;
305
+ font-size: 1.5rem;
306
+ text-align: center;
307
+ opacity: 0;
308
+ pointer-events: none;
309
+ transition: opacity 0.3s;
310
+ border: 2px solid var(--neon-blue);
311
+ box-shadow: 0 0 20px var(--neon-blue);
312
+ z-index: 10;
313
+ }
314
+
315
+ .game-message.show {
316
+ opacity: 1;
317
+ animation: pulse 1.5s infinite;
318
+ }
319
+ </style>
320
+ </head>
321
+ <body>
322
+ <h1 class="game-title">NEON PONG AI CHALLENGE</h1>
323
+
324
+ <div class="score-container">
325
+ <div class="score-box">
326
+ <div class="score-title">PLAYER</div>
327
+ <div class="score" id="playerScore">0</div>
328
+ </div>
329
+ <div class="score-box">
330
+ <div class="score-title">AI</div>
331
+ <div class="score" id="aiScore">0</div>
332
+ </div>
333
+ </div>
334
+
335
+ <div class="game-container">
336
+ <div class="center-line"></div>
337
+ <canvas id="gameCanvas"></canvas>
338
+ <div class="game-message" id="gameMessage">GAME OVER<br><span style="font-size:1rem">Click Start to Play Again</span></div>
339
+ </div>
340
+
341
+ <div class="mobile-controls">
342
+ <button class="mobile-btn" id="mobileUp">↑</button>
343
+ <button class="mobile-btn" id="mobileDown">↓</button>
344
+ </div>
345
+
346
+ <div class="controls">
347
+ <button class="btn btn-start" id="startBtn">START</button>
348
+ <button class="btn" id="pauseBtn">PAUSE</button>
349
+ <button class="btn" id="resetBtn">RESET</button>
350
+ </div>
351
+
352
+ <div class="settings">
353
+ <h3 class="settings-title">GAME SETTINGS</h3>
354
+ <div class="slider-container">
355
+ <div class="slider-label">
356
+ <span>Ball Speed</span>
357
+ <span id="ballSpeedValue">5</span>
358
+ </div>
359
+ <input type="range" min="1" max="10" value="5" class="slider" id="ballSpeedSlider">
360
+ </div>
361
+ <div class="slider-container">
362
+ <div class="slider-label">
363
+ <span>AI Difficulty</span>
364
+ <span id="aiDifficultyValue">5</span>
365
+ </div>
366
+ <input type="range" min="1" max="10" value="5" class="slider" id="aiDifficultySlider">
367
+ </div>
368
+ </div>
369
+
370
+ <div class="match-history">
371
+ <h3 class="history-title">MATCH HISTORY</h3>
372
+ <div id="historyList"></div>
373
+ </div>
374
+
375
+ <script>
376
+ document.addEventListener('DOMContentLoaded', () => {
377
+ // Canvas setup
378
+ const canvas = document.getElementById('gameCanvas');
379
+ const ctx = canvas.getContext('2d');
380
+ canvas.width = canvas.offsetWidth;
381
+ canvas.height = canvas.offsetHeight;
382
+
383
+ // Game elements
384
+ const paddleWidth = 15;
385
+ const paddleHeight = 100;
386
+ const ballSize = 12;
387
+ const borderOffset = 20;
388
+ const maxAngle = Math.PI / 3; // 60 degrees max bounce angle
389
+
390
+ // Game state
391
+ const state = {
392
+ playerScore: 0,
393
+ aiScore: 0,
394
+ gameRunning: false,
395
+ gamePaused: false,
396
+ winner: null,
397
+ history: [],
398
+ ballSpeed: 5,
399
+ aiDifficulty: 5
400
+ };
401
+
402
+ // Game objects
403
+ const player = {
404
+ x: borderOffset,
405
+ y: canvas.height / 2 - paddleHeight / 2,
406
+ width: paddleWidth,
407
+ height: paddleHeight,
408
+ speed: 8,
409
+ color: '#08f',
410
+ movingUp: false,
411
+ movingDown: false
412
+ };
413
+
414
+ const ai = {
415
+ x: canvas.width - borderOffset - paddleWidth,
416
+ y: canvas.height / 2 - paddleHeight / 2,
417
+ width: paddleWidth,
418
+ height: paddleHeight,
419
+ speed: 6,
420
+ color: '#f0f',
421
+ reactionDelay: 150 // ms
422
+ };
423
+
424
+ const ball = {
425
+ x: canvas.width / 2,
426
+ y: canvas.height / 2,
427
+ size: ballSize,
428
+ speedX: 0,
429
+ speedY: 0,
430
+ color: '#fff',
431
+ maxSpeed: 15
432
+ };
433
+
434
+ // DOM elements
435
+ const playerScoreDisplay = document.getElementById('playerScore');
436
+ const aiScoreDisplay = document.getElementById('aiScore');
437
+ const startBtn = document.getElementById('startBtn');
438
+ const pauseBtn = document.getElementById('pauseBtn');
439
+ const resetBtn = document.getElementById('resetBtn');
440
+ const ballSpeedSlider = document.getElementById('ballSpeedSlider');
441
+ const ballSpeedValue = document.getElementById('ballSpeedValue');
442
+ const aiDifficultySlider = document.getElementById('aiDifficultySlider');
443
+ const aiDifficultyValue = document.getElementById('aiDifficultyValue');
444
+ const historyList = document.getElementById('historyList');
445
+ const gameMessage = document.getElementById('gameMessage');
446
+ const mobileUpBtn = document.getElementById('mobileUp');
447
+ const mobileDownBtn = document.getElementById('mobileDown');
448
+
449
+ // Event listeners
450
+ startBtn.addEventListener('click', startGame);
451
+ pauseBtn.addEventListener('click', togglePause);
452
+ resetBtn.addEventListener('click', resetGame);
453
+ ballSpeedSlider.addEventListener('input', updateBallSpeed);
454
+ aiDifficultySlider.addEventListener('input', updateAiDifficulty);
455
+
456
+ // Mobile controls
457
+ mobileUpBtn.addEventListener('touchstart', () => player.movingUp = true);
458
+ mobileUpBtn.addEventListener('touchend', () => player.movingUp = false);
459
+ mobileUpBtn.addEventListener('mousedown', () => player.movingUp = true);
460
+ mobileUpBtn.addEventListener('mouseup', () => player.movingUp = false);
461
+ mobileDownBtn.addEventListener('touchstart', () => player.movingDown = true);
462
+ mobileDownBtn.addEventListener('touchend', () => player.movingDown = false);
463
+ mobileDownBtn.addEventListener('mousedown', () => player.movingDown = true);
464
+ mobileDownBtn.addEventListener('mouseup', () => player.movingDown = false);
465
+
466
+ // Keyboard controls
467
+ document.addEventListener('keydown', (e) => {
468
+ if (e.key === 'ArrowUp' || e.key === 'w') player.movingUp = true;
469
+ if (e.key === 'ArrowDown' || e.key === 's') player.movingDown = true;
470
+ if (e.key === ' ') togglePause();
471
+ });
472
+
473
+ document.addEventListener('keyup', (e) => {
474
+ if (e.key === 'ArrowUp' || e.key === 'w') player.movingUp = false;
475
+ if (e.key === 'ArrowDown' || e.key === 's') player.movingDown = false;
476
+ });
477
+
478
+ // Window resize
479
+ window.addEventListener('resize', () => {
480
+ canvas.width = canvas.offsetWidth;
481
+ canvas.height = canvas.offsetHeight;
482
+ // Re-center paddles and ball
483
+ player.y = canvas.height / 2 - paddleHeight / 2;
484
+ ai.y = canvas.height / 2 - paddleHeight / 2;
485
+ ball.x = canvas.width / 2;
486
+ ball.y = canvas.height / 2;
487
+ });
488
+
489
+ // Game functions
490
+ function startGame() {
491
+ if (state.gameRunning) return;
492
+
493
+ resetPositions();
494
+ ball.speedX = state.ballSpeed * (Math.random() > 0.5 ? 1 : -1);
495
+ ball.speedY = (Math.random() * 2 - 1) * state.ballSpeed;
496
+
497
+ state.gameRunning = true;
498
+ state.gamePaused = false;
499
+ state.winner = null;
500
+ gameMessage.classList.remove('show');
501
+
502
+ animate();
503
+ }
504
+
505
+ function resetGame() {
506
+ state.playerScore = 0;
507
+ state.aiScore = 0;
508
+ state.gameRunning = false;
509
+ state.gamePaused = false;
510
+ state.winner = null;
511
+
512
+ updateScoreboard();
513
+ resetPositions();
514
+ gameMessage.classList.remove('show');
515
+
516
+ // Clear the animation frame if it's running
517
+ cancelAnimationFrame(animationId);
518
+ }
519
+
520
+ function togglePause() {
521
+ if (!state.gameRunning) return;
522
+
523
+ state.gamePaused = !state.gamePaused;
524
+ if (!state.gamePaused) {
525
+ animate();
526
+ }
527
+ }
528
+
529
+ function resetPositions() {
530
+ player.y = canvas.height / 2 - paddleHeight / 2;
531
+ ai.y = canvas.height / 2 - paddleHeight / 2;
532
+ ball.x = canvas.width / 2;
533
+ ball.y = canvas.height / 2;
534
+ ball.speedX = 0;
535
+ ball.speedY = 0;
536
+ }
537
+
538
+ function updateBallSpeed() {
539
+ state.ballSpeed = parseInt(ballSpeedSlider.value);
540
+ ballSpeedValue.textContent = state.ballSpeed;
541
+ }
542
+
543
+ function updateAiDifficulty() {
544
+ state.aiDifficulty = parseInt(aiDifficultySlider.value);
545
+ aiDifficultyValue.textContent = state.aiDifficulty;
546
+
547
+ // Update AI speed based on difficulty (1-10 map to 3-10 speed)
548
+ ai.speed = 3 + (state.aiDifficulty / 10) * 7;
549
+
550
+ // Update reaction delay (faster AI reacts quicker)
551
+ ai.reactionDelay = 200 - (state.aiDifficulty * 15);
552
+ }
553
+
554
+ function checkCollision() {
555
+ // Ball collision with walls
556
+ if (ball.y <= 0 || ball.y >= canvas.height - ball.size) {
557
+ ball.speedY = -ball.speedY;
558
+ createParticles(ball.x, ball.y, 5, ball.color);
559
+ }
560
+
561
+ // Ball collision with player paddle
562
+ if (
563
+ ball.x <= player.x + player.width &&
564
+ ball.x >= player.x &&
565
+ ball.y + ball.size >= player.y &&
566
+ ball.y <= player.y + player.height
567
+ ) {
568
+ // Calculate bounce angle based on where ball hits paddle
569
+ const hitPosition = (ball.y - player.y) / player.height;
570
+ const bounceAngle = maxAngle * (hitPosition - 0.5) * 2;
571
+
572
+ ball.speedX = Math.cos(bounceAngle) * state.ballSpeed;
573
+ ball.speedY = Math.sin(bounceAngle) * state.ballSpeed;
574
+
575
+ // Make the ball faster after each hit (but cap at max speed)
576
+ const speed = Math.sqrt(ball.speedX * ball.speedX + ball.speedY * ball.speedY);
577
+ if (speed < ball.maxSpeed) {
578
+ ball.speedX *= 1.05;
579
+ ball.speedY *= 1.05;
580
+ }
581
+
582
+ createParticles(ball.x, ball.y, 10, player.color);
583
+ }
584
+
585
+ // Ball collision with AI paddle
586
+ if (
587
+ ball.x + ball.size >= ai.x &&
588
+ ball.x <= ai.x + ai.width &&
589
+ ball.y + ball.size >= ai.y &&
590
+ ball.y <= ai.y + ai.height
591
+ ) {
592
+ // Calculate bounce angle based on where ball hits paddle
593
+ const hitPosition = (ball.y - ai.y) / ai.height;
594
+ const bounceAngle = maxAngle * (hitPosition - 0.5) * 2;
595
+
596
+ ball.speedX = -Math.cos(bounceAngle) * state.ballSpeed;
597
+ ball.speedY = Math.sin(bounceAngle) * state.ballSpeed;
598
+
599
+ // Make the ball faster after each hit (but cap at max speed)
600
+ const speed = Math.sqrt(ball.speedX * ball.speedX + ball.speedY * ball.speedY);
601
+ if (speed < ball.maxSpeed) {
602
+ ball.speedX *= 1.05;
603
+ ball.speedY *= 1.05;
604
+ }
605
+
606
+ createParticles(ball.x, ball.y, 10, ai.color);
607
+ }
608
+
609
+ // Score points
610
+ if (ball.x <= 0) {
611
+ // AI scores
612
+ state.aiScore++;
613
+ updateScoreboard();
614
+ scorePoint(false);
615
+ } else if (ball.x >= canvas.width) {
616
+ // Player scores
617
+ state.playerScore++;
618
+ updateScoreboard();
619
+ scorePoint(true);
620
+ }
621
+ }
622
+
623
+ function scorePoint(isPlayer) {
624
+ resetPositions();
625
+ state.gameRunning = false;
626
+
627
+ // Add to history
628
+ const now = new Date();
629
+ const timeString = now.toLocaleTimeString();
630
+ const dateString = now.toLocaleDateString();
631
+
632
+ const newEntry = {
633
+ timestamp: `${dateString} ${timeString}`,
634
+ playerScore: state.playerScore,
635
+ aiScore: state.aiScore,
636
+ winner: state.playerScore > state.aiScore ? 'Player' : 'AI'
637
+ };
638
+
639
+ state.history.unshift(newEntry);
640
+ updateHistoryList();
641
+
642
+ // Check for game over (first to 5 points)
643
+ if (state.playerScore >= 5 || state.aiScore >= 5) {
644
+ state.winner = state.playerScore > state.aiScore ? 'Player' : 'AI';
645
+ gameMessage.textContent = state.playerScore > state.aiScore
646
+ ? 'YOU WIN!'
647
+ : 'AI WINS!';
648
+ gameMessage.textContent += '\n<span style="font-size:1rem">Click Start to Play Again</span>';
649
+ gameMessage.classList.add('show');
650
+ } else {
651
+ // Small delay before serving again
652
+ setTimeout(() => {
653
+ startGame();
654
+ }, 1000);
655
+ }
656
+ }
657
+
658
+ function updateScoreboard() {
659
+ playerScoreDisplay.textContent = state.playerScore;
660
+ aiScoreDisplay.textContent = state.aiScore;
661
+ }
662
+
663
+ function updateHistoryList() {
664
+ historyList.innerHTML = '';
665
+ state.history.slice(0, 10).forEach((match, index) => {
666
+ const item = document.createElement('div');
667
+ item.className = 'history-item';
668
+ item.style.animationDelay = `${index * 0.1}s`;
669
+ item.innerHTML = `
670
+ <strong>${match.timestamp}</strong> -
671
+ Player: ${match.playerScore} | AI: ${match.aiScore} -
672
+ <span style="color: ${match.winner === 'Player' ? '#08f' : '#f0f'}">${match.winner} won</span>
673
+ `;
674
+ historyList.appendChild(item);
675
+ });
676
+ }
677
+
678
+ let lastAiUpdate = 0;
679
+ function updateAI(currentTime) {
680
+ // Only update AI every reactionDelay ms for more "human-like" behavior
681
+ if (currentTime - lastAiUpdate < ai.reactionDelay) return;
682
+ lastAiUpdate = currentTime;
683
+
684
+ // AI prediction with error based on difficulty
685
+ const predictY = ball.y + (ball.speedY * (ai.x - ball.x) / ball.speedX);
686
+ const errorRange = 100 - (state.aiDifficulty * 8);
687
+ const error = (Math.random() - 0.5) * errorRange;
688
+ const targetY = predictY + error;
689
+
690
+ // Move AI paddle towards predicted position
691
+ const paddleCenter = ai.y + ai.height / 2;
692
+ if (paddleCenter < targetY - ai.height / 4) {
693
+ ai.y += ai.speed;
694
+ } else if (paddleCenter > targetY + ai.height / 4) {
695
+ ai.y -= ai.speed;
696
+ }
697
+
698
+ // Keep AI paddle within bounds
699
+ ai.y = Math.max(0, Math.min(canvas.height - ai.height, ai.y));
700
+ }
701
+
702
+ function updatePlayer() {
703
+ if (player.movingUp) {
704
+ player.y -= player.speed;
705
+ } else if (player.movingDown) {
706
+ player.y += player.speed;
707
+ }
708
+
709
+ // Keep player paddle within bounds
710
+ player.y = Math.max(0, Math.min(canvas.height - player.height, player.y));
711
+ }
712
+
713
+ function updateBall() {
714
+ ball.x += ball.speedX;
715
+ ball.y += ball.speedY;
716
+ }
717
+
718
+ const particles = [];
719
+ function createParticles(x, y, count, color) {
720
+ for (let i = 0; i < count; i++) {
721
+ particles.push({
722
+ x,
723
+ y,
724
+ size: Math.random() * 4 + 2,
725
+ speedX: (Math.random() - 0.5) * 6,
726
+ speedY: (Math.random() - 0.5) * 6,
727
+ color,
728
+ life: 30 + Math.random() * 20,
729
+ alpha: 1
730
+ });
731
+ }
732
+ }
733
+
734
+ function updateParticles() {
735
+ for (let i = particles.length - 1; i >= 0; i--) {
736
+ const p = particles[i];
737
+ p.x += p.speedX;
738
+ p.y += p.speedY;
739
+ p.life--;
740
+ p.alpha = p.life / 50;
741
+
742
+ if (p.life <= 0) {
743
+ particles.splice(i, 1);
744
+ }
745
+ }
746
+ }
747
+
748
+ function draw() {
749
+ // Clear canvas
750
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
751
+
752
+ // Draw background grid
753
+ ctx.strokeStyle = 'rgba(144, 0, 255, 0.05)';
754
+ ctx.lineWidth = 1;
755
+
756
+ // Vertical grid
757
+ for (let x = borderOffset; x < canvas.width; x += 40) {
758
+ ctx.beginPath();
759
+ ctx.moveTo(x, 0);
760
+ ctx.lineTo(x, canvas.height);
761
+ ctx.stroke();
762
+ }
763
+
764
+ // Horizontal grid
765
+ for (let y = 0; y < canvas.height; y += 40) {
766
+ ctx.beginPath();
767
+ ctx.moveTo(0, y);
768
+ ctx.lineTo(canvas.width, y);
769
+ ctx.stroke();
770
+ }
771
+
772
+ // Draw paddles with glow effect
773
+ // Player paddle
774
+ ctx.shadowColor = player.color;
775
+ ctx.shadowBlur = 15;
776
+ ctx.fillStyle = player.color;
777
+ ctx.fillRect(player.x, player.y, player.width, player.height);
778
+
779
+ // AI paddle
780
+ ctx.shadowColor = ai.color;
781
+ ctx.fillStyle = ai.color;
782
+ ctx.fillRect(ai.x, ai.y, ai.width, ai.height);
783
+
784
+ // Reset shadow
785
+ ctx.shadowBlur = 0;
786
+
787
+ // Draw ball with glow effect
788
+ ctx.shadowColor = ball.color;
789
+ ctx.shadowBlur = 15;
790
+ ctx.fillStyle = ball.color;
791
+ ctx.beginPath();
792
+ ctx.arc(ball.x + ball.size/2, ball.y + ball.size/2, ball.size/2, 0, Math.PI * 2);
793
+ ctx.fill();
794
+ ctx.shadowBlur = 0;
795
+
796
+ // Draw trail behind ball
797
+ ctx.strokeStyle = `rgba(255, 255, 255, 0.3)`;
798
+ ctx.lineWidth = 2;
799
+ ctx.beginPath();
800
+ ctx.moveTo(ball.x + ball.size/2, ball.y + ball.size/2);
801
+ ctx.lineTo(
802
+ ball.x + ball.size/2 - ball.speedX * 2,
803
+ ball.y + ball.size/2 - ball.speedY * 2
804
+ );
805
+ ctx.stroke();
806
+
807
+ // Draw particles
808
+ particles.forEach(p => {
809
+ ctx.fillStyle = `${p.color.replace(')', `, ${p.alpha})`).replace('rgb', 'rgba')}`;
810
+ ctx.beginPath();
811
+ ctx.arc(p.x, p.y, p.size, 0, Math.PI * 2);
812
+ ctx.fill();
813
+ });
814
+ }
815
+
816
+ let animationId;
817
+ function animate(timestamp = 0) {
818
+ if (state.gamePaused || !state.gameRunning) return;
819
+
820
+ // Update game state
821
+ updatePlayer();
822
+ updateAI(timestamp);
823
+ updateBall();
824
+ updateParticles();
825
+ checkCollision();
826
+
827
+ // Draw everything
828
+ draw();
829
+
830
+ animationId = requestAnimationFrame(animate);
831
+ }
832
+
833
+ // Initialize controls
834
+ updateBallSpeed();
835
+ updateAiDifficulty();
836
+ });
837
+ </script>
838
+ </body>
839
  </html>