// /public/js/client.js document.addEventListener('DOMContentLoaded', () => { const socket = io({ // Опции Socket.IO, если нужны }); // --- Состояние клиента --- let currentGameState = null; let myPlayerId = null; // Технический ID слота ('player' или 'opponent') let myCharacterKey = null; // Ключ моего персонажа ('elena' или 'almagest') let opponentCharacterKey = null; // Ключ персонажа оппонента let currentGameId = null; let playerBaseStatsServer = null; // Статы персонажа, которым УПРАВЛЯЕТ этот клиент let opponentBaseStatsServer = null; // Статы персонажа-противника этого клиента let playerAbilitiesServer = null; // Способности персонажа, которым УПРАВЛЯЕТ этот клиент let opponentAbilitiesServer = null; // Способности персонажа-противника let isLoggedIn = false; let loggedInUsername = ''; // --- DOM Элементы --- // Аутентификация const authSection = document.getElementById('auth-section'); const registerForm = document.getElementById('register-form'); const loginForm = document.getElementById('login-form'); const authMessage = document.getElementById('auth-message'); const userInfoDiv = document.getElementById('user-info'); const loggedInUsernameSpan = document.getElementById('logged-in-username'); const logoutButton = document.getElementById('logout-button'); // Настройка игры const gameSetupDiv = document.getElementById('game-setup'); const createAIGameButton = document.getElementById('create-ai-game'); const createPvPGameButton = document.getElementById('create-pvp-game'); const joinPvPGameButton = document.getElementById('join-pvp-game'); const findRandomPvPGameButton = document.getElementById('find-random-pvp-game'); const gameIdInput = document.getElementById('game-id-input'); const availableGamesDiv = document.getElementById('available-games-list'); const gameStatusMessage = document.getElementById('game-status-message'); const pvpCharacterRadios = document.querySelectorAll('input[name="pvp-character"]'); // Игровая Арена const gameWrapper = document.querySelector('.game-wrapper'); const attackButton = document.getElementById('button-attack'); const restartGameButton = document.getElementById('restart-game-button'); const gameOverScreen = document.getElementById('game-over-screen'); console.log('Client.js DOMContentLoaded. Initializing elements...'); // --- Функции управления UI --- function showAuthScreen() { console.log('[UI] Showing Auth Screen'); if (authSection) authSection.style.display = 'block'; if (userInfoDiv) userInfoDiv.style.display = 'none'; if (gameSetupDiv) gameSetupDiv.style.display = 'none'; if (gameWrapper) gameWrapper.style.display = 'none'; hideGameOverModal(); setGameStatusMessage("Войдите или зарегистрируйтесь для начала игры."); setAuthMessage(""); } function showGameSelectionScreen(username) { console.log('[UI] Showing Game Selection Screen for:', username); if (authSection) authSection.style.display = 'none'; if (userInfoDiv) { userInfoDiv.style.display = 'block'; if(loggedInUsernameSpan) loggedInUsernameSpan.textContent = username; } if (gameSetupDiv) gameSetupDiv.style.display = 'block'; if (gameWrapper) gameWrapper.style.display = 'none'; hideGameOverModal(); setGameStatusMessage("Выберите режим игры или присоединитесь к существующей."); socket.emit('requestPvPGameList'); updateAvailableGamesList([]); if (gameIdInput) gameIdInput.value = ''; const elenaRadio = document.getElementById('char-elena'); if (elenaRadio) elenaRadio.checked = true; } function showGameScreen() { console.log('[UI] Showing Game Screen'); hideGameOverModal(); if (authSection) authSection.style.display = 'none'; if (userInfoDiv) userInfoDiv.style.display = 'block'; if (gameSetupDiv) gameSetupDiv.style.display = 'none'; if (gameWrapper) gameWrapper.style.display = 'flex'; setGameStatusMessage(""); } function hideGameOverModal() { const hiddenClass = (window.GAME_CONFIG && window.GAME_CONFIG.CSS_CLASS_HIDDEN) ? window.GAME_CONFIG.CSS_CLASS_HIDDEN : 'hidden'; if (gameOverScreen && !gameOverScreen.classList.contains(hiddenClass)) { gameOverScreen.classList.add(hiddenClass); if (window.gameUI && gameUI.uiElements && gameUI.uiElements.gameOver && gameUI.uiElements.gameOver.modalContent) { gameUI.uiElements.gameOver.modalContent.style.transform = 'scale(0.8) translateY(30px)'; gameUI.uiElements.gameOver.modalContent.style.opacity = '0'; } } } function setAuthMessage(message, isError = false) { if (authMessage) { authMessage.textContent = message; authMessage.className = isError ? 'error' : 'success'; authMessage.style.display = message ? 'block' : 'none'; } } function setGameStatusMessage(message, isError = false) { if (gameStatusMessage) { gameStatusMessage.textContent = message; gameStatusMessage.style.display = message ? 'block' : 'none'; gameStatusMessage.style.color = isError ? 'var(--damage-color, red)' : 'var(--turn-color, yellow)'; } } function getSelectedCharacterKey() { let selectedKey = 'elena'; if (pvpCharacterRadios) { pvpCharacterRadios.forEach(radio => { if (radio.checked) { selectedKey = radio.value; } }); } return selectedKey; } // --- Инициализация кнопок и обработчиков --- if (registerForm) { registerForm.addEventListener('submit', (e) => { e.preventDefault(); const usernameInput = document.getElementById('register-username'); const passwordInput = document.getElementById('register-password'); if (usernameInput && passwordInput) { socket.emit('register', { username: usernameInput.value, password: passwordInput.value }); } else { setAuthMessage("Ошибка: поля ввода не найдены.", true); } }); } if (loginForm) { loginForm.addEventListener('submit', (e) => { e.preventDefault(); const usernameInput = document.getElementById('login-username'); const passwordInput = document.getElementById('login-password'); if (usernameInput && passwordInput) { socket.emit('login', { username: usernameInput.value, password: passwordInput.value }); } else { setAuthMessage("Ошибка: поля ввода не найдены.", true); } }); } if (logoutButton) { logoutButton.addEventListener('click', () => { socket.emit('logout'); isLoggedIn = false; loggedInUsername = ''; currentGameId = null; currentGameState = null; myPlayerId = null; myCharacterKey = null; opponentCharacterKey = null; showAuthScreen(); setGameStatusMessage("Вы вышли из системы."); }); } if (createAIGameButton) { createAIGameButton.addEventListener('click', () => { if (!isLoggedIn) { setGameStatusMessage("Пожалуйста, войдите, чтобы начать игру.", true); return; } socket.emit('createGame', { mode: 'ai', characterKey: 'elena' }); setGameStatusMessage("Создание игры против AI..."); }); } if (createPvPGameButton) { createPvPGameButton.addEventListener('click', () => { if (!isLoggedIn) { setGameStatusMessage("Пожалуйста, войдите, чтобы начать игру.", true); return; } const selectedCharacter = getSelectedCharacterKey(); socket.emit('createGame', { mode: 'pvp', characterKey: selectedCharacter }); setGameStatusMessage(`Создание PvP игры за ${selectedCharacter === 'elena' ? 'Елену' : 'Альмагест'}...`); }); } if (joinPvPGameButton && gameIdInput) { joinPvPGameButton.addEventListener('click', () => { if (!isLoggedIn) { setGameStatusMessage("Пожалуйста, войдите, чтобы присоединиться к игре.", true); return; } const gameIdToJoin = gameIdInput.value.trim(); if (gameIdToJoin) { socket.emit('joinGame', { gameId: gameIdToJoin }); setGameStatusMessage(`Присоединение к игре ${gameIdToJoin}...`); } else { setGameStatusMessage("Пожалуйста, введите ID игры для присоединения.", true); } }); } if (findRandomPvPGameButton) { findRandomPvPGameButton.addEventListener('click', () => { if (!isLoggedIn) { setGameStatusMessage("Пожалуйста, войдите, чтобы найти игру.", true); return; } const selectedCharacter = getSelectedCharacterKey(); socket.emit('findRandomGame', { characterKey: selectedCharacter }); setGameStatusMessage(`Поиск случайной PvP игры (предпочтение: ${selectedCharacter === 'elena' ? 'Елена' : 'Альмагест'})...`); }); } if (attackButton) { attackButton.addEventListener('click', () => { if (currentGameId && currentGameState && !currentGameState.isGameOver && isLoggedIn) { socket.emit('playerAction', { actionType: 'attack' }); } }); } function handleAbilityButtonClick(event) { const button = event.currentTarget; const abilityId = button.dataset.abilityId; if (currentGameId && abilityId && currentGameState && !currentGameState.isGameOver && isLoggedIn) { socket.emit('playerAction', { actionType: 'ability', abilityId: abilityId }); } } if (restartGameButton) { restartGameButton.addEventListener('click', () => { if (currentGameId && currentGameState && currentGameState.isGameOver && isLoggedIn) { socket.emit('requestRestart', { gameId: currentGameId }); setGameStatusMessage("Запрос на рестарт отправлен..."); restartGameButton.disabled = true; } else { if (!currentGameId && isLoggedIn) { alert("Ошибка: ID текущей игры не определен. Невозможно запросить рестарт."); showGameSelectionScreen(loggedInUsername); } else if (!isLoggedIn) { showAuthScreen(); } } }); } // --- Функции для UI игры --- function initializeAbilityButtons() { const abilitiesGrid = document.getElementById('abilities-grid'); if (!abilitiesGrid || !window.gameUI || !window.GAME_CONFIG) { if(abilitiesGrid) abilitiesGrid.innerHTML = '

