bc/public/js/client.js

629 lines
36 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// /public/js/client.js
document.addEventListener('DOMContentLoaded', () => {
const socket = io({
// Опции Socket.IO, если нужны
});
// --- Состояние клиента ---
let currentGameState = null;
let myPlayerId = null;
let myCharacterKey = null;
let opponentCharacterKey = null;
let currentGameId = null;
let playerBaseStatsServer = null;
let opponentBaseStatsServer = null;
let playerAbilitiesServer = null;
let opponentAbilitiesServer = null;
let isLoggedIn = false;
let loggedInUsername = '';
let isInGame = false;
// --- 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 statusContainer = document.getElementById('status-container');
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'); // Опечатка в ID, должно быть 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 returnToMenuButton = document.getElementById('return-to-menu-button');
const gameOverScreen = document.getElementById('game-over-screen');
const abilitiesGrid = document.getElementById('abilities-grid');
// === ИЗМЕНЕНИЕ: DOM элемент для таймера ===
const turnTimerSpan = document.getElementById('turn-timer'); // Элемент для отображения времени
const turnTimerContainer = document.getElementById('turn-timer-container'); // Контейнер таймера для управления видимостью
// === КОНЕЦ ИЗМЕНЕНИЯ ===
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();
setAuthMessage("Ожидание подключения к серверу...");
if (statusContainer) statusContainer.style.display = 'block';
isInGame = false;
disableGameControls();
resetGameVariables();
// === ИЗМЕНЕНИЕ: Скрываем таймер при выходе на экран аутентификации ===
if (turnTimerContainer) turnTimerContainer.style.display = 'none';
if (turnTimerSpan) turnTimerSpan.textContent = '--';
// === КОНЕЦ ИЗМЕНЕНИЯ ===
}
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("Выберите режим игры или присоединитесь к существующей.");
if (statusContainer) statusContainer.style.display = 'block';
socket.emit('requestPvPGameList');
updateAvailableGamesList([]);
if (gameIdInput) gameIdInput.value = '';
const elenaRadio = document.getElementById('char-elena');
if (elenaRadio) elenaRadio.checked = true;
isInGame = false;
disableGameControls();
resetGameVariables();
// === ИЗМЕНЕНИЕ: Скрываем таймер при выходе на экран выбора игры ===
if (turnTimerContainer) turnTimerContainer.style.display = 'none';
if (turnTimerSpan) turnTimerSpan.textContent = '--';
// === КОНЕЦ ИЗМЕНЕНИЯ ===
}
function showGameScreen() {
console.log('[UI] Showing Game Screen');
hideGameOverModal();
if (authSection) authSection.style.display = 'none';
if (userInfoDiv) userInfoDiv.style.display = 'block'; // Оставляем видимым, чтобы видеть "Привет, username"
if (gameSetupDiv) gameSetupDiv.style.display = 'none';
if (gameWrapper) gameWrapper.style.display = 'flex';
setGameStatusMessage("");
if (statusContainer) statusContainer.style.display = 'none';
isInGame = true;
disableGameControls();
// === ИЗМЕНЕНИЕ: Показываем контейнер таймера, когда игра начинается ===
if (turnTimerContainer) turnTimerContainer.style.display = 'block';
if (turnTimerSpan) turnTimerSpan.textContent = '--'; // Начальное значение
// === КОНЕЦ ИЗМЕНЕНИЯ ===
}
function resetGameVariables() {
currentGameId = null;
currentGameState = null;
myPlayerId = null;
myCharacterKey = null;
opponentCharacterKey = null;
playerBaseStatsServer = null;
opponentBaseStatsServer = null;
playerAbilitiesServer = null;
opponentAbilitiesServer = null;
window.gameState = null;
window.gameData = null;
window.myPlayerId = null;
}
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)) {
console.log('[Client.js DEBUG] Hiding GameOver Modal.');
gameOverScreen.classList.add(hiddenClass);
if (window.gameUI?.uiElements?.gameOver?.modalContent) {
window.gameUI.uiElements.gameOver.modalContent.style.transform = 'scale(0.8) translateY(30px)';
window.gameUI.uiElements.gameOver.modalContent.style.opacity = '0';
}
if (window.gameUI?.uiElements?.opponent?.panel) {
const opponentPanel = window.gameUI.uiElements.opponent.panel;
if (opponentPanel.classList.contains('dissolving')) {
opponentPanel.classList.remove('dissolving');
opponentPanel.style.opacity = '1';
opponentPanel.style.transform = 'scale(1) translateY(0)';
}
}
}
}
function setAuthMessage(message, isError = false) {
if (authMessage) {
authMessage.textContent = message;
authMessage.className = isError ? 'error' : 'success';
authMessage.style.display = message ? 'block' : 'none';
}
if (message && gameStatusMessage) gameStatusMessage.style.display = '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)';
if (statusContainer) statusContainer.style.display = message ? 'block' : 'none';
}
if (message && authMessage) authMessage.style.display = 'none';
}
function getSelectedCharacterKey() {
let selectedKey = 'elena';
if (pvpCharacterRadios) {
pvpCharacterRadios.forEach(radio => { if (radio.checked) selectedKey = radio.value; });
}
return selectedKey;
}
function enableGameControls(enableAttack = true, enableAbilities = true) {
if (attackButton) attackButton.disabled = !enableAttack;
if (abilitiesGrid) {
const abilityButtonClass = window.GAME_CONFIG?.CSS_CLASS_ABILITY_BUTTON || 'ability-button';
abilitiesGrid.querySelectorAll(`.${abilityButtonClass}`).forEach(button => { button.disabled = !enableAbilities; });
}
if (window.gameUI?.uiElements?.controls?.buttonBlock) window.gameUI.uiElements.controls.buttonBlock.disabled = true;
}
function disableGameControls() {
enableGameControls(false, false);
}
// Инициализация кнопок и обработчиков
if (registerForm) {
registerForm.addEventListener('submit', (e) => {
e.preventDefault();
const usernameInput = document.getElementById('register-username');
const passwordInput = document.getElementById('register-password');
if (usernameInput && passwordInput) {
registerForm.querySelector('button').disabled = true;
if (loginForm) loginForm.querySelector('button').disabled = true;
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) {
if (registerForm) registerForm.querySelector('button').disabled = true;
loginForm.querySelector('button').disabled = true;
socket.emit('login', { username: usernameInput.value, password: passwordInput.value });
} else { setAuthMessage("Ошибка: поля ввода не найдены.", true); }
});
}
if (logoutButton) {
logoutButton.addEventListener('click', () => {
logoutButton.disabled = true;
socket.emit('logout');
isLoggedIn = false; loggedInUsername = '';
resetGameVariables(); isInGame = false; disableGameControls();
showAuthScreen();
setGameStatusMessage("Вы вышли из системы.");
logoutButton.disabled = false;
});
}
if (createAIGameButton) {
createAIGameButton.addEventListener('click', () => {
if (!isLoggedIn) { setGameStatusMessage("Пожалуйста, войдите, чтобы начать игру.", true); return; }
disableSetupButtons();
socket.emit('createGame', { mode: 'ai', characterKey: 'elena' });
setGameStatusMessage("Создание игры против AI...");
});
}
if (createPvPGameButton) {
createPvPGameButton.addEventListener('click', () => {
if (!isLoggedIn) { setGameStatusMessage("Пожалуйста, войдите, чтобы начать игру.", true); return; }
disableSetupButtons();
const selectedCharacter = getSelectedCharacterKey();
socket.emit('createGame', { mode: 'pvp', characterKey: selectedCharacter });
setGameStatusMessage(`Создание PvP игры за ${selectedCharacter === 'elena' ? 'Елену' : 'Альмагест'}...`);
});
}
// Исправляем селектор для joinPvPGameButton, если ID в HTML был join-pvP-game
const actualJoinPvPGameButton = document.getElementById('join-pvp-game') || document.getElementById('join-pvP-game');
if (actualJoinPvPGameButton && gameIdInput) {
actualJoinPvPGameButton.addEventListener('click', () => {
if (!isLoggedIn) { setGameStatusMessage("Пожалуйста, войдите, чтобы присоединиться к игре.", true); return; }
const gameIdToJoin = gameIdInput.value.trim();
if (gameIdToJoin) {
disableSetupButtons();
socket.emit('joinGame', { gameId: gameIdToJoin });
setGameStatusMessage(`Присоединение к игре ${gameIdToJoin}...`);
} else { setGameStatusMessage("Пожалуйста, введите ID игры для присоединения.", true); }
});
}
if (findRandomPvPGameButton) {
findRandomPvPGameButton.addEventListener('click', () => {
if (!isLoggedIn) { setGameStatusMessage("Пожалуйста, войдите, чтобы найти игру.", true); return; }
disableSetupButtons();
const selectedCharacter = getSelectedCharacterKey();
socket.emit('findRandomGame', { characterKey: selectedCharacter });
setGameStatusMessage(`Поиск случайной PvP игры (предпочтение: ${selectedCharacter === 'elena' ? 'Елена' : 'Альмагест'})...`);
});
}
function disableSetupButtons() {
if (createAIGameButton) createAIGameButton.disabled = true;
if (createPvPGameButton) createPvPGameButton.disabled = true;
if (actualJoinPvPGameButton) actualJoinPvPGameButton.disabled = true;
if (findRandomPvPGameButton) findRandomPvPGameButton.disabled = true;
if (availableGamesDiv) availableGamesDiv.querySelectorAll('button').forEach(btn => btn.disabled = true);
}
function enableSetupButtons() {
if (createAIGameButton) createAIGameButton.disabled = false;
if (createPvPGameButton) createPvPGameButton.disabled = false;
if (actualJoinPvPGameButton) actualJoinPvPGameButton.disabled = false;
if (findRandomPvPGameButton) findRandomPvPGameButton.disabled = false;
}
if (attackButton) {
attackButton.addEventListener('click', () => {
if (isLoggedIn && isInGame && currentGameId && currentGameState && !currentGameState.isGameOver) {
socket.emit('playerAction', { actionType: 'attack' });
} else {
console.warn('[Client] Попытка действия (атака) вне допустимого состояния игры. isLogged:', isLoggedIn, 'isInGame:', isInGame);
disableGameControls();
if (isLoggedIn && !isInGame) showGameSelectionScreen(loggedInUsername);
else if (!isLoggedIn) showAuthScreen();
}
});
}
function handleAbilityButtonClick(event) {
const button = event.currentTarget;
const abilityId = button.dataset.abilityId;
if (isLoggedIn && isInGame && currentGameId && abilityId && currentGameState && !currentGameState.isGameOver) {
socket.emit('playerAction', { actionType: 'ability', abilityId: abilityId });
} else {
console.warn('[Client] Попытка действия (способность) вне допустимого состояния игры. isLogged:', isLoggedIn, 'isInGame:', isInGame);
disableGameControls();
if (isLoggedIn && !isInGame) showGameSelectionScreen(loggedInUsername);
else if (!isLoggedIn) showAuthScreen();
}
}
if (returnToMenuButton) {
returnToMenuButton.addEventListener('click', () => {
if (!isLoggedIn) { showAuthScreen(); return; }
returnToMenuButton.disabled = true;
console.log('[Client] Return to menu button clicked. Resetting game state and showing selection screen.');
resetGameVariables(); isInGame = false; disableGameControls(); hideGameOverModal();
showGameSelectionScreen(loggedInUsername);
});
}
function initializeAbilityButtons() {
if (!abilitiesGrid || !window.gameUI || !window.GAME_CONFIG) {
if (abilitiesGrid) abilitiesGrid.innerHTML = '<p class="placeholder-text">Ошибка загрузки способностей.</p>';
console.error('[Client.js] initializeAbilityButtons failed: abilitiesGrid, gameUI, or GAME_CONFIG not found.');
return;
}
abilitiesGrid.innerHTML = '';
const config = window.GAME_CONFIG;
const abilitiesToDisplay = playerAbilitiesServer;
const baseStatsForResource = playerBaseStatsServer;
if (!abilitiesToDisplay || abilitiesToDisplay.length === 0 || !baseStatsForResource) {
abilitiesGrid.innerHTML = '<p class="placeholder-text">Нет доступных способностей.</p>';
return;
}
const resourceName = baseStatsForResource.resourceName || "Ресурс";
const abilityButtonClass = config.CSS_CLASS_ABILITY_BUTTON || 'ability-button';
abilitiesToDisplay.forEach(ability => {
const button = document.createElement('button');
button.id = `ability-btn-${ability.id}`;
button.classList.add(abilityButtonClass);
button.dataset.abilityId = ability.id;
let cooldown = ability.cooldown;
let cooldownText = (typeof cooldown === 'number' && cooldown > 0) ? ` (КД: ${cooldown} х.)` : "";
let title = `${ability.name} (${ability.cost} ${resourceName})${cooldownText} - ${ability.description || 'Нет описания'}`;
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);
});
const placeholder = abilitiesGrid.querySelector('.placeholder-text');
if (placeholder) placeholder.remove();
}
function updateAvailableGamesList(games) {
if (!availableGamesDiv) return;
availableGamesDiv.innerHTML = '<h3>Доступные PvP игры:</h3>';
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; }
disableSetupButtons();
socket.emit('joinGame', { gameId: e.target.dataset.gameId });
});
li.appendChild(joinBtn); ul.appendChild(li);
}
});
availableGamesDiv.appendChild(ul);
availableGamesDiv.querySelectorAll('button').forEach(btn => btn.disabled = false);
} else { availableGamesDiv.innerHTML += '<p>Нет доступных игр. Создайте свою!</p>'; }
enableSetupButtons();
}
// --- Обработчики событий Socket.IO ---
socket.on('connect', () => {
console.log('[Client] Socket connected to server! Socket ID:', socket.id);
if (isLoggedIn) {
console.log(`[Client] Reconnected as ${loggedInUsername}. Requesting state.`);
socket.emit('requestGameState');
} else { showAuthScreen(); }
});
socket.on('registerResponse', (data) => {
setAuthMessage(data.message, !data.success);
if (data.success && registerForm) registerForm.reset();
if (registerForm) registerForm.querySelector('button').disabled = false;
if (loginForm) loginForm.querySelector('button').disabled = false;
});
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 = '';
if (registerForm) registerForm.querySelector('button').disabled = false;
if (loginForm) loginForm.querySelector('button').disabled = false;
}
});
socket.on('gameNotFound', (data) => {
console.log('[Client] Game not found response:', data?.message);
resetGameVariables(); isInGame = false; disableGameControls(); hideGameOverModal();
if (turnTimerContainer) turnTimerContainer.style.display = 'none'; // Скрываем таймер
if (isLoggedIn) {
showGameSelectionScreen(loggedInUsername);
setGameStatusMessage("Выберите режим игры или присоединитесь к существующей.");
enableSetupButtons();
} else {
showAuthScreen();
setAuthMessage(data?.message || "Пожалуйста, войдите, чтобы начать новую игру.", false);
}
});
socket.on('disconnect', (reason) => {
console.log('[Client] Disconnected from server:', reason);
setGameStatusMessage(`Отключено от сервера: ${reason}. Пожалуйста, обновите страницу.`, true);
disableGameControls();
// === ИЗМЕНЕНИЕ: При дисконнекте останавливаем таймер (если он виден) ===
if (turnTimerSpan) turnTimerSpan.textContent = 'Отключено';
// Не скрываем контейнер, чтобы было видно сообщение "Отключено"
// === КОНЕЦ ИЗМЕНЕНИЯ ===
});
socket.on('gameStarted', (data) => {
if (!isLoggedIn) { console.warn('[Client] Ignoring gameStarted: Not logged in.'); return; }
console.log('[Client] Event "gameStarted" received:', data);
if (window.gameUI?.uiElements?.opponent?.panel) {
const opponentPanel = window.gameUI.uiElements.opponent.panel;
if (opponentPanel.classList.contains('dissolving')) {
opponentPanel.classList.remove('dissolving');
opponentPanel.style.opacity = '1'; opponentPanel.style.transform = 'scale(1) translateY(0)';
}
}
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;
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' };
console.warn('[Client.js gameStarted] No clientConfig received from server. Using fallback.');
}
window.gameState = currentGameState;
window.gameData = { playerBaseStats: playerBaseStatsServer, opponentBaseStats: opponentBaseStatsServer, playerAbilities: playerAbilitiesServer, opponentAbilities: opponentAbilitiesServer };
window.myPlayerId = myPlayerId;
showGameScreen(); initializeAbilityButtons();
if (window.gameUI?.uiElements?.log?.list) window.gameUI.uiElements.log.list.innerHTML = '';
if (window.gameUI && typeof window.gameUI.addToLog === 'function' && data.log) {
data.log.forEach(logEntry => window.gameUI.addToLog(logEntry.message, logEntry.type));
}
requestAnimationFrame(() => {
if (window.gameUI && typeof window.gameUI.updateUI === 'function') {
console.log('[Client] Calling gameUI.updateUI() after gameStarted.');
window.gameUI.updateUI();
}
});
hideGameOverModal(); setGameStatusMessage("");
});
socket.on('gameStateUpdate', (data) => {
if (!isLoggedIn || !isInGame || !currentGameId || !window.GAME_CONFIG) {
console.warn('[Client] Ignoring gameStateUpdate: Not logged in or not in game context.');
return;
}
currentGameState = data.gameState; window.gameState = currentGameState;
if (window.gameUI && typeof window.gameUI.updateUI === 'function') window.gameUI.updateUI();
if (window.gameUI && typeof window.gameUI.addToLog === 'function' && data.log) {
data.log.forEach(logEntry => window.gameUI.addToLog(logEntry.message, logEntry.type));
}
});
socket.on('logUpdate', (data) => {
if (!isLoggedIn || !isInGame || !currentGameId || !window.GAME_CONFIG) {
console.warn('[Client] Ignoring logUpdate: Not logged in or not in game context.');
return;
}
if (window.gameUI && typeof window.gameUI.addToLog === 'function' && data.log) {
data.log.forEach(logEntry => window.gameUI.addToLog(logEntry.message, logEntry.type));
}
});
socket.on('gameOver', (data) => {
if (!isLoggedIn || !currentGameId || !window.GAME_CONFIG) {
console.warn('[Client] Ignoring gameOver: Not logged in or currentGameId is null/stale.');
if (!currentGameId && isLoggedIn) socket.emit('requestGameState');
else if (!isLoggedIn) showAuthScreen();
return;
}
console.log(`[Client gameOver] Received for game ${currentGameId}. My technical slot ID (myPlayerId): ${myPlayerId}, Winner's slot ID from server (data.winnerId): ${data.winnerId}`);
const playerWon = data.winnerId === myPlayerId;
console.log(`[Client gameOver] Calculated playerWon for this client: ${playerWon}`);
currentGameState = data.finalGameState; window.gameState = currentGameState;
console.log('[Client gameOver] Final GameState:', currentGameState);
if (window.gameData) console.log(`[Client gameOver] For ui.js, myName: ${window.gameData.playerBaseStats?.name}, opponentName: ${window.gameData.opponentBaseStats?.name}`);
if (window.gameUI && typeof window.gameUI.updateUI === 'function') window.gameUI.updateUI();
if (window.gameUI && typeof window.gameUI.addToLog === 'function' && data.log) {
data.log.forEach(logEntry => window.gameUI.addToLog(logEntry.message, logEntry.type));
}
if (window.gameUI && typeof window.gameUI.showGameOver === 'function') {
const opponentKeyForModal = window.gameData?.opponentBaseStats?.characterKey;
window.gameUI.showGameOver(playerWon, data.reason, opponentKeyForModal, data);
}
if (returnToMenuButton) returnToMenuButton.disabled = false;
setGameStatusMessage("Игра окончена. " + (playerWon ? "Вы победили!" : "Вы проиграли."));
// === ИЗМЕНЕНИЕ: При gameOver скрываем таймер или показываем "Игра окончена" ===
if (turnTimerContainer) turnTimerContainer.style.display = 'block'; // Оставляем видимым
if (turnTimerSpan) turnTimerSpan.textContent = 'Конец';
// === КОНЕЦ ИЗМЕНЕНИЯ ===
});
socket.on('waitingForOpponent', () => {
if (!isLoggedIn) return;
setGameStatusMessage("Ожидание присоединения оппонента...");
disableGameControls();
enableSetupButtons(); // Можно оставить возможность отменить, если долго ждет
// === ИЗМЕНЕНИЕ: При ожидании оппонента таймер неактивен ===
if (turnTimerContainer) turnTimerContainer.style.display = 'none';
if (turnTimerSpan) turnTimerSpan.textContent = '--';
// === КОНЕЦ ИЗМЕНЕНИЯ ===
});
socket.on('opponentDisconnected', (data) => {
if (!isLoggedIn || !isInGame || !currentGameId || !window.GAME_CONFIG) {
console.warn('[Client] Ignoring opponentDisconnected: Not logged in or not in game context.');
return;
}
const systemLogType = (window.GAME_CONFIG?.LOG_TYPE_SYSTEM) || 'system';
const disconnectedCharacterName = data.disconnectedCharacterName || 'Противник';
if (window.gameUI && typeof window.gameUI.addToLog === 'function') {
window.gameUI.addToLog(`🔌 Противник (${disconnectedCharacterName}) отключился.`, systemLogType);
}
if (currentGameState && !currentGameState.isGameOver) {
setGameStatusMessage(`Противник (${disconnectedCharacterName}) отключился. Ожидание завершения игры сервером...`, true);
disableGameControls();
}
});
socket.on('gameError', (data) => {
console.error('[Client] Server error:', data.message);
const systemLogType = (window.GAME_CONFIG?.LOG_TYPE_SYSTEM) || 'system';
if (isLoggedIn && isInGame && currentGameId && currentGameState && !currentGameState.isGameOver && window.gameUI && typeof window.gameUI.addToLog === 'function') {
window.gameUI.addToLog(`❌ Ошибка игры: ${data.message}`, systemLogType);
disableGameControls();
setGameStatusMessage(`Ошибка в игре: ${data.message}.`, true);
} else {
setGameStatusMessage(`❌ Ошибка игры: ${data.message}`, true);
resetGameVariables(); isInGame = false; disableGameControls();
if (isLoggedIn && loggedInUsername) showGameSelectionScreen(loggedInUsername);
else showAuthScreen();
}
if (!isLoggedIn) {
if (registerForm) registerForm.querySelector('button').disabled = false;
if (loginForm) loginForm.querySelector('button').disabled = false;
} else if (!isInGame) { enableSetupButtons(); }
});
socket.on('availablePvPGamesList', (games) => {
if (!isLoggedIn) return;
updateAvailableGamesList(games);
});
socket.on('noPendingGamesFound', (data) => {
if (!isLoggedIn) return;
setGameStatusMessage(data.message || "Свободных игр не найдено. Создана новая для вас, ожидайте оппонента.");
updateAvailableGamesList([]);
isInGame = false; disableGameControls(); disableSetupButtons();
// === ИЗМЕНЕНИЕ: При ожидании оппонента (создана новая игра) таймер неактивен ===
if (turnTimerContainer) turnTimerContainer.style.display = 'none';
if (turnTimerSpan) turnTimerSpan.textContent = '--';
// === КОНЕЦ ИЗМЕНЕНИЯ ===
});
// === ИЗМЕНЕНИЕ: Обработчик события обновления таймера ===
socket.on('turnTimerUpdate', (data) => {
if (!isInGame || !currentGameState || currentGameState.isGameOver) {
// Если игра не активна, или уже завершена, или нет состояния, игнорируем обновление таймера
if (turnTimerContainer && !currentGameState?.isGameOver) turnTimerContainer.style.display = 'none'; // Скрываем, если не game over
if (turnTimerSpan && !currentGameState?.isGameOver) turnTimerSpan.textContent = '--';
return;
}
if (turnTimerSpan && turnTimerContainer) {
if (data.remainingTime === null || data.remainingTime === undefined) {
// Сервер сигнализирует, что таймер неактивен (например, ход AI)
turnTimerContainer.style.display = 'block'; // Контейнер может быть видимым
// Определяем, чей ход, чтобы показать соответствующее сообщение
const isMyActualTurn = myPlayerId && currentGameState.isPlayerTurn === (myPlayerId === GAME_CONFIG.PLAYER_ID);
if (!data.isPlayerTurn && currentGameState.gameMode === 'ai') { // Ход AI
turnTimerSpan.textContent = 'Ход ИИ';
turnTimerSpan.classList.remove('low-time');
} else if (!isMyActualTurn && currentGameState.gameMode === 'pvp' && !data.isPlayerTurn !== (myPlayerId === GAME_CONFIG.PLAYER_ID)) { // Ход оппонента в PvP
turnTimerSpan.textContent = 'Ход оппонента';
turnTimerSpan.classList.remove('low-time');
} else { // Ход текущего игрока, но сервер прислал null - странно, но покажем '--'
turnTimerSpan.textContent = '--';
turnTimerSpan.classList.remove('low-time');
}
} else {
turnTimerContainer.style.display = 'block'; // Убедимся, что контейнер виден
const seconds = Math.ceil(data.remainingTime / 1000);
turnTimerSpan.textContent = `0:${seconds < 10 ? '0' : ''}${seconds}`;
// Добавляем/удаляем класс для предупреждения, если времени мало
if (seconds <= 10) { // Например, 10 секунд - порог
turnTimerSpan.classList.add('low-time');
} else {
turnTimerSpan.classList.remove('low-time');
}
}
}
});
// === КОНЕЦ ИЗМЕНЕНИЯ ===
showAuthScreen();
});