af2022 commited on
Commit
4557d76
·
verified ·
1 Parent(s): a076768

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +848 -19
index.html CHANGED
@@ -1,19 +1,848 @@
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>Classic Pong Game</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/peerjs.min.js"></script>
9
+ <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
10
+ <style>
11
+ @keyframes pulse {
12
+ 0%, 100% { opacity: 1; }
13
+ 50% { opacity: 0.5; }
14
+ }
15
+ .pulse-animation {
16
+ animation: pulse 1.5s infinite;
17
+ }
18
+ #gameCanvas {
19
+ background-color: #111827;
20
+ border-radius: 8px;
21
+ box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
22
+ }
23
+ .glow {
24
+ box-shadow: 0 0 10px rgba(59, 130, 246, 0.7);
25
+ }
26
+ .score-display {
27
+ font-family: 'Courier New', monospace;
28
+ text-shadow: 0 0 5px rgba(59, 130, 246, 0.7);
29
+ }
30
+ </style>
31
+ </head>
32
+ <body class="bg-gray-900 text-white min-h-screen flex flex-col items-center justify-center p-4">
33
+ <div class="max-w-4xl w-full">
34
+ <h1 class="text-4xl font-bold text-center mb-2 text-blue-400">Classic Pong</h1>
35
+ <p class="text-center text-gray-400 mb-8">Relive the arcade classic with modern multiplayer</p>
36
+
37
+ <div id="menu" class="flex flex-col items-center space-y-6 mb-8">
38
+ <button id="singlePlayerBtn" class="bg-blue-600 hover:bg-blue-700 text-white font-bold py-3 px-6 rounded-lg transition-all duration-300 transform hover:scale-105 w-64 flex items-center justify-center space-x-2">
39
+ <i class="fas fa-robot"></i>
40
+ <span>Play vs Computer</span>
41
+ </button>
42
+
43
+ <button id="multiplayerBtn" class="bg-purple-600 hover:bg-purple-700 text-white font-bold py-3 px-6 rounded-lg transition-all duration-300 transform hover:scale-105 w-64 flex items-center justify-center space-x-2">
44
+ <i class="fas fa-users"></i>
45
+ <span>Multiplayer</span>
46
+ </button>
47
+
48
+ <div id="multiplayerControls" class="hidden flex-col items-center space-y-4 w-full max-w-md">
49
+ <div class="flex space-x-4 w-full">
50
+ <button id="createRoomBtn" class="bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-lg transition-all duration-300 flex-1 flex items-center justify-center space-x-2">
51
+ <i class="fas fa-plus"></i>
52
+ <span>Create Room</span>
53
+ </button>
54
+ <button id="joinRoomBtn" class="bg-yellow-600 hover:bg-yellow-700 text-white font-bold py-2 px-4 rounded-lg transition-all duration-300 flex-1 flex items-center justify-center space-x-2">
55
+ <i class="fas fa-sign-in-alt"></i>
56
+ <span>Join Room</span>
57
+ </button>
58
+ </div>
59
+
60
+ <div id="roomControls" class="hidden w-full space-y-4">
61
+ <div id="createRoomSection" class="hidden">
62
+ <div class="bg-gray-800 p-4 rounded-lg">
63
+ <p class="text-sm text-gray-400 mb-2">Share this ID with your friend:</p>
64
+ <div class="flex items-center space-x-2">
65
+ <input id="hostPeerId" type="text" readonly class="bg-gray-700 text-white p-2 rounded flex-1 font-mono">
66
+ <button id="copyHostIdBtn" class="bg-blue-600 hover:bg-blue-700 text-white p-2 rounded">
67
+ <i class="fas fa-copy"></i>
68
+ </button>
69
+ </div>
70
+ <p class="text-xs text-gray-500 mt-2">Waiting for player to join...</p>
71
+ </div>
72
+ </div>
73
+
74
+ <div id="joinRoomSection" class="hidden">
75
+ <div class="bg-gray-800 p-4 rounded-lg">
76
+ <p class="text-sm text-gray-400 mb-2">Enter host's ID:</p>
77
+ <div class="flex items-center space-x-2">
78
+ <input id="guestPeerId" type="text" class="bg-gray-700 text-white p-2 rounded flex-1 font-mono" placeholder="Enter host ID">
79
+ <button id="connectBtn" class="bg-green-600 hover:bg-green-700 text-white p-2 rounded">
80
+ <i class="fas fa-plug"></i> Connect
81
+ </button>
82
+ </div>
83
+ </div>
84
+ </div>
85
+
86
+ <div id="connectionStatus" class="hidden bg-gray-800 p-4 rounded-lg text-center">
87
+ <p class="text-yellow-400 pulse-animation">
88
+ <i class="fas fa-circle-notch fa-spin"></i> Connecting...
89
+ </p>
90
+ </div>
91
+
92
+ <div id="connectedStatus" class="hidden bg-gray-800 p-4 rounded-lg text-center">
93
+ <p class="text-green-400">
94
+ <i class="fas fa-check-circle"></i> Connected!
95
+ </p>
96
+ </div>
97
+ </div>
98
+ </div>
99
+ </div>
100
+
101
+ <div id="gameContainer" class="hidden flex flex-col items-center">
102
+ <div class="flex justify-between w-full mb-4">
103
+ <div class="score-display text-2xl">Player: <span id="playerScore">0</span></div>
104
+ <div class="score-display text-2xl">Opponent: <span id="opponentScore">0</span></div>
105
+ </div>
106
+
107
+ <canvas id="gameCanvas" width="800" height="500" class="w-full max-w-full"></canvas>
108
+
109
+ <div id="gameControls" class="mt-4 flex space-x-4">
110
+ <button id="pauseBtn" class="bg-yellow-600 hover:bg-yellow-700 text-white font-bold py-2 px-4 rounded-lg">
111
+ <i class="fas fa-pause"></i> Pause
112
+ </button>
113
+ <button id="restartBtn" class="bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded-lg">
114
+ <i class="fas fa-redo"></i> Restart
115
+ </button>
116
+ <button id="backToMenuBtn" class="bg-gray-600 hover:bg-gray-700 text-white font-bold py-2 px-4 rounded-lg">
117
+ <i class="fas fa-arrow-left"></i> Menu
118
+ </button>
119
+ </div>
120
+
121
+ <div id="gameMessage" class="mt-4 text-xl font-bold text-center hidden"></div>
122
+ </div>
123
+ </div>
124
+
125
+ <script>
126
+ // Game elements
127
+ const canvas = document.getElementById('gameCanvas');
128
+ const ctx = canvas.getContext('2d');
129
+ const playerScoreDisplay = document.getElementById('playerScore');
130
+ const opponentScoreDisplay = document.getElementById('opponentScore');
131
+ const gameMessage = document.getElementById('gameMessage');
132
+
133
+ // Menu elements
134
+ const menu = document.getElementById('menu');
135
+ const gameContainer = document.getElementById('gameContainer');
136
+ const singlePlayerBtn = document.getElementById('singlePlayerBtn');
137
+ const multiplayerBtn = document.getElementById('multiplayerBtn');
138
+ const multiplayerControls = document.getElementById('multiplayerControls');
139
+ const roomControls = document.getElementById('roomControls');
140
+ const createRoomBtn = document.getElementById('createRoomBtn');
141
+ const joinRoomBtn = document.getElementById('joinRoomBtn');
142
+ const createRoomSection = document.getElementById('createRoomSection');
143
+ const joinRoomSection = document.getElementById('joinRoomSection');
144
+ const hostPeerId = document.getElementById('hostPeerId');
145
+ const copyHostIdBtn = document.getElementById('copyHostIdBtn');
146
+ const guestPeerId = document.getElementById('guestPeerId');
147
+ const connectBtn = document.getElementById('connectBtn');
148
+ const connectionStatus = document.getElementById('connectionStatus');
149
+ const connectedStatus = document.getElementById('connectedStatus');
150
+ const pauseBtn = document.getElementById('pauseBtn');
151
+ const restartBtn = document.getElementById('restartBtn');
152
+ const backToMenuBtn = document.getElementById('backToMenuBtn');
153
+
154
+ // Game variables
155
+ let gameMode = null; // 'single' or 'multiplayer'
156
+ let isHost = false;
157
+ let isPaused = false;
158
+ let gameRunning = false;
159
+ let animationId;
160
+ let resetInProgress = false; // Flag to prevent reset loops
161
+ let paddleUpdateInterval;
162
+
163
+ // Pong game objects
164
+ const paddleWidth = 15;
165
+ const paddleHeight = 100;
166
+ const ballSize = 10;
167
+ const paddleSpeed = 8;
168
+ const initialBallSpeed = 5;
169
+ const paddleUpdateFrequency = 50; // ms between paddle position updates
170
+
171
+ let leftPaddle = {
172
+ x: 30,
173
+ y: canvas.height / 2 - paddleHeight / 2,
174
+ width: paddleWidth,
175
+ height: paddleHeight,
176
+ dy: 0,
177
+ score: 0
178
+ };
179
+
180
+ let rightPaddle = {
181
+ x: canvas.width - 30 - paddleWidth,
182
+ y: canvas.height / 2 - paddleHeight / 2,
183
+ width: paddleWidth,
184
+ height: paddleHeight,
185
+ dy: 0,
186
+ score: 0
187
+ };
188
+
189
+ let ball = {
190
+ x: canvas.width / 2,
191
+ y: canvas.height / 2,
192
+ size: ballSize,
193
+ dx: initialBallSpeed,
194
+ dy: initialBallSpeed
195
+ };
196
+
197
+ // PeerJS variables
198
+ let peer;
199
+ let conn;
200
+ let myPeerId;
201
+ let connectionTimeout;
202
+
203
+ // Event listeners
204
+ singlePlayerBtn.addEventListener('click', () => {
205
+ gameMode = 'single';
206
+ startGame();
207
+ });
208
+
209
+ multiplayerBtn.addEventListener('click', () => {
210
+ multiplayerControls.classList.remove('hidden');
211
+ multiplayerBtn.classList.add('hidden');
212
+ singlePlayerBtn.classList.add('hidden');
213
+ });
214
+
215
+ createRoomBtn.addEventListener('click', () => {
216
+ isHost = true;
217
+ setupPeerConnection();
218
+ createRoomSection.classList.remove('hidden');
219
+ joinRoomSection.classList.add('hidden');
220
+ roomControls.classList.remove('hidden');
221
+ });
222
+
223
+ joinRoomBtn.addEventListener('click', () => {
224
+ isHost = false;
225
+ setupPeerConnection();
226
+ joinRoomSection.classList.remove('hidden');
227
+ createRoomSection.classList.add('hidden');
228
+ roomControls.classList.remove('hidden');
229
+ });
230
+
231
+ copyHostIdBtn.addEventListener('click', () => {
232
+ hostPeerId.select();
233
+ document.execCommand('copy');
234
+ showMessage('Copied to clipboard!', 'green-400');
235
+ });
236
+
237
+ connectBtn.addEventListener('click', () => {
238
+ const hostId = guestPeerId.value.trim();
239
+ if (!hostId) {
240
+ showMessage('Please enter a valid host ID', 'red-400');
241
+ return;
242
+ }
243
+
244
+ if (!peer) {
245
+ showMessage('Peer connection not ready yet', 'red-400');
246
+ return;
247
+ }
248
+
249
+ connectionStatus.classList.remove('hidden');
250
+
251
+ // Set timeout for connection attempt
252
+ connectionTimeout = setTimeout(() => {
253
+ if (connectionStatus && !connectionStatus.classList.contains('hidden')) {
254
+ showMessage('Connection timed out. Try again.', 'red-400');
255
+ connectionStatus.classList.add('hidden');
256
+ if (conn) conn.close();
257
+ }
258
+ }, 15000); // 15 seconds timeout
259
+
260
+ conn = peer.connect(hostId, {
261
+ reliable: true,
262
+ serialization: 'json',
263
+ metadata: {
264
+ game: 'pong',
265
+ version: '1.0'
266
+ }
267
+ });
268
+
269
+ conn.on('open', () => {
270
+ clearTimeout(connectionTimeout);
271
+ connectionStatus.classList.add('hidden');
272
+ connectedStatus.classList.remove('hidden');
273
+ gameMode = 'multiplayer';
274
+
275
+ // Set up data handler
276
+ conn.on('data', handleData);
277
+
278
+ // Handle connection close
279
+ conn.on('close', () => {
280
+ if (gameRunning) {
281
+ showMessage('Player disconnected', 'red-400');
282
+ returnToMenu();
283
+ }
284
+ });
285
+
286
+ // Start sending paddle updates
287
+ if (!isHost) {
288
+ startPaddleUpdates(rightPaddle);
289
+ } else {
290
+ startPaddleUpdates(leftPaddle);
291
+ }
292
+
293
+ startGame();
294
+ });
295
+
296
+ conn.on('error', (err) => {
297
+ clearTimeout(connectionTimeout);
298
+ console.error('Connection error:', err);
299
+ showMessage('Connection failed: ' + (err.message || 'Unknown error'), 'red-400');
300
+ connectionStatus.classList.add('hidden');
301
+ if (conn) conn.close();
302
+ });
303
+ });
304
+
305
+ pauseBtn.addEventListener('click', togglePause);
306
+ restartBtn.addEventListener('click', resetGame);
307
+ backToMenuBtn.addEventListener('click', returnToMenu);
308
+
309
+ // Keyboard controls
310
+ const keys = {};
311
+
312
+ document.addEventListener('keydown', (e) => {
313
+ keys[e.key] = true;
314
+
315
+ // Prevent default for arrow keys and space to avoid page scrolling
316
+ if (['ArrowUp', 'ArrowDown', ' '].includes(e.key)) {
317
+ e.preventDefault();
318
+ }
319
+ });
320
+
321
+ document.addEventListener('keyup', (e) => {
322
+ keys[e.key] = false;
323
+ });
324
+
325
+ // Game functions
326
+ function startGame() {
327
+ menu.classList.add('hidden');
328
+ gameContainer.classList.remove('hidden');
329
+ gameRunning = true;
330
+ resetGame();
331
+ gameLoop();
332
+
333
+ // Focus canvas for keyboard controls
334
+ canvas.focus();
335
+ }
336
+
337
+ function gameLoop() {
338
+ if (isPaused || !gameRunning) return;
339
+
340
+ update();
341
+ draw();
342
+
343
+ animationId = requestAnimationFrame(gameLoop);
344
+ }
345
+
346
+ function update() {
347
+ // Update paddles based on keyboard input
348
+ if (gameMode === 'single') {
349
+ // Player controls (left paddle)
350
+ if (keys['w'] || keys['ArrowUp']) {
351
+ leftPaddle.dy = -paddleSpeed;
352
+ } else if (keys['s'] || keys['ArrowDown']) {
353
+ leftPaddle.dy = paddleSpeed;
354
+ } else {
355
+ leftPaddle.dy = 0;
356
+ }
357
+
358
+ // Simple AI for right paddle
359
+ const paddleCenter = rightPaddle.y + rightPaddle.height / 2;
360
+ const ballCenter = ball.y + ball.size / 2;
361
+
362
+ if (paddleCenter < ballCenter - 10) {
363
+ rightPaddle.dy = paddleSpeed * 0.7; // Slightly slower than player
364
+ } else if (paddleCenter > ballCenter + 10) {
365
+ rightPaddle.dy = -paddleSpeed * 0.7;
366
+ } else {
367
+ rightPaddle.dy = 0;
368
+ }
369
+ } else {
370
+ // Multiplayer controls
371
+ if (isHost) {
372
+ // Host controls left paddle
373
+ if (keys['w'] || keys['ArrowUp']) {
374
+ leftPaddle.dy = -paddleSpeed;
375
+ } else if (keys['s'] || keys['ArrowDown']) {
376
+ leftPaddle.dy = paddleSpeed;
377
+ } else {
378
+ leftPaddle.dy = 0;
379
+ }
380
+ } else {
381
+ // Guest controls right paddle
382
+ if (keys['ArrowUp']) {
383
+ rightPaddle.dy = -paddleSpeed;
384
+ } else if (keys['ArrowDown']) {
385
+ rightPaddle.dy = paddleSpeed;
386
+ } else {
387
+ rightPaddle.dy = 0;
388
+ }
389
+ }
390
+ }
391
+
392
+ // Move paddles
393
+ leftPaddle.y += leftPaddle.dy;
394
+ rightPaddle.y += rightPaddle.dy;
395
+
396
+ // Paddle boundaries
397
+ if (leftPaddle.y < 0) leftPaddle.y = 0;
398
+ if (leftPaddle.y + leftPaddle.height > canvas.height) leftPaddle.y = canvas.height - leftPaddle.height;
399
+
400
+ if (rightPaddle.y < 0) rightPaddle.y = 0;
401
+ if (rightPaddle.y + rightPaddle.height > canvas.height) rightPaddle.y = canvas.height - rightPaddle.height;
402
+
403
+ // Move ball
404
+ ball.x += ball.dx;
405
+ ball.y += ball.dy;
406
+
407
+ // Ball collision with top and bottom
408
+ if (ball.y - ball.size / 2 < 0 || ball.y + ball.size / 2 > canvas.height) {
409
+ ball.dy = -ball.dy;
410
+ }
411
+
412
+ // Ball collision with paddles
413
+ if (
414
+ ball.x - ball.size / 2 < leftPaddle.x + leftPaddle.width &&
415
+ ball.x + ball.size / 2 > leftPaddle.x &&
416
+ ball.y + ball.size / 2 > leftPaddle.y &&
417
+ ball.y - ball.size / 2 < leftPaddle.y + leftPaddle.height
418
+ ) {
419
+ const hitPosition = (ball.y - (leftPaddle.y + leftPaddle.height / 2)) / (leftPaddle.height / 2);
420
+ ball.dx = Math.abs(ball.dx) * 1.05; // Increase speed slightly
421
+ ball.dy = hitPosition * 5; // Change angle based on where ball hits paddle
422
+ ball.x = leftPaddle.x + leftPaddle.width + ball.size / 2;
423
+
424
+ if (gameMode === 'multiplayer' && isHost) {
425
+ sendBallUpdate();
426
+ }
427
+ }
428
+
429
+ if (
430
+ ball.x + ball.size / 2 > rightPaddle.x &&
431
+ ball.x - ball.size / 2 < rightPaddle.x + rightPaddle.width &&
432
+ ball.y + ball.size / 2 > rightPaddle.y &&
433
+ ball.y - ball.size / 2 < rightPaddle.y + rightPaddle.height
434
+ ) {
435
+ const hitPosition = (ball.y - (rightPaddle.y + rightPaddle.height / 2)) / (rightPaddle.height / 2);
436
+ ball.dx = -Math.abs(ball.dx) * 1.05;
437
+ ball.dy = hitPosition * 5;
438
+ ball.x = rightPaddle.x - ball.size / 2;
439
+
440
+ if (gameMode === 'multiplayer' && !isHost) {
441
+ // Guest doesn't send ball updates - only host does
442
+ }
443
+ }
444
+
445
+ // Ball out of bounds (score)
446
+ if (ball.x - ball.size / 2 < 0) {
447
+ rightPaddle.score++;
448
+ opponentScoreDisplay.textContent = rightPaddle.score;
449
+ resetBall();
450
+
451
+ if (gameMode === 'multiplayer' && isHost) {
452
+ sendScoreUpdate();
453
+ }
454
+
455
+ if (rightPaddle.score >= 5) {
456
+ endGame(isHost ? 'You lost!' : 'You won!');
457
+ }
458
+ }
459
+
460
+ if (ball.x + ball.size / 2 > canvas.width) {
461
+ leftPaddle.score++;
462
+ playerScoreDisplay.textContent = leftPaddle.score;
463
+ resetBall();
464
+
465
+ if (gameMode === 'multiplayer' && !isHost) {
466
+ sendScoreUpdate();
467
+ }
468
+
469
+ if (leftPaddle.score >= 5) {
470
+ endGame(isHost ? 'You won!' : 'You lost!');
471
+ }
472
+ }
473
+ }
474
+
475
+ function draw() {
476
+ // Clear canvas
477
+ ctx.clearRect(0, 0, canvas.width, canvas.height);
478
+
479
+ // Draw center line
480
+ ctx.strokeStyle = 'rgba(255, 255, 255, 0.2)';
481
+ ctx.setLineDash([10, 10]);
482
+ ctx.beginPath();
483
+ ctx.moveTo(canvas.width / 2, 0);
484
+ ctx.lineTo(canvas.width / 2, canvas.height);
485
+ ctx.stroke();
486
+ ctx.setLineDash([]);
487
+
488
+ // Draw paddles
489
+ ctx.fillStyle = '#3B82F6';
490
+ ctx.fillRect(leftPaddle.x, leftPaddle.y, leftPaddle.width, leftPaddle.height);
491
+
492
+ ctx.fillStyle = '#EC4899';
493
+ ctx.fillRect(rightPaddle.x, rightPaddle.y, rightPaddle.width, rightPaddle.height);
494
+
495
+ // Draw ball
496
+ ctx.fillStyle = '#FFFFFF';
497
+ ctx.beginPath();
498
+ ctx.arc(ball.x, ball.y, ball.size / 2, 0, Math.PI * 2);
499
+ ctx.fill();
500
+ }
501
+
502
+ function resetBall() {
503
+ ball.x = canvas.width / 2;
504
+ ball.y = canvas.height / 2;
505
+
506
+ // Random direction but always towards the scoring player
507
+ const direction = Math.random() > 0.5 ? 1 : -1;
508
+ ball.dx = initialBallSpeed * direction;
509
+ ball.dy = (Math.random() * 4 - 2); // Random angle between -2 and 2
510
+ }
511
+
512
+ function resetGame() {
513
+ if (resetInProgress) return;
514
+ resetInProgress = true;
515
+
516
+ leftPaddle.y = canvas.height / 2 - paddleHeight / 2;
517
+ rightPaddle.y = canvas.height / 2 - paddleHeight / 2;
518
+ leftPaddle.score = 0;
519
+ rightPaddle.score = 0;
520
+ playerScoreDisplay.textContent = '0';
521
+ opponentScoreDisplay.textContent = '0';
522
+ resetBall();
523
+
524
+ // In multiplayer, only host can send reset command
525
+ if (gameMode === 'multiplayer' && isHost) {
526
+ sendGameState('reset');
527
+ }
528
+
529
+ gameMessage.classList.add('hidden');
530
+ isPaused = false;
531
+ pauseBtn.innerHTML = '<i class="fas fa-pause"></i> Pause';
532
+
533
+ // Reset the flag after a small delay
534
+ setTimeout(() => {
535
+ resetInProgress = false;
536
+ }, 100);
537
+ }
538
+
539
+ function endGame(message) {
540
+ gameRunning = false;
541
+ cancelAnimationFrame(animationId);
542
+ clearInterval(paddleUpdateInterval);
543
+
544
+ gameMessage.textContent = message;
545
+ gameMessage.classList.remove('hidden');
546
+ gameMessage.className = 'mt-4 text-xl font-bold text-center';
547
+
548
+ if (message.includes('won')) {
549
+ gameMessage.classList.add('text-green-400');
550
+ } else {
551
+ gameMessage.classList.add('text-red-400');
552
+ }
553
+
554
+ if (gameMode === 'multiplayer') {
555
+ sendGameState('end', message);
556
+ }
557
+ }
558
+
559
+ function togglePause() {
560
+ isPaused = !isPaused;
561
+
562
+ if (isPaused) {
563
+ cancelAnimationFrame(animationId);
564
+ clearInterval(paddleUpdateInterval);
565
+ pauseBtn.innerHTML = '<i class="fas fa-play"></i> Resume';
566
+ gameMessage.textContent = 'Game Paused';
567
+ gameMessage.classList.remove('hidden');
568
+ gameMessage.className = 'mt-4 text-xl font-bold text-center text-yellow-400';
569
+ if (gameMode === 'multiplayer') {
570
+ sendGameState('pause');
571
+ }
572
+ } else {
573
+ pauseBtn.innerHTML = '<i class="fas fa-pause"></i> Pause';
574
+ gameMessage.classList.add('hidden');
575
+ if (gameMode === 'multiplayer') {
576
+ // Restart paddle updates
577
+ if (isHost) {
578
+ startPaddleUpdates(leftPaddle);
579
+ } else {
580
+ startPaddleUpdates(rightPaddle);
581
+ }
582
+ sendGameState('resume');
583
+ }
584
+ gameLoop();
585
+ }
586
+ }
587
+
588
+ function startPaddleUpdates(paddle) {
589
+ // Clear any existing interval
590
+ clearInterval(paddleUpdateInterval);
591
+
592
+ // Start sending paddle position updates at regular intervals
593
+ paddleUpdateInterval = setInterval(() => {
594
+ if (gameRunning && !isPaused && conn && conn.open) {
595
+ sendPaddlePosition(paddle.y);
596
+ }
597
+ }, paddleUpdateFrequency);
598
+ }
599
+
600
+ function returnToMenu() {
601
+ // Clean up PeerJS connection
602
+ if (conn) {
603
+ conn.close();
604
+ }
605
+ if (peer) {
606
+ peer.destroy();
607
+ }
608
+ clearTimeout(connectionTimeout);
609
+ clearInterval(paddleUpdateInterval);
610
+
611
+ // Reset game state
612
+ cancelAnimationFrame(animationId);
613
+ gameRunning = false;
614
+ resetInProgress = false;
615
+
616
+ // Show menu and hide game
617
+ menu.classList.remove('hidden');
618
+ gameContainer.classList.add('hidden');
619
+ multiplayerControls.classList.add('hidden');
620
+ roomControls.classList.add('hidden');
621
+ connectionStatus.classList.add('hidden');
622
+ connectedStatus.classList.add('hidden');
623
+ multiplayerBtn.classList.remove('hidden');
624
+ singlePlayerBtn.classList.remove('hidden');
625
+
626
+ // Reset multiplayer UI
627
+ createRoomSection.classList.add('hidden');
628
+ joinRoomSection.classList.add('hidden');
629
+ }
630
+
631
+ function showMessage(message, colorClass) {
632
+ const messageDiv = document.createElement('div');
633
+ messageDiv.className = `fixed top-4 left-1/2 transform -translate-x-1/2 bg-gray-800 text-${colorClass} px-4 py-2 rounded-lg shadow-lg z-50`;
634
+ messageDiv.textContent = message;
635
+ document.body.appendChild(messageDiv);
636
+
637
+ setTimeout(() => {
638
+ messageDiv.remove();
639
+ }, 3000);
640
+ }
641
+
642
+ // PeerJS functions
643
+ function setupPeerConnection() {
644
+ // Destroy previous peer instance if exists
645
+ if (peer) {
646
+ peer.destroy();
647
+ }
648
+
649
+ // Create PeerJS instance with TURN servers for better connectivity
650
+ peer = new Peer({
651
+ config: {
652
+ iceServers: [
653
+ { urls: 'stun:stun.l.google.com:19302' },
654
+ { urls: 'stun:global.stun.twilio.com:3478?transport=udp' },
655
+ {
656
+ urls: 'turn:numb.viagenie.ca',
657
+ username: '[email protected]',
658
+ credential: 'muazkh'
659
+ },
660
+ {
661
+ urls: 'turn:turn.bistri.com:80',
662
+ username: 'homeo',
663
+ credential: 'homeo'
664
+ }
665
+ ]
666
+ },
667
+ debug: 3 // Enable debug logging
668
+ });
669
+
670
+ peer.on('open', (id) => {
671
+ console.log('My peer ID is: ' + id);
672
+ myPeerId = id;
673
+ if (isHost) {
674
+ hostPeerId.value = id;
675
+
676
+ // Listen for incoming connections
677
+ peer.on('connection', (connection) => {
678
+ console.log('Incoming connection from:', connection.peer);
679
+ conn = connection;
680
+
681
+ conn.on('open', () => {
682
+ console.log('Connection established with:', conn.peer);
683
+ clearTimeout(connectionTimeout);
684
+ connectionStatus.classList.add('hidden');
685
+ connectedStatus.classList.remove('hidden');
686
+
687
+ // Set up data handler
688
+ conn.on('data', handleData);
689
+
690
+ // Handle connection close
691
+ conn.on('close', () => {
692
+ console.log('Connection closed');
693
+ if (gameRunning) {
694
+ showMessage('Player disconnected', 'red-400');
695
+ returnToMenu();
696
+ }
697
+ });
698
+
699
+ gameMode = 'multiplayer';
700
+ startGame();
701
+
702
+ // Host starts sending paddle updates
703
+ startPaddleUpdates(leftPaddle);
704
+ });
705
+
706
+ conn.on('error', (err) => {
707
+ console.error('Connection error:', err);
708
+ showMessage('Connection error: ' + (err.message || 'Unknown error'), 'red-400');
709
+ });
710
+ });
711
+ }
712
+
713
+ // Reset connection status if we're joining
714
+ if (!isHost) {
715
+ connectionStatus.classList.add('hidden');
716
+ connectedStatus.classList.add('hidden');
717
+ }
718
+ });
719
+
720
+ peer.on('error', (err) => {
721
+ console.error('PeerJS error:', err);
722
+ showMessage('Connection error: ' + (err.message || 'Unknown error'), 'red-400');
723
+ connectionStatus.classList.add('hidden');
724
+ if (conn) conn.close();
725
+ if (isHost) {
726
+ returnToMenu();
727
+ }
728
+ });
729
+ }
730
+
731
+ function handleData(data) {
732
+ console.log('Received data:', data);
733
+ if (data.type === 'paddle') {
734
+ if (isHost) {
735
+ // Host receives guest's paddle position (right paddle)
736
+ rightPaddle.y = data.y;
737
+ } else {
738
+ // Guest receives host's paddle position (left paddle)
739
+ leftPaddle.y = data.y;
740
+ }
741
+ } else if (data.type === 'ball') {
742
+ if (!isHost) { // Only guests should update ball from host
743
+ ball.x = data.x;
744
+ ball.y = data.y;
745
+ ball.dx = data.dx;
746
+ ball.dy = data.dy;
747
+ }
748
+ } else if (data.type === 'score') {
749
+ if (isHost) {
750
+ leftPaddle.score = data.playerScore;
751
+ playerScoreDisplay.textContent = data.playerScore;
752
+ } else {
753
+ rightPaddle.score = data.opponentScore;
754
+ opponentScoreDisplay.textContent = data.opponentScore;
755
+ }
756
+ } else if (data.type === 'gameState') {
757
+ if (data.state === 'reset' && !isHost) {
758
+ // Only guests should respond to reset commands
759
+ leftPaddle.y = canvas.height / 2 - paddleHeight / 2;
760
+ rightPaddle.y = canvas.height / 2 - paddleHeight / 2;
761
+ leftPaddle.score = 0;
762
+ rightPaddle.score = 0;
763
+ playerScoreDisplay.textContent = '0';
764
+ opponentScoreDisplay.textContent = '0';
765
+ resetBall();
766
+
767
+ gameMessage.textContent = 'Game reset by host';
768
+ gameMessage.classList.remove('hidden');
769
+ gameMessage.className = 'mt-4 text-xl font-bold text-center text-yellow-400';
770
+ setTimeout(() => {
771
+ gameMessage.classList.add('hidden');
772
+ }, 2000);
773
+ } else if (data.state === 'pause') {
774
+ isPaused = true;
775
+ cancelAnimationFrame(animationId);
776
+ clearInterval(paddleUpdateInterval);
777
+ pauseBtn.innerHTML = '<i class="fas fa-play"></i> Resume';
778
+ gameMessage.textContent = 'Game Paused by ' + (isHost ? 'you' : 'host');
779
+ gameMessage.classList.remove('hidden');
780
+ gameMessage.className = 'mt-4 text-xl font-bold text-center text-yellow-400';
781
+ } else if (data.state === 'resume') {
782
+ isPaused = false;
783
+ pauseBtn.innerHTML = '<i class="fas fa-pause"></i> Pause';
784
+ gameMessage.classList.add('hidden');
785
+
786
+ // Restart paddle updates if we're a guest
787
+ if (!isHost) {
788
+ startPaddleUpdates(rightPaddle);
789
+ }
790
+
791
+ gameLoop();
792
+ } else if (data.state === 'end') {
793
+ endGame(data.message);
794
+ }
795
+ }
796
+ }
797
+
798
+ function sendPaddlePosition(y) {
799
+ if (conn && conn.open) {
800
+ const data = {
801
+ type: 'paddle',
802
+ y: y
803
+ };
804
+ console.log('Sending paddle position:', data);
805
+ conn.send(data);
806
+ }
807
+ }
808
+
809
+ function sendBallUpdate() {
810
+ if (conn && conn.open && isHost) { // Only host should send ball updates
811
+ const data = {
812
+ type: 'ball',
813
+ x: ball.x,
814
+ y: ball.y,
815
+ dx: ball.dx,
816
+ dy: ball.dy
817
+ };
818
+ console.log('Sending ball update:', data);
819
+ conn.send(data);
820
+ }
821
+ }
822
+
823
+ function sendScoreUpdate() {
824
+ if (conn && conn.open) {
825
+ const data = {
826
+ type: 'score',
827
+ playerScore: leftPaddle.score,
828
+ opponentScore: rightPaddle.score
829
+ };
830
+ console.log('Sending score update:', data);
831
+ conn.send(data);
832
+ }
833
+ }
834
+
835
+ function sendGameState(state, message = '') {
836
+ if (conn && conn.open) {
837
+ const data = {
838
+ type: 'gameState',
839
+ state: state,
840
+ message: message
841
+ };
842
+ console.log('Sending game state:', data);
843
+ conn.send(data);
844
+ }
845
+ }
846
+ </script>
847
+ </body>
848
+ </html>