Ошибка загрузки способностей.

'; return; } abilitiesGrid.innerHTML = ''; const config = window.GAME_CONFIG; const abilitiesToDisplay = playerAbilitiesServer; const baseStatsForResource = playerBaseStatsServer; if (!abilitiesToDisplay || abilitiesToDisplay.length === 0 || !baseStatsForResource) { abilitiesGrid.innerHTML = '

Нет доступных способностей.

'; return; } const resourceName = baseStatsForResource.resourceName || "Ресурс"; abilitiesToDisplay.forEach(ability => { const button = document.createElement('button'); button.id = `ability-btn-${ability.id}`; button.classList.add(config.CSS_CLASS_ABILITY_BUTTON || 'ability-button'); button.dataset.abilityId = ability.id; let descriptionText = ability.description; if (typeof ability.descriptionFunction === 'function') { const targetStatsForDesc = opponentBaseStatsServer; descriptionText = ability.descriptionFunction(config, targetStatsForDesc); } let title = `${ability.name} (${ability.cost} ${resourceName}) - ${descriptionText}`; let cooldown = ability.cooldown; if (ability.internalCooldownFromConfig && config[ability.internalCooldownFromConfig]) { cooldown = config[ability.internalCooldownFromConfig]; } else if (ability.internalCooldownValue) { cooldown = ability.internalCooldownValue; } if (cooldown) title += ` (КД: ${cooldown} х.)`; button.setAttribute('title', title); const nameSpan = document.createElement('span'); nameSpan.classList.add('ability-name'); nameSpan.textContent = ability.name; button.appendChild(nameSpan); const descSpan = document.createElement('span'); descSpan.classList.add('ability-desc'); descSpan.textContent = `(${ability.cost} ${resourceName})`; button.appendChild(descSpan); const cdDisplay = document.createElement('span'); cdDisplay.classList.add('ability-cooldown-display'); cdDisplay.style.display = 'none'; button.appendChild(cdDisplay); button.addEventListener('click', handleAbilityButtonClick); abilitiesGrid.appendChild(button); }); } function updateAvailableGamesList(games) { if (!availableGamesDiv) return; availableGamesDiv.innerHTML = '

