bc/public/js/main.js
2025-05-26 14:57:38 +03:00

429 lines
24 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/main.js
import { initAuth } from './auth.js';
import { initGameSetup } from './gameSetup.js';
import { initGameplay } from './gameplay.js';
// ui.js загружен глобально и ожидает window.* переменных
function parseJwtPayload(token) {
try {
if (typeof token !== 'string') { return null; }
const parts = token.split('.');
if (parts.length !== 3) { return null; }
const base64Url = parts[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
const jsonPayload = decodeURIComponent(atob(base64).split('').map(function(c) {
return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
}).join(''));
return JSON.parse(jsonPayload);
} catch (e) {
console.error("[Main.js parseJwtPayload] Error parsing JWT payload:", e);
return null;
}
}
document.addEventListener('DOMContentLoaded', () => {
console.log('[Main.js] DOMContentLoaded event fired.'); // <--- ДОБАВЛЕНО
const initialToken = localStorage.getItem('jwtToken');
console.log('[Main.js] Initial token from localStorage:', initialToken); // <--- ДОБАВЛЕНО
let clientState = {
isLoggedIn: false,
loggedInUsername: '',
myUserId: null,
isInGame: false,
currentGameId: null,
currentGameState: null,
myPlayerId: null,
myCharacterKey: null,
opponentCharacterKey: null,
playerBaseStatsServer: null,
opponentBaseStatsServer: null,
playerAbilitiesServer: null,
opponentAbilitiesServer: null,
};
if (initialToken) {
const decodedToken = parseJwtPayload(initialToken);
if (decodedToken && decodedToken.userId && decodedToken.username) {
const nowInSeconds = Math.floor(Date.now() / 1000);
if (decodedToken.exp && decodedToken.exp > nowInSeconds) {
console.log("[Main.js] Token found and valid, pre-populating clientState:", decodedToken); // <--- ДОБАВЛЕНО
clientState.isLoggedIn = true;
clientState.myUserId = decodedToken.userId;
clientState.loggedInUsername = decodedToken.username;
} else {
console.warn("[Main.js] Token expired or invalid 'exp'. Clearing.");
localStorage.removeItem('jwtToken');
}
} else {
console.warn("[Main.js] Token invalid or missing data. Clearing.");
localStorage.removeItem('jwtToken');
}
} else {
console.log("[Main.js] No initial token found in localStorage."); // <--- ДОБАВЛЕНО
}
console.log('[Main.js] Initial clientState after token check:', JSON.parse(JSON.stringify(clientState))); // <--- ДОБАВЛЕНО
console.log('[Main.js] Initializing Socket.IO client...'); // <--- ДОБАВЛЕНО
const socket = io({
autoConnect: false,
auth: { token: localStorage.getItem('jwtToken') } // Передаем токен (может быть null)
});
console.log('[Main.js] Socket.IO client initialized. Socket ID (if connected):', socket.id); // <--- ДОБАВЛЕНО (ID будет при autoConnect:true или после .connect())
// --- DOM Элементы ---
console.log('[Main.js] Getting DOM elements...'); // <--- ДОБАВЛЕНО
const authSection = document.getElementById('auth-section');
const loginForm = document.getElementById('login-form');
const registerForm = document.getElementById('register-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');
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 returnToMenuButton = document.getElementById('return-to-menu-button');
const turnTimerContainer = document.getElementById('turn-timer-container');
const turnTimerSpan = document.getElementById('turn-timer');
// --- ДОБАВЛЕНО: Логирование найденных DOM элементов ---
console.log('[Main.js DOM Check] authSection:', authSection);
console.log('[Main.js DOM Check] loginForm:', loginForm);
console.log('[Main.js DOM Check] registerForm:', registerForm);
console.log('[Main.js DOM Check] logoutButton:', logoutButton);
// --- КОНЕЦ: Логирование найденных DOM элементов ---
// --- Функции обновления UI и состояния ---
function updateGlobalWindowVariablesForUI() {
// console.log("[Main.js updateGlobalWindowVariablesForUI] Updating window variables."); // Можно раскомментировать для очень детальной отладки
window.gameState = clientState.currentGameState;
window.gameData = {
playerBaseStats: clientState.playerBaseStatsServer,
opponentBaseStats: clientState.opponentBaseStatsServer,
playerAbilities: clientState.playerAbilitiesServer,
opponentAbilities: clientState.opponentAbilitiesServer
};
window.myPlayerId = clientState.myPlayerId;
}
function resetGameVariables() {
console.log("[Main.js resetGameVariables] Resetting game variables. State BEFORE:", JSON.parse(JSON.stringify(clientState)));
clientState.currentGameId = null;
clientState.currentGameState = null;
clientState.myPlayerId = null;
clientState.myCharacterKey = null;
clientState.opponentCharacterKey = null;
clientState.playerBaseStatsServer = null;
clientState.opponentBaseStatsServer = null;
clientState.playerAbilitiesServer = null;
clientState.opponentAbilitiesServer = null;
updateGlobalWindowVariablesForUI();
console.log("[Main.js resetGameVariables] Game variables reset. State AFTER:", JSON.parse(JSON.stringify(clientState)));
}
function explicitlyHideGameOverModal() {
// console.log("[Main.js explicitlyHideGameOverModal] Attempting to hide Game Over modal."); // Можно раскомментировать
if (window.gameUI?.uiElements?.gameOver?.screen && window.GAME_CONFIG) {
const gameOverScreenElement = window.gameUI.uiElements.gameOver.screen;
const modalContentElement = window.gameUI.uiElements.gameOver.modalContent;
const messageElement = window.gameUI.uiElements.gameOver.message;
const hiddenClass = window.GAME_CONFIG.CSS_CLASS_HIDDEN || 'hidden';
if (gameOverScreenElement && !gameOverScreenElement.classList.contains(hiddenClass)) {
gameOverScreenElement.classList.add(hiddenClass);
gameOverScreenElement.style.opacity = '0';
if (modalContentElement) {
modalContentElement.style.transform = 'scale(0.8) translateY(30px)';
modalContentElement.style.opacity = '0';
}
// console.log("[Main.js explicitlyHideGameOverModal] Game Over screen explicitly hidden.");
} else if (gameOverScreenElement) {
// console.log("[Main.js explicitlyHideGameOverModal] Game Over screen was already hidden or not found.");
}
if (messageElement) messageElement.textContent = '';
} else {
// console.warn("[Main.js explicitlyHideGameOverModal] Cannot hide Game Over modal: gameUI or GAME_CONFIG not available.");
}
}
function showAuthScreen() {
console.log("[Main.js showAuthScreen] Showing Auth Screen. Resetting game state if not already done.");
authSection.style.display = 'block';
userInfoDiv.style.display = 'none';
gameSetupDiv.style.display = 'none';
gameWrapper.style.display = 'none';
explicitlyHideGameOverModal();
statusContainer.style.display = 'block'; // Убедимся, что контейнер статуса виден
clientState.isInGame = false; // Сбрасываем флаг игры
resetGameVariables(); // Сбрасываем игровые переменные
if (turnTimerContainer) turnTimerContainer.style.display = 'none';
if (turnTimerSpan) turnTimerSpan.textContent = '--';
if(registerForm && registerForm.querySelector('button')) registerForm.querySelector('button').disabled = false;
if(loginForm && loginForm.querySelector('button')) loginForm.querySelector('button').disabled = false;
if(logoutButton) logoutButton.disabled = true; // Кнопка выхода должна быть неактивна на экране логина
}
function showGameSelectionScreen(username) {
console.log(`[Main.js showGameSelectionScreen] Showing Game Selection Screen for ${username}.`);
authSection.style.display = 'none';
userInfoDiv.style.display = 'block';
if(loggedInUsernameSpan) loggedInUsernameSpan.textContent = username;
if(logoutButton) logoutButton.disabled = false;
gameSetupDiv.style.display = 'block';
gameWrapper.style.display = 'none';
explicitlyHideGameOverModal();
setGameStatusMessage("Выберите режим игры или присоединитесь к существующей.");
statusContainer.style.display = 'block';
if (socket.connected) {
console.log("[Main.js showGameSelectionScreen] Socket connected, requesting PvP game list."); // <--- ДОБАВЛЕНО
socket.emit('requestPvPGameList');
} else {
console.warn("[Main.js showGameSelectionScreen] Socket not connected, cannot request PvP game list yet.");
}
if (availableGamesDiv) availableGamesDiv.innerHTML = '<h3>Доступные PvP игры:</h3><p>Загрузка...</p>';
if (gameIdInput) gameIdInput.value = '';
const elenaRadio = document.getElementById('char-elena');
if (elenaRadio) elenaRadio.checked = true;
clientState.isInGame = false;
// resetGameVariables(); // Обычно не нужно здесь, т.к. мы только вошли, игры еще не было
if (turnTimerContainer) turnTimerContainer.style.display = 'none';
if (turnTimerSpan) turnTimerSpan.textContent = '--';
enableSetupButtons();
if (window.gameUI?.uiElements?.gameOver?.returnToMenuButton) {
window.gameUI.uiElements.gameOver.returnToMenuButton.disabled = false;
}
}
function showGameScreen() {
console.log("[Main.js showGameScreen] Showing Game Screen.");
authSection.style.display = 'none';
userInfoDiv.style.display = 'block';
if(logoutButton) logoutButton.disabled = false;
gameSetupDiv.style.display = 'none';
gameWrapper.style.display = 'flex'; // Используем flex для .game-wrapper
setGameStatusMessage(""); // Очищаем статусное сообщение
statusContainer.style.display = 'none'; // Скрываем контейнер статуса, т.к. игра началась
clientState.isInGame = true;
updateGlobalWindowVariablesForUI();
if (turnTimerContainer) turnTimerContainer.style.display = 'block';
if (turnTimerSpan) turnTimerSpan.textContent = '--';
}
function setAuthMessage(message, isError = false) {
console.log(`[Main.js setAuthMessage] Message: "${message}", isError: ${isError}`); // <--- ДОБАВЛЕНО
if (authMessage) {
authMessage.textContent = message;
authMessage.className = isError ? 'error' : 'success';
authMessage.style.display = message ? 'block' : 'none';
}
if (message && gameStatusMessage && gameStatusMessage.style.display !== 'none') gameStatusMessage.style.display = 'none';
}
function setGameStatusMessage(message, isError = false) {
console.log(`[Main.js setGameStatusMessage] Message: "${message}", isError: ${isError}`); // <--- ДОБАВЛЕНО
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') authMessage.style.display = 'none';
}
function disableSetupButtons() {
// console.log("[Main.js] Disabling setup buttons."); // Можно раскомментировать
if(createAIGameButton) createAIGameButton.disabled = true;
if(createPvPGameButton) createPvPGameButton.disabled = true;
if(joinPvPGameButton) joinPvPGameButton.disabled = true;
if(findRandomPvPGameButton) findRandomPvPGameButton.disabled = true;
if(availableGamesDiv) availableGamesDiv.querySelectorAll('button').forEach(btn => btn.disabled = true);
}
function enableSetupButtons() {
// console.log("[Main.js] Enabling setup buttons."); // Можно раскомментировать
if(createAIGameButton) createAIGameButton.disabled = false;
if(createPvPGameButton) createPvPGameButton.disabled = false;
if(joinPvPGameButton) joinPvPGameButton.disabled = false;
if(findRandomPvPGameButton) findRandomPvPGameButton.disabled = false;
}
// --- Сборка зависимостей для модулей ---
console.log('[Main.js] Preparing dependencies for modules...'); // <--- ДОБАВЛЕНО
const dependencies = {
socket,
clientState,
ui: {
showAuthScreen,
showGameSelectionScreen,
showGameScreen,
setAuthMessage,
setGameStatusMessage,
resetGameVariables,
updateGlobalWindowVariablesForUI,
disableSetupButtons,
enableSetupButtons,
elements: {
loginForm, registerForm, logoutButton,
createAIGameButton, createPvPGameButton, joinPvPGameButton,
findRandomPvPGameButton, gameIdInput, availableGamesDiv,
pvpCharacterRadios, returnToMenuButton,
}
}
};
console.log('[Main.js] Initializing auth module...'); // <--- ДОБАВЛЕНО
initAuth(dependencies);
console.log('[Main.js] Initializing gameSetup module...'); // <--- ДОБАВЛЕНО
initGameSetup(dependencies);
console.log('[Main.js] Initializing gameplay module...'); // <--- ДОБАВЛЕНО
initGameplay(dependencies);
console.log('[Main.js] All modules initialized.'); // <--- ДОБАВЛЕНО
// --- Обработчики событий Socket.IO ---
socket.on('connect', () => {
const currentToken = socket.auth.token || localStorage.getItem('jwtToken'); // Проверяем токен для лога
console.log('[Main.js Socket.IO] Event: connect. Socket ID:', socket.id, 'Auth token sent to server:', !!currentToken); // <--- ИЗМЕНЕНО
if (clientState.isLoggedIn && clientState.myUserId) {
console.log(`[Main.js Socket.IO] Client state indicates logged in as ${clientState.loggedInUsername} (ID: ${clientState.myUserId}). Requesting game state.`);
// Сообщение о восстановлении сессии лучше показывать, только если мы НЕ в игре
if (!clientState.isInGame && (authSection.style.display === 'block' || gameSetupDiv.style.display === 'block')) {
setGameStatusMessage("Восстановление игровой сессии...");
}
socket.emit('requestGameState');
} else {
console.log('[Main.js Socket.IO] Client state indicates NOT logged in. Showing auth screen if not already visible.');
if (authSection.style.display !== 'block') { // Показываем, только если еще не там
showAuthScreen();
}
setAuthMessage("Пожалуйста, войдите или зарегистрируйтесь.");
}
});
socket.on('connect_error', (err) => {
console.error('[Main.js Socket.IO] Event: connect_error. Message:', err.message, err.data ? JSON.stringify(err.data) : ''); // <--- ИЗМЕНЕНО
const errorMessageLower = err.message ? err.message.toLowerCase() : "";
const isAuthError = errorMessageLower.includes('auth') || errorMessageLower.includes('token') ||
errorMessageLower.includes('unauthorized') || err.message === 'invalid token' ||
err.message === 'no token' || (err.data && typeof err.data === 'string' && err.data.toLowerCase().includes('auth'));
if (isAuthError) {
console.warn('[Main.js Socket.IO] Authentication error during connection. Clearing token, resetting state, showing auth screen.');
localStorage.removeItem('jwtToken'); // Убедимся, что токен удален
clientState.isLoggedIn = false;
clientState.loggedInUsername = '';
clientState.myUserId = null;
if (socket.auth) socket.auth.token = null; // Очищаем токен и в объекте сокета
if (authSection.style.display !== 'block') {
showAuthScreen();
}
setAuthMessage("Ошибка аутентификации. Пожалуйста, войдите снова.", true);
} else {
let currentScreenMessageFunc = setAuthMessage;
if (clientState.isLoggedIn && clientState.isInGame) {
currentScreenMessageFunc = setGameStatusMessage;
} else if (clientState.isLoggedIn) {
// Если залогинен, но не в игре (на экране выбора игры), сообщение на gameStatusMessage
currentScreenMessageFunc = setGameStatusMessage;
}
currentScreenMessageFunc(`Ошибка подключения: ${err.message}. Попытка переподключения...`, true);
if (authSection.style.display !== 'block' && !clientState.isLoggedIn) {
showAuthScreen();
}
}
if (turnTimerSpan) turnTimerSpan.textContent = 'Ошибка';
});
socket.on('disconnect', (reason) => {
console.warn('[Main.js Socket.IO] Event: disconnect. Reason:', reason); // <--- ИЗМЕНЕНО
let messageFunc = setAuthMessage;
if (clientState.isInGame) {
messageFunc = setGameStatusMessage;
} else if (clientState.isLoggedIn && gameSetupDiv.style.display === 'block') {
messageFunc = setGameStatusMessage;
}
// Не показываем сообщение об ошибке, если это преднамеренный дисконнект при логауте
if (reason !== 'io client disconnect') {
messageFunc(`Потеряно соединение: ${reason}. Попытка переподключения...`, true);
}
if (turnTimerSpan) turnTimerSpan.textContent = 'Откл.';
});
socket.on('gameError', (data) => {
console.error('[Main.js Socket.IO] Event: gameError. Message:', data.message, 'Data:', JSON.stringify(data)); // <--- ИЗМЕНЕНО
if (clientState.isInGame && window.gameUI?.addToLog) {
window.gameUI.addToLog(`❌ Ошибка сервера: ${data.message}`, 'system');
} else if (clientState.isLoggedIn) {
setGameStatusMessage(`❌ Ошибка: ${data.message}`, true);
enableSetupButtons();
} else {
setAuthMessage(`❌ Ошибка: ${data.message}`, true);
if(registerForm && registerForm.querySelector('button')) registerForm.querySelector('button').disabled = false;
if(loginForm && loginForm.querySelector('button')) loginForm.querySelector('button').disabled = false;
}
});
socket.on('gameNotFound', (data) => {
console.log('[Main.js Socket.IO] Event: gameNotFound. Message:', data?.message, 'Data:', JSON.stringify(data)); // <--- ИЗМЕНЕНО
clientState.isInGame = false;
resetGameVariables();
explicitlyHideGameOverModal();
if (turnTimerContainer) turnTimerContainer.style.display = 'none';
if (turnTimerSpan) turnTimerSpan.textContent = '--';
if (clientState.isLoggedIn && clientState.myUserId) {
if (gameSetupDiv.style.display !== 'block') { // Показываем, только если еще не там
showGameSelectionScreen(clientState.loggedInUsername);
}
setGameStatusMessage(data?.message || "Активная игровая сессия не найдена. Выберите новую игру.");
} else {
if (authSection.style.display !== 'block') {
showAuthScreen();
}
setAuthMessage(data?.message || "Пожалуйста, войдите.");
}
});
// --- Инициализация UI ---
console.log('[Main.js] Initializing UI visibility...'); // <--- ДОБАВЛЕНО
authSection.style.display = 'none';
gameSetupDiv.style.display = 'none';
gameWrapper.style.display = 'none';
userInfoDiv.style.display = 'none';
statusContainer.style.display = 'block'; // Показываем контейнер статуса по умолчанию
if (clientState.isLoggedIn) {
// Если токен есть и валиден, НЕ показываем экран логина, а пытаемся восстановить сессию
// Это будет обработано в socket.on('connect') -> requestGameState
// Если requestGameState вернет gameNotFound, тогда покажется gameSelectionScreen
// Если requestGameState вернет игру, покажется gameScreen
// Сообщение "Подключение и восстановление сессии..." может быть показано здесь
console.log('[Main.js] Client is considered logged in. Will attempt session recovery on socket connect.');
setGameStatusMessage("Подключение и восстановление сессии..."); // Показываем на общем статусном элементе
} else {
// Если нет валидного токена, показываем экран аутентификации
console.log('[Main.js] Client is NOT considered logged in. Showing auth screen.');
showAuthScreen(); // Это установит правильные display для authSection и скроет другие
setAuthMessage("Подключение к серверу..."); // Начальное сообщение на экране логина
}
console.log('[Main.js] Attempting to connect socket...'); // <--- ДОБАВЛЕНО
socket.connect(); // Начинаем подключение к серверу
console.log('[Main.js] socket.connect() called.'); // <--- ДОБАВЛЕНО
});