smodusermc commited on
Commit
11683d6
·
verified ·
1 Parent(s): e11a464

Create js/game.js

Browse files
Files changed (1) hide show
  1. static/js/game.js +1670 -0
static/js/game.js ADDED
@@ -0,0 +1,1670 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // ========================================
2
+ // Chess Arena - Main Game JavaScript
3
+ // ========================================
4
+
5
+ (function() {
6
+ 'use strict';
7
+
8
+ // ========================================
9
+ // Configuration & Constants
10
+ // ========================================
11
+ const PIECES = {
12
+ 'K': '♔', 'Q': '♕', 'R': '♖', 'B': '♗', 'N': '♘', 'P': '♙',
13
+ 'k': '♚', 'q': '♛', 'r': '♜', 'b': '♝', 'n': '♞', 'p': '♟'
14
+ };
15
+
16
+ const FILES = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
17
+ const RANKS = ['8', '7', '6', '5', '4', '3', '2', '1'];
18
+
19
+ // ========================================
20
+ // Game State
21
+ // ========================================
22
+ let socket = null;
23
+ let chess = null;
24
+ let gameState = {
25
+ roomId: null,
26
+ playerColor: null,
27
+ username: null,
28
+ opponent: null,
29
+ timeControl: null,
30
+ isMyTurn: false,
31
+ gameStarted: false,
32
+ gameOver: false,
33
+ selectedSquare: null,
34
+ legalMoves: [],
35
+ lastMove: null,
36
+ isFlipped: false,
37
+ soundEnabled: true,
38
+ pendingPromotion: null
39
+ };
40
+
41
+ let timers = {
42
+ white: null,
43
+ black: null,
44
+ interval: null
45
+ };
46
+
47
+ let unreadMessages = 0;
48
+
49
+ // ========================================
50
+ // Chess.js Helper Functions (Version Compatible)
51
+ // ========================================
52
+ function chessInCheck() {
53
+ if (typeof chess.inCheck === 'function') {
54
+ return chess.inCheck();
55
+ } else if (typeof chess.in_check === 'function') {
56
+ return chess.in_check();
57
+ } else if (typeof chess.isCheck === 'function') {
58
+ return chess.isCheck();
59
+ }
60
+ // Manual check detection
61
+ return isKingInCheck();
62
+ }
63
+
64
+ function chessIsCheckmate() {
65
+ if (typeof chess.isCheckmate === 'function') {
66
+ return chess.isCheckmate();
67
+ } else if (typeof chess.in_checkmate === 'function') {
68
+ return chess.in_checkmate();
69
+ }
70
+ return false;
71
+ }
72
+
73
+ function chessIsDraw() {
74
+ if (typeof chess.isDraw === 'function') {
75
+ return chess.isDraw();
76
+ } else if (typeof chess.in_draw === 'function') {
77
+ return chess.in_draw();
78
+ }
79
+ return false;
80
+ }
81
+
82
+ function chessIsStalemate() {
83
+ if (typeof chess.isStalemate === 'function') {
84
+ return chess.isStalemate();
85
+ } else if (typeof chess.in_stalemate === 'function') {
86
+ return chess.in_stalemate();
87
+ }
88
+ return false;
89
+ }
90
+
91
+ function chessIsGameOver() {
92
+ if (typeof chess.isGameOver === 'function') {
93
+ return chess.isGameOver();
94
+ } else if (typeof chess.game_over === 'function') {
95
+ return chess.game_over();
96
+ }
97
+ return chessIsCheckmate() || chessIsDraw() || chessIsStalemate();
98
+ }
99
+
100
+ function chessTurn() {
101
+ if (typeof chess.turn === 'function') {
102
+ return chess.turn();
103
+ }
104
+ return chess.turn;
105
+ }
106
+
107
+ function chessFen() {
108
+ if (typeof chess.fen === 'function') {
109
+ return chess.fen();
110
+ }
111
+ return chess.fen;
112
+ }
113
+
114
+ function chessPgn() {
115
+ if (typeof chess.pgn === 'function') {
116
+ return chess.pgn();
117
+ }
118
+ return chess.pgn || '';
119
+ }
120
+
121
+ function chessHistory() {
122
+ if (typeof chess.history === 'function') {
123
+ return chess.history();
124
+ }
125
+ return chess.history || [];
126
+ }
127
+
128
+ function chessGet(square) {
129
+ if (typeof chess.get === 'function') {
130
+ return chess.get(square);
131
+ }
132
+ return null;
133
+ }
134
+
135
+ function chessMoves(options) {
136
+ if (typeof chess.moves === 'function') {
137
+ return chess.moves(options);
138
+ }
139
+ return [];
140
+ }
141
+
142
+ function chessMove(moveObj) {
143
+ if (typeof chess.move === 'function') {
144
+ try {
145
+ return chess.move(moveObj);
146
+ } catch (e) {
147
+ console.error('Move error:', e);
148
+ return null;
149
+ }
150
+ }
151
+ return null;
152
+ }
153
+
154
+ function isKingInCheck() {
155
+ // Manual check detection as fallback
156
+ const turn = chessTurn();
157
+ const kingSquare = findKingSquare(turn);
158
+ if (!kingSquare) return false;
159
+
160
+ // Check if any opponent piece can capture the king
161
+ const opponentColor = turn === 'w' ? 'b' : 'w';
162
+ for (let file of FILES) {
163
+ for (let rank of RANKS) {
164
+ const square = file + rank;
165
+ const piece = chessGet(square);
166
+ if (piece && piece.color === opponentColor) {
167
+ const moves = chessMoves({ square: square, verbose: true });
168
+ for (let move of moves) {
169
+ if (move.to === kingSquare) {
170
+ return true;
171
+ }
172
+ }
173
+ }
174
+ }
175
+ }
176
+ return false;
177
+ }
178
+
179
+ // ========================================
180
+ // Audio
181
+ // ========================================
182
+ const sounds = {
183
+ move: null,
184
+ capture: null,
185
+ check: null,
186
+ castle: null,
187
+ promote: null,
188
+ gameStart: null,
189
+ gameEnd: null,
190
+ notify: null
191
+ };
192
+
193
+ function initSounds() {
194
+ const AudioContext = window.AudioContext || window.webkitAudioContext;
195
+ if (!AudioContext) return;
196
+
197
+ const audioCtx = new AudioContext();
198
+
199
+ function createSound(frequency, duration, type = 'sine') {
200
+ return function() {
201
+ if (!gameState.soundEnabled) return;
202
+
203
+ try {
204
+ const oscillator = audioCtx.createOscillator();
205
+ const gainNode = audioCtx.createGain();
206
+
207
+ oscillator.connect(gainNode);
208
+ gainNode.connect(audioCtx.destination);
209
+
210
+ oscillator.frequency.value = frequency;
211
+ oscillator.type = type;
212
+
213
+ gainNode.gain.setValueAtTime(0.3, audioCtx.currentTime);
214
+ gainNode.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + duration);
215
+
216
+ oscillator.start(audioCtx.currentTime);
217
+ oscillator.stop(audioCtx.currentTime + duration);
218
+ } catch (e) {
219
+ console.log('Audio error:', e);
220
+ }
221
+ };
222
+ }
223
+
224
+ sounds.move = createSound(400, 0.1);
225
+ sounds.capture = createSound(300, 0.15);
226
+ sounds.check = createSound(600, 0.2);
227
+ sounds.castle = createSound(350, 0.15);
228
+ sounds.promote = createSound(800, 0.2);
229
+ sounds.gameStart = createSound(500, 0.3);
230
+ sounds.gameEnd = createSound(200, 0.5);
231
+ sounds.notify = createSound(700, 0.1);
232
+ }
233
+
234
+ // ========================================
235
+ // DOM Elements
236
+ // ========================================
237
+ const elements = {};
238
+
239
+ function cacheElements() {
240
+ elements.loadingScreen = document.getElementById('loading-screen');
241
+ elements.app = document.getElementById('app');
242
+ elements.lobbyScreen = document.getElementById('lobby-screen');
243
+ elements.waitingScreen = document.getElementById('waiting-screen');
244
+ elements.gameScreen = document.getElementById('game-screen');
245
+
246
+ elements.usernameInput = document.getElementById('username-input');
247
+ elements.roomCodeInput = document.getElementById('room-code-input');
248
+ elements.createGameBtn = document.getElementById('create-game-btn');
249
+ elements.joinGameBtn = document.getElementById('join-game-btn');
250
+ elements.timeBtns = document.querySelectorAll('.time-btn');
251
+ elements.roomsList = document.getElementById('rooms-list');
252
+ elements.refreshRoomsBtn = document.getElementById('refresh-rooms');
253
+
254
+ elements.displayRoomCode = document.getElementById('display-room-code');
255
+ elements.waitingTimeControl = document.getElementById('waiting-time-control');
256
+ elements.copyCodeBtn = document.getElementById('copy-code-btn');
257
+ elements.cancelWaitingBtn = document.getElementById('cancel-waiting');
258
+
259
+ elements.chessboard = document.getElementById('chessboard');
260
+ elements.opponentName = document.getElementById('opponent-name');
261
+ elements.opponentColor = document.getElementById('opponent-color');
262
+ elements.opponentTime = document.getElementById('opponent-time');
263
+ elements.opponentTimer = document.getElementById('opponent-timer');
264
+ elements.playerName = document.getElementById('player-name');
265
+ elements.playerColor = document.getElementById('player-color');
266
+ elements.playerTime = document.getElementById('player-time');
267
+ elements.playerTimer = document.getElementById('player-timer');
268
+ elements.opponentCaptured = document.getElementById('opponent-captured');
269
+ elements.playerCaptured = document.getElementById('player-captured');
270
+ elements.moveHistory = document.getElementById('move-history');
271
+
272
+ elements.resignBtn = document.getElementById('resign-btn');
273
+ elements.drawBtn = document.getElementById('draw-btn');
274
+ elements.flipBoardBtn = document.getElementById('flip-board-btn');
275
+ elements.chatToggleBtn = document.getElementById('chat-toggle-btn');
276
+ elements.chatBadge = document.getElementById('chat-badge');
277
+ elements.chatPanel = document.getElementById('chat-panel');
278
+ elements.chatMessages = document.getElementById('chat-messages');
279
+ elements.chatInput = document.getElementById('chat-input');
280
+ elements.sendChatBtn = document.getElementById('send-chat-btn');
281
+
282
+ elements.themeToggle = document.getElementById('theme-toggle');
283
+ elements.themeDropdown = document.getElementById('theme-dropdown');
284
+ elements.soundToggle = document.getElementById('sound-toggle');
285
+
286
+ elements.gameOverModal = document.getElementById('game-over-modal');
287
+ elements.gameOverIcon = document.getElementById('game-over-icon');
288
+ elements.gameOverTitle = document.getElementById('game-over-title');
289
+ elements.gameOverMessage = document.getElementById('game-over-message');
290
+ elements.playAgainBtn = document.getElementById('play-again-btn');
291
+ elements.backToLobbyBtn = document.getElementById('back-to-lobby-btn');
292
+
293
+ elements.drawOfferModal = document.getElementById('draw-offer-modal');
294
+ elements.drawOfferMessage = document.getElementById('draw-offer-message');
295
+ elements.acceptDrawBtn = document.getElementById('accept-draw-btn');
296
+ elements.declineDrawBtn = document.getElementById('decline-draw-btn');
297
+
298
+ elements.resignModal = document.getElementById('resign-modal');
299
+ elements.confirmResignBtn = document.getElementById('confirm-resign-btn');
300
+ elements.cancelResignBtn = document.getElementById('cancel-resign-btn');
301
+
302
+ elements.promotionModal = document.getElementById('promotion-modal');
303
+ elements.promotionPieces = document.getElementById('promotion-pieces');
304
+
305
+ elements.disconnectModal = document.getElementById('disconnect-modal');
306
+ elements.disconnectMessage = document.getElementById('disconnect-message');
307
+ elements.disconnectCountdown = document.getElementById('disconnect-countdown');
308
+
309
+ elements.toastContainer = document.getElementById('toast-container');
310
+ }
311
+
312
+ // ========================================
313
+ // Utilities
314
+ // ========================================
315
+ function showToast(message, type = 'info') {
316
+ const toast = document.createElement('div');
317
+ toast.className = `toast ${type}`;
318
+
319
+ const icons = {
320
+ success: 'fa-check-circle',
321
+ error: 'fa-exclamation-circle',
322
+ info: 'fa-info-circle',
323
+ warning: 'fa-exclamation-triangle'
324
+ };
325
+
326
+ toast.innerHTML = `
327
+ <i class="fas ${icons[type]}"></i>
328
+ <span>${message}</span>
329
+ `;
330
+
331
+ elements.toastContainer.appendChild(toast);
332
+
333
+ setTimeout(() => {
334
+ if (toast.parentNode) {
335
+ toast.remove();
336
+ }
337
+ }, 3000);
338
+ }
339
+
340
+ function formatTime(seconds) {
341
+ if (seconds === null || seconds === undefined) return '--:--';
342
+ const mins = Math.floor(seconds / 60);
343
+ const secs = Math.floor(seconds % 60);
344
+ return `${mins}:${secs.toString().padStart(2, '0')}`;
345
+ }
346
+
347
+ function showScreen(screenId) {
348
+ document.querySelectorAll('.screen').forEach(screen => {
349
+ screen.classList.add('hidden');
350
+ });
351
+ const screen = document.getElementById(screenId);
352
+ if (screen) {
353
+ screen.classList.remove('hidden');
354
+ }
355
+ }
356
+
357
+ function showModal(modalId) {
358
+ const modal = document.getElementById(modalId);
359
+ if (modal) {
360
+ modal.classList.remove('hidden');
361
+ }
362
+ }
363
+
364
+ function hideModal(modalId) {
365
+ const modal = document.getElementById(modalId);
366
+ if (modal) {
367
+ modal.classList.add('hidden');
368
+ }
369
+ }
370
+
371
+ function hideAllModals() {
372
+ document.querySelectorAll('.modal').forEach(modal => {
373
+ modal.classList.add('hidden');
374
+ });
375
+ }
376
+
377
+ function capitalizeFirst(str) {
378
+ if (!str) return '';
379
+ return str.charAt(0).toUpperCase() + str.slice(1);
380
+ }
381
+
382
+ function escapeHtml(text) {
383
+ const div = document.createElement('div');
384
+ div.textContent = text;
385
+ return div.innerHTML;
386
+ }
387
+
388
+ // ========================================
389
+ // Theme Management
390
+ // ========================================
391
+ function initTheme() {
392
+ const savedTheme = localStorage.getItem('chess-theme') || 'dark';
393
+ setTheme(savedTheme);
394
+ }
395
+
396
+ function setTheme(themeName) {
397
+ document.body.className = '';
398
+ document.body.classList.add(`theme-${themeName}`);
399
+ localStorage.setItem('chess-theme', themeName);
400
+
401
+ document.querySelectorAll('.theme-option').forEach(option => {
402
+ option.classList.remove('active');
403
+ if (option.dataset.theme === themeName) {
404
+ option.classList.add('active');
405
+ }
406
+ });
407
+ }
408
+
409
+ // ========================================
410
+ // Socket Connection
411
+ // ========================================
412
+ function initSocket() {
413
+ try {
414
+ socket = io({
415
+ transports: ['websocket', 'polling'],
416
+ upgrade: true,
417
+ reconnection: true,
418
+ reconnectionAttempts: 5,
419
+ reconnectionDelay: 1000
420
+ });
421
+
422
+ socket.on('connect', () => {
423
+ console.log('Connected to server');
424
+ showToast('Connected to server', 'success');
425
+ });
426
+
427
+ socket.on('disconnect', () => {
428
+ console.log('Disconnected from server');
429
+ showToast('Disconnected from server', 'error');
430
+ });
431
+
432
+ socket.on('connect_error', (error) => {
433
+ console.error('Connection error:', error);
434
+ showToast('Connection error', 'error');
435
+ });
436
+
437
+ socket.on('error', (data) => {
438
+ showToast(data.message || 'An error occurred', 'error');
439
+ });
440
+
441
+ socket.on('room_created', handleRoomCreated);
442
+ socket.on('room_joined', handleRoomJoined);
443
+ socket.on('game_restored', handleGameRestored);
444
+ socket.on('opponent_joined', handleOpponentJoined);
445
+ socket.on('opponent_reconnected', handleOpponentReconnected);
446
+ socket.on('game_start', handleGameStart);
447
+ socket.on('move_made', handleMoveMade);
448
+ socket.on('game_over', handleGameOver);
449
+ socket.on('opponent_disconnected', handleOpponentDisconnected);
450
+ socket.on('draw_offered', handleDrawOffered);
451
+ socket.on('draw_declined', handleDrawDeclined);
452
+ socket.on('chat_message', handleChatMessage);
453
+ socket.on('rooms_list', handleRoomsList);
454
+ } catch (e) {
455
+ console.error('Socket initialization error:', e);
456
+ showToast('Failed to connect to server', 'error');
457
+ }
458
+ }
459
+
460
+ // ========================================
461
+ // Socket Event Handlers
462
+ // ========================================
463
+ function handleRoomCreated(data) {
464
+ gameState.roomId = data.room_id;
465
+ gameState.playerColor = data.color;
466
+ gameState.timeControl = data.time_control;
467
+
468
+ if (elements.displayRoomCode) {
469
+ elements.displayRoomCode.textContent = data.room_id;
470
+ }
471
+ if (elements.waitingTimeControl) {
472
+ elements.waitingTimeControl.textContent = data.time_control
473
+ ? `${data.time_control} minutes`
474
+ : 'No Limit';
475
+ }
476
+
477
+ showScreen('waiting-screen');
478
+ showToast('Room created! Share the code with your friend.', 'success');
479
+ }
480
+
481
+ function handleRoomJoined(data) {
482
+ gameState.roomId = data.room_id;
483
+ gameState.playerColor = data.color;
484
+ gameState.timeControl = data.time_control;
485
+ gameState.opponent = data.opponent;
486
+
487
+ showToast(`Joined room! Playing against ${data.opponent}`, 'success');
488
+ }
489
+
490
+ function handleGameRestored(data) {
491
+ gameState.roomId = data.room_id;
492
+ gameState.playerColor = data.color;
493
+ gameState.opponent = data.opponent;
494
+
495
+ chess = new Chess(data.game_state.fen);
496
+ timers.white = data.white_time;
497
+ timers.black = data.black_time;
498
+
499
+ initGameScreen();
500
+ renderBoard();
501
+ updateTimers();
502
+
503
+ showToast('Game restored!', 'success');
504
+ }
505
+
506
+ function handleOpponentJoined(data) {
507
+ gameState.opponent = data.username;
508
+ showToast(`${data.username} joined the game!`, 'success');
509
+ }
510
+
511
+ function handleOpponentReconnected(data) {
512
+ hideModal('disconnect-modal');
513
+ gameState.opponent = data.username;
514
+
515
+ chess = new Chess(data.game_state.fen);
516
+ timers.white = data.white_time;
517
+ timers.black = data.black_time;
518
+
519
+ renderBoard();
520
+ updateTimers();
521
+ startTimer();
522
+
523
+ showToast(`${data.username} reconnected!`, 'success');
524
+ }
525
+
526
+ function handleGameStart(data) {
527
+ console.log('Game starting:', data);
528
+
529
+ gameState.gameStarted = true;
530
+ gameState.opponent = gameState.playerColor === 'white' ? data.black : data.white;
531
+ gameState.timeControl = data.time_control;
532
+
533
+ // Initialize chess.js
534
+ if (data.game_state && data.game_state.fen) {
535
+ chess = new Chess(data.game_state.fen);
536
+ } else {
537
+ chess = new Chess();
538
+ }
539
+
540
+ // Initialize timers
541
+ if (gameState.timeControl) {
542
+ timers.white = gameState.timeControl * 60;
543
+ timers.black = gameState.timeControl * 60;
544
+ }
545
+
546
+ initGameScreen();
547
+ renderBoard();
548
+ updateTimers();
549
+ startTimer();
550
+
551
+ if (sounds.gameStart) sounds.gameStart();
552
+ showToast('Game started! Good luck!', 'success');
553
+ }
554
+
555
+ function handleMoveMade(data) {
556
+ console.log('Move received:', data);
557
+
558
+ // Update chess.js state
559
+ chess = new Chess(data.fen);
560
+
561
+ // Store last move for highlighting
562
+ gameState.lastMove = data.move;
563
+
564
+ // Update timers
565
+ if (gameState.timeControl) {
566
+ timers.white = data.white_time;
567
+ timers.black = data.black_time;
568
+ }
569
+
570
+ // Clear selection
571
+ gameState.selectedSquare = null;
572
+ gameState.legalMoves = [];
573
+
574
+ // Play appropriate sound
575
+ if (data.is_checkmate && sounds.gameEnd) {
576
+ sounds.gameEnd();
577
+ } else if (data.is_check && sounds.check) {
578
+ sounds.check();
579
+ } else if (data.move && data.move.includes('x') && sounds.capture) {
580
+ sounds.capture();
581
+ } else if (data.move && (data.move === 'O-O' || data.move === 'O-O-O') && sounds.castle) {
582
+ sounds.castle();
583
+ } else if (sounds.move) {
584
+ sounds.move();
585
+ }
586
+
587
+ // Update display
588
+ renderBoard();
589
+ updateTimers();
590
+ updateMoveHistory();
591
+ updateCapturedPieces();
592
+
593
+ // Check game end conditions
594
+ if (data.is_checkmate || data.is_draw || data.is_stalemate) {
595
+ handleGameEnd(data);
596
+ }
597
+ }
598
+
599
+ function handleGameOver(data) {
600
+ gameState.gameOver = true;
601
+ stopTimer();
602
+
603
+ const isWinner = data.winner === gameState.username;
604
+ const isDraw = !data.winner;
605
+
606
+ if (elements.gameOverIcon) {
607
+ elements.gameOverIcon.className = 'game-over-icon';
608
+
609
+ if (isDraw) {
610
+ elements.gameOverIcon.classList.add('draw');
611
+ elements.gameOverIcon.innerHTML = '<i class="fas fa-handshake"></i>';
612
+ } else if (isWinner) {
613
+ elements.gameOverIcon.innerHTML = '<i class="fas fa-trophy"></i>';
614
+ } else {
615
+ elements.gameOverIcon.classList.add('loss');
616
+ elements.gameOverIcon.innerHTML = '<i class="fas fa-times"></i>';
617
+ }
618
+ }
619
+
620
+ if (elements.gameOverTitle) {
621
+ if (isDraw) {
622
+ elements.gameOverTitle.textContent = 'Draw!';
623
+ } else if (isWinner) {
624
+ elements.gameOverTitle.textContent = 'Victory!';
625
+ } else {
626
+ elements.gameOverTitle.textContent = 'Defeat';
627
+ }
628
+ }
629
+
630
+ if (elements.gameOverMessage) {
631
+ if (isDraw) {
632
+ elements.gameOverMessage.textContent = getDrawMessage(data.reason);
633
+ } else if (isWinner) {
634
+ elements.gameOverMessage.textContent = getWinMessage(data.reason);
635
+ } else {
636
+ elements.gameOverMessage.textContent = getLossMessage(data.reason, data);
637
+ }
638
+ }
639
+
640
+ if (sounds.gameEnd) sounds.gameEnd();
641
+ showModal('game-over-modal');
642
+ }
643
+
644
+ function getDrawMessage(reason) {
645
+ switch (reason) {
646
+ case 'stalemate': return 'Stalemate - No legal moves available.';
647
+ case 'insufficient': return 'Insufficient material to checkmate.';
648
+ case 'threefold': return 'Threefold repetition.';
649
+ case 'fifty_moves': return 'Fifty-move rule.';
650
+ case 'draw_agreement': return 'Draw by agreement.';
651
+ case 'draw': return 'Draw by agreement.';
652
+ default: return 'The game ended in a draw.';
653
+ }
654
+ }
655
+
656
+ function getWinMessage(reason) {
657
+ switch (reason) {
658
+ case 'checkmate': return 'Checkmate! Excellent play!';
659
+ case 'resignation': return 'Your opponent resigned.';
660
+ case 'timeout': return 'Your opponent ran out of time.';
661
+ case 'abandonment': return 'Your opponent abandoned the game.';
662
+ default: return 'You won the game!';
663
+ }
664
+ }
665
+
666
+ function getLossMessage(reason, data) {
667
+ switch (reason) {
668
+ case 'checkmate': return 'Checkmate! Better luck next time.';
669
+ case 'resignation': return 'You resigned the game.';
670
+ case 'timeout': return 'You ran out of time.';
671
+ case 'abandonment': return 'Game forfeited due to disconnection.';
672
+ default: return 'You lost the game.';
673
+ }
674
+ }
675
+
676
+ function handleGameEnd(data) {
677
+ gameState.gameOver = true;
678
+ stopTimer();
679
+
680
+ const turn = chessTurn();
681
+
682
+ if (data.is_checkmate) {
683
+ const winner = turn === 'w' ? 'Black' : 'White';
684
+ const isWinner = (winner === 'White' && gameState.playerColor === 'white') ||
685
+ (winner === 'Black' && gameState.playerColor === 'black');
686
+
687
+ if (elements.gameOverIcon) {
688
+ elements.gameOverIcon.className = 'game-over-icon';
689
+
690
+ if (isWinner) {
691
+ elements.gameOverIcon.innerHTML = '<i class="fas fa-trophy"></i>';
692
+ } else {
693
+ elements.gameOverIcon.classList.add('loss');
694
+ elements.gameOverIcon.innerHTML = '<i class="fas fa-times"></i>';
695
+ }
696
+ }
697
+
698
+ if (elements.gameOverTitle) {
699
+ elements.gameOverTitle.textContent = isWinner ? 'Victory!' : 'Defeat';
700
+ }
701
+
702
+ if (elements.gameOverMessage) {
703
+ elements.gameOverMessage.textContent = isWinner
704
+ ? 'Checkmate! Excellent play!'
705
+ : 'Checkmate! Better luck next time.';
706
+ }
707
+ } else if (data.is_stalemate || data.is_draw) {
708
+ if (elements.gameOverIcon) {
709
+ elements.gameOverIcon.className = 'game-over-icon draw';
710
+ elements.gameOverIcon.innerHTML = '<i class="fas fa-handshake"></i>';
711
+ }
712
+
713
+ if (elements.gameOverTitle) {
714
+ elements.gameOverTitle.textContent = 'Draw!';
715
+ }
716
+
717
+ if (elements.gameOverMessage) {
718
+ elements.gameOverMessage.textContent = data.is_stalemate
719
+ ? 'Stalemate - No legal moves available.'
720
+ : 'The game ended in a draw.';
721
+ }
722
+ }
723
+
724
+ showModal('game-over-modal');
725
+ }
726
+
727
+ function handleOpponentDisconnected(data) {
728
+ showModal('disconnect-modal');
729
+
730
+ if (elements.disconnectMessage) {
731
+ elements.disconnectMessage.textContent = `${data.username} disconnected. Waiting for reconnection...`;
732
+ }
733
+
734
+ let countdown = data.timeout;
735
+ stopTimer();
736
+
737
+ const disconnectInterval = setInterval(() => {
738
+ countdown--;
739
+ if (elements.disconnectCountdown) {
740
+ elements.disconnectCountdown.textContent = formatTime(countdown);
741
+ }
742
+
743
+ if (countdown <= 0) {
744
+ clearInterval(disconnectInterval);
745
+ }
746
+ }, 1000);
747
+
748
+ gameState.disconnectInterval = disconnectInterval;
749
+ }
750
+
751
+ function handleDrawOffered(data) {
752
+ if (elements.drawOfferMessage) {
753
+ elements.drawOfferMessage.textContent = `${data.from} offers a draw`;
754
+ }
755
+ showModal('draw-offer-modal');
756
+ if (sounds.notify) sounds.notify();
757
+ }
758
+
759
+ function handleDrawDeclined(data) {
760
+ showToast(`${data.by} declined the draw offer`, 'info');
761
+ }
762
+
763
+ function handleChatMessage(data) {
764
+ addChatMessage(data.username, data.message, data.timestamp);
765
+
766
+ if (data.username !== gameState.username) {
767
+ unreadMessages++;
768
+ updateChatBadge();
769
+ if (sounds.notify) sounds.notify();
770
+ }
771
+ }
772
+
773
+ function handleRoomsList(data) {
774
+ renderRoomsList(data.rooms);
775
+ }
776
+
777
+ // ========================================
778
+ // Game Screen Initialization
779
+ // ========================================
780
+ function initGameScreen() {
781
+ showScreen('game-screen');
782
+
783
+ if (elements.playerName) {
784
+ elements.playerName.textContent = gameState.username || 'You';
785
+ }
786
+ if (elements.playerColor) {
787
+ elements.playerColor.textContent = capitalizeFirst(gameState.playerColor);
788
+ }
789
+ if (elements.opponentName) {
790
+ elements.opponentName.textContent = gameState.opponent || 'Opponent';
791
+ }
792
+ if (elements.opponentColor) {
793
+ elements.opponentColor.textContent = gameState.playerColor === 'white' ? 'Black' : 'White';
794
+ }
795
+
796
+ // Hide timers if no time control
797
+ if (!gameState.timeControl) {
798
+ if (elements.playerTimer) elements.playerTimer.style.display = 'none';
799
+ if (elements.opponentTimer) elements.opponentTimer.style.display = 'none';
800
+ } else {
801
+ if (elements.playerTimer) elements.playerTimer.style.display = 'flex';
802
+ if (elements.opponentTimer) elements.opponentTimer.style.display = 'flex';
803
+ }
804
+
805
+ // Flip board if playing black
806
+ if (gameState.playerColor === 'black') {
807
+ gameState.isFlipped = true;
808
+ }
809
+
810
+ // Reset game state
811
+ gameState.gameOver = false;
812
+ gameState.selectedSquare = null;
813
+ gameState.legalMoves = [];
814
+ gameState.lastMove = null;
815
+
816
+ // Clear move history
817
+ if (elements.moveHistory) {
818
+ elements.moveHistory.innerHTML = '';
819
+ }
820
+
821
+ // Clear captured pieces
822
+ if (elements.opponentCaptured) elements.opponentCaptured.innerHTML = '';
823
+ if (elements.playerCaptured) elements.playerCaptured.innerHTML = '';
824
+
825
+ // Clear chat
826
+ if (elements.chatMessages) elements.chatMessages.innerHTML = '';
827
+ unreadMessages = 0;
828
+ updateChatBadge();
829
+ }
830
+
831
+ // ========================================
832
+ // Board Rendering
833
+ // ========================================
834
+ function renderBoard() {
835
+ if (!elements.chessboard || !chess) {
836
+ console.error('Chessboard element or chess instance not found');
837
+ return;
838
+ }
839
+
840
+ elements.chessboard.innerHTML = '';
841
+
842
+ const files = gameState.isFlipped ? [...FILES].reverse() : FILES;
843
+ const ranks = gameState.isFlipped ? [...RANKS].reverse() : RANKS;
844
+
845
+ ranks.forEach((rank, rankIdx) => {
846
+ files.forEach((file, fileIdx) => {
847
+ const square = file + rank;
848
+ const isLight = (rankIdx + fileIdx) % 2 === 0;
849
+
850
+ const squareEl = document.createElement('div');
851
+ squareEl.className = `board-square ${isLight ? 'light' : 'dark'}`;
852
+ squareEl.dataset.square = square;
853
+
854
+ // Add coordinates on edges
855
+ if (fileIdx === 7) {
856
+ const rankLabel = document.createElement('span');
857
+ rankLabel.className = 'coord-rank';
858
+ rankLabel.textContent = rank;
859
+ squareEl.appendChild(rankLabel);
860
+ }
861
+ if (rankIdx === 7) {
862
+ const fileLabel = document.createElement('span');
863
+ fileLabel.className = 'coord-file';
864
+ fileLabel.textContent = file;
865
+ squareEl.appendChild(fileLabel);
866
+ }
867
+
868
+ // Add piece
869
+ const piece = chessGet(square);
870
+ if (piece) {
871
+ const pieceEl = document.createElement('span');
872
+ pieceEl.className = `piece piece-${piece.color === 'w' ? 'white' : 'black'}`;
873
+ const pieceChar = piece.color === 'w' ? piece.type.toUpperCase() : piece.type;
874
+ pieceEl.textContent = PIECES[pieceChar];
875
+ squareEl.appendChild(pieceEl);
876
+ }
877
+
878
+ // Highlight selected square
879
+ if (gameState.selectedSquare === square) {
880
+ squareEl.classList.add('selected');
881
+ }
882
+
883
+ // Highlight last move
884
+ if (gameState.lastMove) {
885
+ const lastMoveFrom = typeof gameState.lastMove === 'object'
886
+ ? gameState.lastMove.from
887
+ : null;
888
+ const lastMoveTo = typeof gameState.lastMove === 'object'
889
+ ? gameState.lastMove.to
890
+ : extractSquareFromSan(gameState.lastMove);
891
+
892
+ if (square === lastMoveFrom || square === lastMoveTo) {
893
+ squareEl.classList.add('last-move');
894
+ }
895
+ }
896
+
897
+ // Highlight legal moves
898
+ if (gameState.legalMoves.includes(square)) {
899
+ const targetPiece = chessGet(square);
900
+ if (targetPiece) {
901
+ squareEl.classList.add('legal-capture');
902
+ } else {
903
+ squareEl.classList.add('legal-move');
904
+ }
905
+ }
906
+
907
+ // Add check indicator
908
+ if (chessInCheck()) {
909
+ const turn = chessTurn();
910
+ const kingSquare = findKingSquare(turn);
911
+ if (square === kingSquare) {
912
+ const checkRing = document.createElement('div');
913
+ checkRing.className = 'check-ring';
914
+ squareEl.appendChild(checkRing);
915
+ }
916
+ }
917
+
918
+ // Add click handler
919
+ squareEl.addEventListener('click', () => handleSquareClick(square));
920
+
921
+ elements.chessboard.appendChild(squareEl);
922
+ });
923
+ });
924
+
925
+ // Update turn indicator
926
+ updateTurnIndicator();
927
+ }
928
+
929
+ function extractSquareFromSan(san) {
930
+ if (!san || typeof san !== 'string') return null;
931
+ // Extract destination square from SAN notation
932
+ const match = san.match(/[a-h][1-8]/g);
933
+ if (match && match.length > 0) {
934
+ return match[match.length - 1];
935
+ }
936
+ return null;
937
+ }
938
+
939
+ function findKingSquare(color) {
940
+ for (let file of FILES) {
941
+ for (let rank of RANKS) {
942
+ const square = file + rank;
943
+ const piece = chessGet(square);
944
+ if (piece && piece.type === 'k' && piece.color === color) {
945
+ return square;
946
+ }
947
+ }
948
+ }
949
+ return null;
950
+ }
951
+
952
+ function handleSquareClick(square) {
953
+ if (gameState.gameOver || !chess) return;
954
+
955
+ const turn = chessTurn();
956
+ const myTurn = (turn === 'w' && gameState.playerColor === 'white') ||
957
+ (turn === 'b' && gameState.playerColor === 'black');
958
+
959
+ if (!myTurn) {
960
+ showToast("It's not your turn", 'warning');
961
+ return;
962
+ }
963
+
964
+ const piece = chessGet(square);
965
+
966
+ // If clicking on own piece, select it
967
+ if (piece && ((piece.color === 'w' && gameState.playerColor === 'white') ||
968
+ (piece.color === 'b' && gameState.playerColor === 'black'))) {
969
+ selectSquare(square);
970
+ return;
971
+ }
972
+
973
+ // If a piece is selected and clicking on a legal move, make the move
974
+ if (gameState.selectedSquare && gameState.legalMoves.includes(square)) {
975
+ attemptMove(gameState.selectedSquare, square);
976
+ return;
977
+ }
978
+
979
+ // Clear selection
980
+ gameState.selectedSquare = null;
981
+ gameState.legalMoves = [];
982
+ renderBoard();
983
+ }
984
+
985
+ function selectSquare(square) {
986
+ gameState.selectedSquare = square;
987
+
988
+ // Get legal moves for this piece
989
+ const moves = chessMoves({ square: square, verbose: true });
990
+ gameState.legalMoves = moves.map(m => m.to);
991
+
992
+ renderBoard();
993
+ }
994
+
995
+ function attemptMove(from, to) {
996
+ const piece = chessGet(from);
997
+
998
+ // Check for pawn promotion
999
+ if (piece && piece.type === 'p') {
1000
+ const targetRank = to.charAt(1);
1001
+ if ((piece.color === 'w' && targetRank === '8') ||
1002
+ (piece.color === 'b' && targetRank === '1')) {
1003
+ gameState.pendingPromotion = { from, to };
1004
+ showPromotionModal(piece.color);
1005
+ return;
1006
+ }
1007
+ }
1008
+
1009
+ makeMove(from, to);
1010
+ }
1011
+
1012
+ function makeMove(from, to, promotion = null) {
1013
+ const moveObj = {
1014
+ from: from,
1015
+ to: to
1016
+ };
1017
+
1018
+ if (promotion) {
1019
+ moveObj.promotion = promotion;
1020
+ }
1021
+
1022
+ const move = chessMove(moveObj);
1023
+
1024
+ if (move) {
1025
+ gameState.lastMove = { from, to };
1026
+ gameState.selectedSquare = null;
1027
+ gameState.legalMoves = [];
1028
+
1029
+ // Send move to server
1030
+ socket.emit('make_move', {
1031
+ move: move.san,
1032
+ fen: chessFen(),
1033
+ pgn: chessPgn(),
1034
+ turn: chessTurn(),
1035
+ is_check: chessInCheck(),
1036
+ is_checkmate: chessIsCheckmate(),
1037
+ is_draw: chessIsDraw(),
1038
+ is_stalemate: chessIsStalemate()
1039
+ });
1040
+
1041
+ // Play promotion sound
1042
+ if (promotion && sounds.promote) {
1043
+ sounds.promote();
1044
+ }
1045
+
1046
+ // Render immediately for responsiveness
1047
+ renderBoard();
1048
+ } else {
1049
+ showToast('Invalid move', 'error');
1050
+ gameState.selectedSquare = null;
1051
+ gameState.legalMoves = [];
1052
+ renderBoard();
1053
+ }
1054
+ }
1055
+
1056
+ function showPromotionModal(color) {
1057
+ if (elements.promotionPieces) {
1058
+ const pieces = elements.promotionPieces.querySelectorAll('.promotion-piece');
1059
+ pieces.forEach(piece => {
1060
+ const pieceType = piece.dataset.piece;
1061
+ const pieceChar = color === 'w' ? pieceType.toUpperCase() : pieceType;
1062
+ piece.textContent = PIECES[pieceChar];
1063
+ piece.className = `promotion-piece piece-${color === 'w' ? 'white' : 'black'}`;
1064
+ });
1065
+ }
1066
+ showModal('promotion-modal');
1067
+ }
1068
+
1069
+ // ========================================
1070
+ // Timer Management
1071
+ // ========================================
1072
+ function startTimer() {
1073
+ if (!gameState.timeControl) return;
1074
+
1075
+ stopTimer();
1076
+
1077
+ timers.interval = setInterval(() => {
1078
+ if (gameState.gameOver || !chess) {
1079
+ stopTimer();
1080
+ return;
1081
+ }
1082
+
1083
+ const currentTurn = chessTurn();
1084
+
1085
+ if (currentTurn === 'w') {
1086
+ timers.white = Math.max(0, (timers.white || 0) - 0.1);
1087
+ } else {
1088
+ timers.black = Math.max(0, (timers.black || 0) - 0.1);
1089
+ }
1090
+
1091
+ updateTimers();
1092
+
1093
+ // Check for timeout
1094
+ if (timers.white <= 0 || timers.black <= 0) {
1095
+ stopTimer();
1096
+ }
1097
+ }, 100);
1098
+ }
1099
+
1100
+ function stopTimer() {
1101
+ if (timers.interval) {
1102
+ clearInterval(timers.interval);
1103
+ timers.interval = null;
1104
+ }
1105
+ }
1106
+
1107
+ function updateTimers() {
1108
+ if (!gameState.timeControl) return;
1109
+
1110
+ const playerIsWhite = gameState.playerColor === 'white';
1111
+ const playerTime = playerIsWhite ? timers.white : timers.black;
1112
+ const opponentTime = playerIsWhite ? timers.black : timers.white;
1113
+
1114
+ if (elements.playerTime) {
1115
+ elements.playerTime.textContent = formatTime(playerTime);
1116
+ }
1117
+ if (elements.opponentTime) {
1118
+ elements.opponentTime.textContent = formatTime(opponentTime);
1119
+ }
1120
+
1121
+ // Add low time warning
1122
+ if (elements.playerTimer) {
1123
+ if (playerTime !== null && playerTime < 30) {
1124
+ elements.playerTimer.classList.add('low-time');
1125
+ } else {
1126
+ elements.playerTimer.classList.remove('low-time');
1127
+ }
1128
+ }
1129
+
1130
+ if (elements.opponentTimer) {
1131
+ if (opponentTime !== null && opponentTime < 30) {
1132
+ elements.opponentTimer.classList.add('low-time');
1133
+ } else {
1134
+ elements.opponentTimer.classList.remove('low-time');
1135
+ }
1136
+ }
1137
+ }
1138
+
1139
+ function updateTurnIndicator() {
1140
+ if (!chess) return;
1141
+
1142
+ const currentTurn = chessTurn();
1143
+ const isPlayerTurn = (currentTurn === 'w' && gameState.playerColor === 'white') ||
1144
+ (currentTurn === 'b' && gameState.playerColor === 'black');
1145
+
1146
+ const playerInfo = document.querySelector('.player-self-info');
1147
+ const opponentInfo = document.querySelector('.opponent-info');
1148
+
1149
+ if (playerInfo && opponentInfo) {
1150
+ if (isPlayerTurn) {
1151
+ playerInfo.classList.add('active-turn');
1152
+ opponentInfo.classList.remove('active-turn');
1153
+ } else {
1154
+ playerInfo.classList.remove('active-turn');
1155
+ opponentInfo.classList.add('active-turn');
1156
+ }
1157
+ }
1158
+ }
1159
+
1160
+ // ========================================
1161
+ // Move History
1162
+ // ========================================
1163
+ function updateMoveHistory() {
1164
+ if (!elements.moveHistory || !chess) return;
1165
+
1166
+ const history = chessHistory();
1167
+ elements.moveHistory.innerHTML = '';
1168
+
1169
+ for (let i = 0; i < history.length; i += 2) {
1170
+ const moveNum = Math.floor(i / 2) + 1;
1171
+ const whiteMove = history[i] || '';
1172
+ const blackMove = history[i + 1] || '';
1173
+
1174
+ const row = document.createElement('div');
1175
+ row.className = 'move-row';
1176
+ row.innerHTML = `
1177
+ <span class="move-number">${moveNum}.</span>
1178
+ <span class="move-white">${whiteMove}</span>
1179
+ <span class="move-black">${blackMove}</span>
1180
+ `;
1181
+
1182
+ elements.moveHistory.appendChild(row);
1183
+ }
1184
+
1185
+ // Scroll to bottom
1186
+ elements.moveHistory.scrollTop = elements.moveHistory.scrollHeight;
1187
+ }
1188
+
1189
+ // ========================================
1190
+ // Captured Pieces
1191
+ // ========================================
1192
+ function updateCapturedPieces() {
1193
+ if (!chess) return;
1194
+
1195
+ const startingPieces = {
1196
+ 'w': { p: 8, n: 2, b: 2, r: 2, q: 1 },
1197
+ 'b': { p: 8, n: 2, b: 2, r: 2, q: 1 }
1198
+ };
1199
+
1200
+ const currentPieces = {
1201
+ 'w': { p: 0, n: 0, b: 0, r: 0, q: 0 },
1202
+ 'b': { p: 0, n: 0, b: 0, r: 0, q: 0 }
1203
+ };
1204
+
1205
+ // Count current pieces
1206
+ for (let file of FILES) {
1207
+ for (let rank of RANKS) {
1208
+ const piece = chessGet(file + rank);
1209
+ if (piece && piece.type !== 'k') {
1210
+ currentPieces[piece.color][piece.type]++;
1211
+ }
1212
+ }
1213
+ }
1214
+
1215
+ // Calculate captured pieces
1216
+ const captured = {
1217
+ 'w': {},
1218
+ 'b': {}
1219
+ };
1220
+
1221
+ for (let color of ['w', 'b']) {
1222
+ for (let type of ['q', 'r', 'b', 'n', 'p']) {
1223
+ const diff = startingPieces[color][type] - currentPieces[color][type];
1224
+ if (diff > 0) {
1225
+ captured[color][type] = diff;
1226
+ }
1227
+ }
1228
+ }
1229
+
1230
+ // Render captured pieces
1231
+ const playerCapturedColor = gameState.playerColor === 'white' ? 'b' : 'w';
1232
+ const opponentCapturedColor = gameState.playerColor === 'white' ? 'w' : 'b';
1233
+
1234
+ if (elements.playerCaptured) {
1235
+ elements.playerCaptured.innerHTML = renderCapturedPieces(captured[playerCapturedColor], playerCapturedColor);
1236
+ }
1237
+ if (elements.opponentCaptured) {
1238
+ elements.opponentCaptured.innerHTML = renderCapturedPieces(captured[opponentCapturedColor], opponentCapturedColor);
1239
+ }
1240
+ }
1241
+
1242
+ function renderCapturedPieces(captured, color) {
1243
+ let html = '';
1244
+ for (let type of ['q', 'r', 'b', 'n', 'p']) {
1245
+ if (captured[type]) {
1246
+ const pieceChar = color === 'w' ? type.toUpperCase() : type;
1247
+ for (let i = 0; i < captured[type]; i++) {
1248
+ html += `<span class="piece-${color === 'w' ? 'white' : 'black'}">${PIECES[pieceChar]}</span>`;
1249
+ }
1250
+ }
1251
+ }
1252
+ return html;
1253
+ }
1254
+
1255
+ // ========================================
1256
+ // Chat
1257
+ // ========================================
1258
+ function addChatMessage(username, message, timestamp) {
1259
+ if (!elements.chatMessages) return;
1260
+
1261
+ const time = timestamp ? new Date(timestamp).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }) : '';
1262
+
1263
+ const messageEl = document.createElement('div');
1264
+ messageEl.className = 'chat-message';
1265
+ messageEl.innerHTML = `
1266
+ <div class="sender">${escapeHtml(username)}</div>
1267
+ <div class="text">${escapeHtml(message)}</div>
1268
+ <div class="time">${time}</div>
1269
+ `;
1270
+
1271
+ elements.chatMessages.appendChild(messageEl);
1272
+ elements.chatMessages.scrollTop = elements.chatMessages.scrollHeight;
1273
+ }
1274
+
1275
+ function updateChatBadge() {
1276
+ if (!elements.chatBadge) return;
1277
+
1278
+ if (unreadMessages > 0) {
1279
+ elements.chatBadge.textContent = unreadMessages > 9 ? '9+' : unreadMessages;
1280
+ elements.chatBadge.classList.remove('hidden');
1281
+ } else {
1282
+ elements.chatBadge.classList.add('hidden');
1283
+ }
1284
+ }
1285
+
1286
+ function sendChatMessage() {
1287
+ if (!elements.chatInput) return;
1288
+
1289
+ const message = elements.chatInput.value.trim();
1290
+ if (message && socket) {
1291
+ socket.emit('chat_message', { message: message });
1292
+ elements.chatInput.value = '';
1293
+ }
1294
+ }
1295
+
1296
+ // ========================================
1297
+ // Rooms List
1298
+ // ========================================
1299
+ function renderRoomsList(rooms) {
1300
+ if (!elements.roomsList) return;
1301
+
1302
+ if (!rooms || rooms.length === 0) {
1303
+ elements.roomsList.innerHTML = '<p class="no-rooms">No rooms available. Create one!</p>';
1304
+ return;
1305
+ }
1306
+
1307
+ elements.roomsList.innerHTML = rooms.map(room => `
1308
+ <div class="room-item" data-room-id="${room.room_id}">
1309
+ <div class="room-info">
1310
+ <div class="room-host">${escapeHtml(room.host)}'s Room</div>
1311
+ <div class="room-time">${room.time_control ? room.time_control + ' min' : 'No Limit'}</div>
1312
+ </div>
1313
+ <button class="btn btn-secondary join-room-btn" data-room-id="${room.room_id}">
1314
+ Join
1315
+ </button>
1316
+ </div>
1317
+ `).join('');
1318
+
1319
+ // Add click handlers
1320
+ elements.roomsList.querySelectorAll('.join-room-btn').forEach(btn => {
1321
+ btn.addEventListener('click', () => {
1322
+ const roomId = btn.dataset.roomId;
1323
+ joinRoom(roomId);
1324
+ });
1325
+ });
1326
+ }
1327
+
1328
+ function joinRoom(roomId) {
1329
+ const username = elements.usernameInput ? elements.usernameInput.value.trim() : 'Anonymous';
1330
+ gameState.username = username || 'Anonymous';
1331
+
1332
+ if (socket) {
1333
+ socket.emit('join_room', {
1334
+ username: gameState.username,
1335
+ room_id: roomId
1336
+ });
1337
+ }
1338
+ }
1339
+
1340
+ // ========================================
1341
+ // Reset Game
1342
+ // ========================================
1343
+ function resetGame() {
1344
+ gameState.roomId = null;
1345
+ gameState.playerColor = null;
1346
+ gameState.opponent = null;
1347
+ gameState.timeControl = null;
1348
+ gameState.isMyTurn = false;
1349
+ gameState.gameStarted = false;
1350
+ gameState.gameOver = false;
1351
+ gameState.selectedSquare = null;
1352
+ gameState.legalMoves = [];
1353
+ gameState.lastMove = null;
1354
+ gameState.isFlipped = false;
1355
+ gameState.pendingPromotion = null;
1356
+
1357
+ stopTimer();
1358
+ timers.white = null;
1359
+ timers.black = null;
1360
+
1361
+ if (gameState.disconnectInterval) {
1362
+ clearInterval(gameState.disconnectInterval);
1363
+ }
1364
+
1365
+ chess = null;
1366
+ }
1367
+
1368
+ // ========================================
1369
+ // Event Listeners
1370
+ // ========================================
1371
+ function initEventListeners() {
1372
+ // Time control buttons
1373
+ if (elements.timeBtns) {
1374
+ elements.timeBtns.forEach(btn => {
1375
+ btn.addEventListener('click', () => {
1376
+ elements.timeBtns.forEach(b => b.classList.remove('active'));
1377
+ btn.classList.add('active');
1378
+ });
1379
+ });
1380
+ }
1381
+
1382
+ // Create game
1383
+ if (elements.createGameBtn) {
1384
+ elements.createGameBtn.addEventListener('click', () => {
1385
+ const username = elements.usernameInput ? elements.usernameInput.value.trim() : '';
1386
+ const activeTimeBtn = document.querySelector('.time-btn.active');
1387
+ const timeControl = activeTimeBtn && activeTimeBtn.dataset.time !== 'null'
1388
+ ? parseInt(activeTimeBtn.dataset.time)
1389
+ : null;
1390
+
1391
+ gameState.username = username || 'Anonymous';
1392
+
1393
+ if (socket) {
1394
+ socket.emit('create_room', {
1395
+ username: gameState.username,
1396
+ time_control: timeControl
1397
+ });
1398
+ }
1399
+ });
1400
+ }
1401
+
1402
+ // Join game
1403
+ if (elements.joinGameBtn) {
1404
+ elements.joinGameBtn.addEventListener('click', () => {
1405
+ const roomCode = elements.roomCodeInput ? elements.roomCodeInput.value.trim().toUpperCase() : '';
1406
+ if (!roomCode) {
1407
+ showToast('Please enter a room code', 'warning');
1408
+ return;
1409
+ }
1410
+ joinRoom(roomCode);
1411
+ });
1412
+ }
1413
+
1414
+ // Refresh rooms
1415
+ if (elements.refreshRoomsBtn) {
1416
+ elements.refreshRoomsBtn.addEventListener('click', () => {
1417
+ if (socket) {
1418
+ socket.emit('get_rooms');
1419
+ }
1420
+ });
1421
+ }
1422
+
1423
+ // Copy room code
1424
+ if (elements.copyCodeBtn) {
1425
+ elements.copyCodeBtn.addEventListener('click', () => {
1426
+ if (gameState.roomId) {
1427
+ navigator.clipboard.writeText(gameState.roomId).then(() => {
1428
+ showToast('Room code copied!', 'success');
1429
+ }).catch(() => {
1430
+ showToast('Failed to copy', 'error');
1431
+ });
1432
+ }
1433
+ });
1434
+ }
1435
+
1436
+ // Cancel waiting
1437
+ if (elements.cancelWaitingBtn) {
1438
+ elements.cancelWaitingBtn.addEventListener('click', () => {
1439
+ showScreen('lobby-screen');
1440
+ if (socket) {
1441
+ socket.emit('leave_room');
1442
+ }
1443
+ resetGame();
1444
+ });
1445
+ }
1446
+
1447
+ // Resign button
1448
+ if (elements.resignBtn) {
1449
+ elements.resignBtn.addEventListener('click', () => {
1450
+ if (!gameState.gameOver) {
1451
+ showModal('resign-modal');
1452
+ }
1453
+ });
1454
+ }
1455
+
1456
+ // Confirm resign
1457
+ if (elements.confirmResignBtn) {
1458
+ elements.confirmResignBtn.addEventListener('click', () => {
1459
+ hideModal('resign-modal');
1460
+ if (socket) {
1461
+ socket.emit('resign');
1462
+ }
1463
+ });
1464
+ }
1465
+
1466
+ // Cancel resign
1467
+ if (elements.cancelResignBtn) {
1468
+ elements.cancelResignBtn.addEventListener('click', () => {
1469
+ hideModal('resign-modal');
1470
+ });
1471
+ }
1472
+
1473
+ // Draw button
1474
+ if (elements.drawBtn) {
1475
+ elements.drawBtn.addEventListener('click', () => {
1476
+ if (!gameState.gameOver && socket) {
1477
+ socket.emit('offer_draw');
1478
+ showToast('Draw offer sent', 'info');
1479
+ }
1480
+ });
1481
+ }
1482
+
1483
+ // Accept draw
1484
+ if (elements.acceptDrawBtn) {
1485
+ elements.acceptDrawBtn.addEventListener('click', () => {
1486
+ hideModal('draw-offer-modal');
1487
+ if (socket) {
1488
+ socket.emit('accept_draw');
1489
+ }
1490
+ });
1491
+ }
1492
+
1493
+ // Decline draw
1494
+ if (elements.declineDrawBtn) {
1495
+ elements.declineDrawBtn.addEventListener('click', () => {
1496
+ hideModal('draw-offer-modal');
1497
+ if (socket) {
1498
+ socket.emit('decline_draw');
1499
+ }
1500
+ });
1501
+ }
1502
+
1503
+ // Flip board
1504
+ if (elements.flipBoardBtn) {
1505
+ elements.flipBoardBtn.addEventListener('click', () => {
1506
+ gameState.isFlipped = !gameState.isFlipped;
1507
+ renderBoard();
1508
+ });
1509
+ }
1510
+
1511
+ // Chat toggle
1512
+ if (elements.chatToggleBtn && elements.chatPanel) {
1513
+ elements.chatToggleBtn.addEventListener('click', () => {
1514
+ elements.chatPanel.classList.toggle('hidden');
1515
+ if (!elements.chatPanel.classList.contains('hidden')) {
1516
+ unreadMessages = 0;
1517
+ updateChatBadge();
1518
+ }
1519
+ });
1520
+ }
1521
+
1522
+ // Send chat
1523
+ if (elements.sendChatBtn) {
1524
+ elements.sendChatBtn.addEventListener('click', sendChatMessage);
1525
+ }
1526
+ if (elements.chatInput) {
1527
+ elements.chatInput.addEventListener('keypress', (e) => {
1528
+ if (e.key === 'Enter') {
1529
+ sendChatMessage();
1530
+ }
1531
+ });
1532
+ }
1533
+
1534
+ // Theme toggle
1535
+ if (elements.themeToggle && elements.themeDropdown) {
1536
+ elements.themeToggle.addEventListener('click', (e) => {
1537
+ e.stopPropagation();
1538
+ elements.themeDropdown.classList.toggle('hidden');
1539
+ });
1540
+ }
1541
+
1542
+ // Theme selection
1543
+ document.querySelectorAll('.theme-option').forEach(option => {
1544
+ option.addEventListener('click', () => {
1545
+ setTheme(option.dataset.theme);
1546
+ if (elements.themeDropdown) {
1547
+ elements.themeDropdown.classList.add('hidden');
1548
+ }
1549
+ });
1550
+ });
1551
+
1552
+ // Close dropdown when clicking outside
1553
+ document.addEventListener('click', (e) => {
1554
+ if (elements.themeToggle && elements.themeDropdown) {
1555
+ if (!elements.themeToggle.contains(e.target) && !elements.themeDropdown.contains(e.target)) {
1556
+ elements.themeDropdown.classList.add('hidden');
1557
+ }
1558
+ }
1559
+ });
1560
+
1561
+ // Sound toggle
1562
+ if (elements.soundToggle) {
1563
+ elements.soundToggle.addEventListener('click', () => {
1564
+ gameState.soundEnabled = !gameState.soundEnabled;
1565
+ elements.soundToggle.innerHTML = gameState.soundEnabled
1566
+ ? '<i class="fas fa-volume-up"></i>'
1567
+ : '<i class="fas fa-volume-mute"></i>';
1568
+ showToast(gameState.soundEnabled ? 'Sound enabled' : 'Sound disabled', 'info');
1569
+ });
1570
+ }
1571
+
1572
+ // Promotion pieces
1573
+ if (elements.promotionPieces) {
1574
+ elements.promotionPieces.querySelectorAll('.promotion-piece').forEach(piece => {
1575
+ piece.addEventListener('click', () => {
1576
+ const promotionPiece = piece.dataset.piece;
1577
+ hideModal('promotion-modal');
1578
+
1579
+ if (gameState.pendingPromotion) {
1580
+ makeMove(gameState.pendingPromotion.from, gameState.pendingPromotion.to, promotionPiece);
1581
+ gameState.pendingPromotion = null;
1582
+ }
1583
+ });
1584
+ });
1585
+ }
1586
+
1587
+ // Play again
1588
+ if (elements.playAgainBtn) {
1589
+ elements.playAgainBtn.addEventListener('click', () => {
1590
+ hideModal('game-over-modal');
1591
+ resetGame();
1592
+ showScreen('lobby-screen');
1593
+ });
1594
+ }
1595
+
1596
+ // Back to lobby
1597
+ if (elements.backToLobbyBtn) {
1598
+ elements.backToLobbyBtn.addEventListener('click', () => {
1599
+ hideModal('game-over-modal');
1600
+ resetGame();
1601
+ showScreen('lobby-screen');
1602
+ });
1603
+ }
1604
+
1605
+ // Enter key for room code
1606
+ if (elements.roomCodeInput) {
1607
+ elements.roomCodeInput.addEventListener('keypress', (e) => {
1608
+ if (e.key === 'Enter' && elements.joinGameBtn) {
1609
+ elements.joinGameBtn.click();
1610
+ }
1611
+ });
1612
+ }
1613
+
1614
+ // Enter key for username
1615
+ if (elements.usernameInput) {
1616
+ elements.usernameInput.addEventListener('keypress', (e) => {
1617
+ if (e.key === 'Enter' && elements.createGameBtn) {
1618
+ elements.createGameBtn.click();
1619
+ }
1620
+ });
1621
+ }
1622
+ }
1623
+
1624
+ // ========================================
1625
+ // Initialization
1626
+ // ========================================
1627
+ function init() {
1628
+ console.log('Initializing Chess Arena...');
1629
+
1630
+ // Cache DOM elements
1631
+ cacheElements();
1632
+
1633
+ // Initialize theme
1634
+ initTheme();
1635
+
1636
+ // Initialize sounds
1637
+ initSounds();
1638
+
1639
+ // Initialize socket
1640
+ initSocket();
1641
+
1642
+ // Initialize event listeners
1643
+ initEventListeners();
1644
+
1645
+ // Request initial rooms list
1646
+ setTimeout(() => {
1647
+ if (socket) {
1648
+ socket.emit('get_rooms');
1649
+ }
1650
+ }, 500);
1651
+
1652
+ // Hide loading screen
1653
+ setTimeout(() => {
1654
+ if (elements.loadingScreen) {
1655
+ elements.loadingScreen.classList.add('hidden');
1656
+ }
1657
+ if (elements.app) {
1658
+ elements.app.classList.remove('hidden');
1659
+ }
1660
+ console.log('Chess Arena initialized successfully');
1661
+ }, 1500);
1662
+ }
1663
+
1664
+ // Start the app when DOM is ready
1665
+ if (document.readyState === 'loading') {
1666
+ document.addEventListener('DOMContentLoaded', init);
1667
+ } else {
1668
+ init();
1669
+ }
1670
+ })();