Доступные PvP игры:

'; if (games && games.length > 0) { const ul = document.createElement('ul'); games.forEach(game => { if (game && game.id) { const li = document.createElement('li'); li.textContent = `ID: ${game.id.substring(0, 8)}... - ${game.status || 'Ожидает игрока'}`; const joinBtn = document.createElement('button'); joinBtn.textContent = 'Присоединиться'; joinBtn.dataset.gameId = game.id; joinBtn.addEventListener('click', (e) => { if (!isLoggedIn) { setGameStatusMessage("Пожалуйста, войдите, чтобы присоединиться к игре.", true); return; } socket.emit('joinGame', { gameId: e.target.dataset.gameId }); }); li.appendChild(joinBtn); ul.appendChild(li); } }); availableGamesDiv.appendChild(ul); } else { availableGamesDiv.innerHTML += '

Нет доступных игр. Создайте свою!

'; } } // --- Обработчики событий Socket.IO --- socket.on('connect', () => { console.log('[Client] Socket connected to server! Socket ID:', socket.id); if (!isLoggedIn) { showAuthScreen(); } else { showGameSelectionScreen(loggedInUsername); } }); socket.on('disconnect', (reason) => { console.log('[Client] Disconnected from server:', reason); setGameStatusMessage(`Отключено от сервера: ${reason}. Попробуйте обновить страницу.`, true); if (currentGameId) { currentGameState = null; currentGameId = null; myPlayerId = null; myCharacterKey = null; opponentCharacterKey = null; if (isLoggedIn && gameWrapper && gameWrapper.style.display !== 'none') { showGameSelectionScreen(loggedInUsername); } else if (!isLoggedIn){ showAuthScreen(); } } hideGameOverModal(); }); socket.on('registerResponse', (data) => { setAuthMessage(data.message, !data.success); if (data.success && registerForm) registerForm.reset(); }); socket.on('loginResponse', (data) => { setAuthMessage(data.message, !data.success); if (data.success) { isLoggedIn = true; loggedInUsername = data.username; setAuthMessage(""); showGameSelectionScreen(data.username); } else { isLoggedIn = false; loggedInUsername = ''; } }); socket.on('gameCreated', (data) => { if (!isLoggedIn) return; currentGameId = data.gameId; myPlayerId = data.yourPlayerId; console.log(`[Client] Game created/joined: ${currentGameId}, Mode: ${data.mode}, You (${loggedInUsername}) are in slot: ${myPlayerId}`); if (data.mode === 'pvp') { if (gameIdInput) gameIdInput.value = currentGameId; setGameStatusMessage(`PvP игра ${currentGameId.substring(0,8)}... создана. ID для друга: ${currentGameId}. Ожидание второго игрока...`); } else { setGameStatusMessage(`Игра против AI ${currentGameId.substring(0,8)}... создана. Ожидание начала...`); } }); socket.on('gameStarted', (data) => { if (!isLoggedIn) return; console.log('[Client] Event "gameStarted" received:', data); currentGameId = data.gameId; myPlayerId = data.yourPlayerId; currentGameState = data.initialGameState; playerBaseStatsServer = data.playerBaseStats; opponentBaseStatsServer = data.opponentBaseStats; playerAbilitiesServer = data.playerAbilities; opponentAbilitiesServer = data.opponentAbilities; myCharacterKey = playerBaseStatsServer?.characterKey; opponentCharacterKey = opponentBaseStatsServer?.characterKey; console.log(`[Client] Game started! My Slot ID: ${myPlayerId}, My Character: ${myCharacterKey}, Opponent Character: ${opponentCharacterKey}`); if (data.clientConfig) { window.GAME_CONFIG = { ...data.clientConfig }; } else if (!window.GAME_CONFIG) { window.GAME_CONFIG = { PLAYER_ID: 'player', OPPONENT_ID: 'opponent', CSS_CLASS_HIDDEN: 'hidden' }; } window.gameState = currentGameState; window.gameData = { playerBaseStats: playerBaseStatsServer, opponentBaseStats: opponentBaseStatsServer, playerAbilities: playerAbilitiesServer, opponentAbilities: opponentAbilitiesServer }; window.myPlayerId = myPlayerId; showGameScreen(); initializeAbilityButtons(); if (window.gameUI && gameUI.uiElements?.log?.list) { gameUI.uiElements.log.list.innerHTML = ''; } if (window.gameUI && typeof gameUI.addToLog === 'function' && data.log) { data.log.forEach(logEntry => gameUI.addToLog(logEntry.message, logEntry.type)); } if (window.gameUI && typeof gameUI.updateUI === 'function') { gameUI.updateUI(); } hideGameOverModal(); if (restartGameButton) { restartGameButton.disabled = true; restartGameButton.dataset.gameIdForRestart = ''; } setGameStatusMessage(""); }); socket.on('gameStateUpdate', (data) => { if (!isLoggedIn || !currentGameId) return; currentGameState = data.gameState; window.gameState = currentGameState; if (window.gameUI && typeof gameUI.updateUI === 'function') { gameUI.updateUI(); } if (window.gameUI && typeof gameUI.addToLog === 'function' && data.log) { data.log.forEach(logEntry => gameUI.addToLog(logEntry.message, logEntry.type)); } }); socket.on('logUpdate', (data) => { if (!isLoggedIn || !currentGameId) return; if (window.gameUI && typeof gameUI.addToLog === 'function' && data.log) { data.log.forEach(logEntry => gameUI.addToLog(logEntry.message, logEntry.type)); } }); socket.on('gameOver', (data) => { if (!isLoggedIn || !currentGameId) return; console.log('[Client] Game over:', data); currentGameState = data.finalGameState; window.gameState = currentGameState; if (window.gameUI && typeof gameUI.updateUI === 'function') gameUI.updateUI(); if (window.gameUI && typeof gameUI.addToLog === 'function' && data.log) { data.log.forEach(logEntry => gameUI.addToLog(logEntry.message, logEntry.type)); } if (window.gameUI && typeof gameUI.showGameOver === 'function') { const playerWon = data.winnerId === myPlayerId; gameUI.showGameOver(playerWon, data.reason); if (restartGameButton) { restartGameButton.disabled = false; restartGameButton.dataset.gameIdForRestart = currentGameId; } } setGameStatusMessage("Игра окончена. " + (data.winnerId === myPlayerId ? "Вы победили!" : "Вы проиграли.")); }); socket.on('waitingForOpponent', () => { if (!isLoggedIn) return; setGameStatusMessage("Ожидание присоединения оппонента..."); }); socket.on('opponentDisconnected', (data) => { if (!isLoggedIn || !currentGameId) return; const systemLogType = (window.GAME_CONFIG?.LOG_TYPE_SYSTEM) || 'system'; if (window.gameUI && typeof gameUI.addToLog === 'function') { gameUI.addToLog("Противник отключился.", systemLogType); } if (currentGameState && !currentGameState.isGameOver) { setGameStatusMessage("Противник отключился. Игра завершена. Вы можете начать новую.", true); } }); socket.on('turnNotification', (data) => { // console.log("[Client] Turn notification for slot:", data.currentTurn, "My Slot ID is:", myPlayerId); }); socket.on('waitingForRestartVote', (data) => { if (!isLoggedIn || !currentGameId) return; const systemLogType = (window.GAME_CONFIG?.LOG_TYPE_SYSTEM) || 'system'; const voterName = data.voterCharacterName || 'Игрок'; if (window.gameUI && typeof gameUI.addToLog === 'function') { gameUI.addToLog( `${voterName} (${data.voterRole}) проголосовал(а) за рестарт. Нужно еще ${data.votesNeeded} голосов.`, systemLogType ); } setGameStatusMessage(`Игрок ${voterName} проголосовал за рестарт. Ожидание вашего решения или решения другого игрока.`); if (restartGameButton && currentGameState?.isGameOver) { restartGameButton.disabled = false; } }); socket.on('gameError', (data) => { console.error('[Client] Server error:', data.message); const systemLogType = (window.GAME_CONFIG?.LOG_TYPE_SYSTEM) || 'system'; if (isLoggedIn && window.gameUI && typeof gameUI.addToLog === 'function' && currentGameId && currentGameState && !currentGameState.isGameOver) { gameUI.addToLog(`Ошибка: ${data.message}`, systemLogType); } setGameStatusMessage(`Ошибка: ${data.message}`, true); }); socket.on('availablePvPGamesList', (games) => { if (!isLoggedIn) return; updateAvailableGamesList(games); }); socket.on('noPendingGamesFound', (data) => { if (!isLoggedIn) return; setGameStatusMessage(data.message || "Свободных игр не найдено. Создана новая для вас, ожидайте оппонента."); updateAvailableGamesList([]); if (data.gameId) { currentGameId = data.gameId; myPlayerId = data.yourPlayerId; if (gameIdInput) gameIdInput.value = currentGameId; console.log(`[Client] New game ${currentGameId} created after no pending games found. My slot: ${myPlayerId}`); } }); // --- Начальное состояние UI --- showAuthScreen(); });