Сохранение локальной работы перед синхронизацией с перезаписанной историей
This commit is contained in:
parent
550212e18d
commit
6e8dce7d71
@ -41,8 +41,6 @@
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<!-- Место для #user-info было здесь, но теперь оно выше для стилизации -->
|
||||
|
||||
<div id="game-setup" style="display: none;"> <!-- Секция Настройки Игры (после логина) -->
|
||||
<h2>Настройка Игры</h2>
|
||||
<div>
|
||||
@ -77,14 +75,6 @@
|
||||
|
||||
|
||||
<div class="game-wrapper" style="display: none;"> <!-- Игровая арена, изначально скрыта -->
|
||||
<!-- === ИЗМЕНЕНИЕ: Удален game-header === -->
|
||||
<!-- <header class="game-header">
|
||||
<h1><span class="title-player">Игрок 1</span> <span class="separator"><i class="fas fa-fist-raised"></i></span> <span class="title-opponent">Игрок 2</span></h1>
|
||||
</header> -->
|
||||
<!-- === КОНЕЦ ИЗМЕНЕНИЯ === -->
|
||||
|
||||
<!-- === БЛОК: Контролы переключения панелей (для мобильных) - ИЗ СЕРВЕРНОЙ ВЕРСИИ === -->
|
||||
<!-- Инлайновый стиль style="display: none;" УДАЛЕН. CSS управляет видимостью. -->
|
||||
<div class="panel-switcher-controls">
|
||||
<button id="show-player-panel-btn" class="panel-switch-button active">
|
||||
<i class="fas fa-user"></i> <span class="button-text">Игрок</span>
|
||||
@ -93,7 +83,6 @@
|
||||
<i class="fas fa-ghost"></i> <span class="button-text">Противник</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- === КОНЕЦ БЛОКА === -->
|
||||
|
||||
<main class="battle-arena-container">
|
||||
<!-- Колонка Игрока (Панель 1 в UI) -->
|
||||
@ -232,8 +221,15 @@
|
||||
</div>
|
||||
</div> <!-- Конец .game-wrapper -->
|
||||
|
||||
<!-- Библиотека Socket.IO клиента -->
|
||||
<script src="/socket.io/socket.io.js"></script>
|
||||
<script src="./js/ui.js"></script> <!-- ui.js теперь перед client.js -->
|
||||
<script src="./js/client.js"></script>
|
||||
|
||||
<!-- Ваш скрипт для UI, который может создавать глобальные объекты или функции -->
|
||||
<!-- Он должен быть загружен до main.js, если main.js ожидает window.gameUI -->
|
||||
<script src="./js/ui.js"></script>
|
||||
|
||||
<!-- Ваш основной клиентский скрипт, теперь как модуль -->
|
||||
<script type="module" src="./js/main.js"></script>
|
||||
|
||||
</body>
|
||||
</html>
|
128
public/js/auth.js
Normal file
128
public/js/auth.js
Normal file
@ -0,0 +1,128 @@
|
||||
// /public/js/auth.js
|
||||
|
||||
// Эта функция будет вызвана из main.js и получит необходимые зависимости
|
||||
export function initAuth(dependencies) {
|
||||
const { socket, clientState, ui } = dependencies;
|
||||
const { loginForm, registerForm, logoutButton } = ui.elements; // Получаем нужные DOM элементы
|
||||
|
||||
// --- Обработчики событий DOM ---
|
||||
if (registerForm) {
|
||||
registerForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const usernameInput = document.getElementById('register-username');
|
||||
const passwordInput = document.getElementById('register-password');
|
||||
if (!usernameInput || !passwordInput) return;
|
||||
|
||||
const username = usernameInput.value;
|
||||
const password = passwordInput.value;
|
||||
|
||||
// Блокируем кнопки на время запроса
|
||||
const regButton = registerForm.querySelector('button');
|
||||
const loginButton = loginForm ? loginForm.querySelector('button') : null;
|
||||
if (regButton) regButton.disabled = true;
|
||||
if (loginButton) loginButton.disabled = true;
|
||||
|
||||
ui.setAuthMessage('Регистрация...');
|
||||
socket.emit('register', { username, password });
|
||||
});
|
||||
}
|
||||
|
||||
if (loginForm) {
|
||||
loginForm.addEventListener('submit', (e) => {
|
||||
e.preventDefault();
|
||||
const usernameInput = document.getElementById('login-username');
|
||||
const passwordInput = document.getElementById('login-password');
|
||||
if (!usernameInput || !passwordInput) return;
|
||||
|
||||
const username = usernameInput.value;
|
||||
const password = passwordInput.value;
|
||||
|
||||
// Блокируем кнопки на время запроса
|
||||
const loginButton = loginForm.querySelector('button');
|
||||
const regButton = registerForm ? registerForm.querySelector('button') : null;
|
||||
if (loginButton) loginButton.disabled = true;
|
||||
if (regButton) regButton.disabled = true;
|
||||
|
||||
ui.setAuthMessage('Вход...');
|
||||
socket.emit('login', { username, password });
|
||||
});
|
||||
}
|
||||
|
||||
if (logoutButton) {
|
||||
logoutButton.addEventListener('click', () => {
|
||||
logoutButton.disabled = true;
|
||||
socket.emit('logout');
|
||||
|
||||
// Обновляем состояние клиента немедленно, не дожидаясь ответа сервера (опционально)
|
||||
clientState.isLoggedIn = false;
|
||||
clientState.loggedInUsername = '';
|
||||
clientState.myUserId = null;
|
||||
// isInGame и другие игровые переменные сбросятся в ui.showAuthScreen()
|
||||
// disableGameControls() также будет вызван опосредованно через showAuthScreen -> resetGameVariables
|
||||
|
||||
ui.showAuthScreen(); // Показываем экран логина
|
||||
ui.setGameStatusMessage("Вы вышли из системы."); // Используем gameStatusMessage для общего статуса после выхода
|
||||
// ui.setAuthMessage("Вы вышли из системы."); // или authMessage, если он виден
|
||||
// Кнопка разблокируется при следующем показе userInfoDiv или можно здесь
|
||||
// logoutButton.disabled = false; // но лучше, чтобы UI сам управлял этим при показе
|
||||
});
|
||||
}
|
||||
|
||||
// --- Обработчики событий Socket.IO ---
|
||||
socket.on('registerResponse', (data) => {
|
||||
ui.setAuthMessage(data.message, !data.success);
|
||||
if (data.success && registerForm) {
|
||||
registerForm.reset(); // Очищаем форму при успехе
|
||||
}
|
||||
// Разблокируем кнопки
|
||||
if (registerForm) {
|
||||
const regButton = registerForm.querySelector('button');
|
||||
if (regButton) regButton.disabled = false;
|
||||
}
|
||||
if (loginForm) {
|
||||
const loginButton = loginForm.querySelector('button');
|
||||
if (loginButton) loginButton.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('loginResponse', (data) => {
|
||||
if (data.success) {
|
||||
clientState.isLoggedIn = true;
|
||||
clientState.loggedInUsername = data.username;
|
||||
clientState.myUserId = data.userId;
|
||||
|
||||
ui.setAuthMessage(""); // Очищаем сообщение об аутентификации
|
||||
ui.showGameSelectionScreen(data.username); // Показываем экран выбора игры
|
||||
// Запрос gameState при успешном логине и реконнекте теперь обрабатывается в main.js
|
||||
// если пользователь уже был залогинен при 'connect'
|
||||
} else {
|
||||
clientState.isLoggedIn = false;
|
||||
clientState.loggedInUsername = '';
|
||||
clientState.myUserId = null;
|
||||
ui.setAuthMessage(data.message, true); // Показываем ошибку
|
||||
}
|
||||
// Разблокируем кнопки
|
||||
if (registerForm) {
|
||||
const regButton = registerForm.querySelector('button');
|
||||
if (regButton) regButton.disabled = false;
|
||||
}
|
||||
if (loginForm) {
|
||||
const loginButton = loginForm.querySelector('button');
|
||||
if (loginButton) loginButton.disabled = false;
|
||||
}
|
||||
// Убедимся, что кнопка logout активна, если пользователь успешно вошел
|
||||
if (logoutButton && clientState.isLoggedIn) {
|
||||
logoutButton.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Примечание: событие 'logout' от сервера обычно не требует специального обработчика здесь,
|
||||
// так как клиент сам инициирует выход и обновляет UI.
|
||||
// Если сервер принудительно разлогинивает, то такой обработчик может понадобиться.
|
||||
// socket.on('forceLogout', (data) => {
|
||||
// clientState.isLoggedIn = false;
|
||||
// // ...
|
||||
// ui.showAuthScreen();
|
||||
// ui.setAuthMessage(data.message || "Вы были разлогинены сервером.");
|
||||
// });
|
||||
}
|
199
public/js/gameSetup.js
Normal file
199
public/js/gameSetup.js
Normal file
@ -0,0 +1,199 @@
|
||||
// /public/js/gameSetup.js
|
||||
|
||||
export function initGameSetup(dependencies) {
|
||||
const { socket, clientState, ui } = dependencies;
|
||||
const {
|
||||
createAIGameButton, createPvPGameButton, joinPvPGameButton,
|
||||
findRandomPvPGameButton, gameIdInput, availableGamesDiv, pvpCharacterRadios
|
||||
} = ui.elements;
|
||||
|
||||
// --- Вспомогательные функции ---
|
||||
function getSelectedCharacterKey() {
|
||||
let selectedKey = 'elena'; // Значение по умолчанию
|
||||
if (pvpCharacterRadios) {
|
||||
pvpCharacterRadios.forEach(radio => {
|
||||
if (radio.checked) {
|
||||
selectedKey = radio.value;
|
||||
}
|
||||
});
|
||||
}
|
||||
return selectedKey;
|
||||
}
|
||||
|
||||
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');
|
||||
// Отображаем только часть ID для краткости
|
||||
li.textContent = `ID: ${game.id.substring(0, 8)}... - ${game.status || 'Ожидает игрока'}`;
|
||||
|
||||
const joinBtn = document.createElement('button');
|
||||
joinBtn.textContent = 'Присоединиться';
|
||||
joinBtn.dataset.gameId = game.id;
|
||||
|
||||
// Деактивация кнопки "Присоединиться" для своих игр
|
||||
if (clientState.isLoggedIn && clientState.myUserId && game.ownerIdentifier === clientState.myUserId) {
|
||||
joinBtn.disabled = true;
|
||||
joinBtn.title = "Вы не можете присоединиться к своей же ожидающей игре.";
|
||||
} else {
|
||||
joinBtn.disabled = false;
|
||||
}
|
||||
|
||||
joinBtn.addEventListener('click', (e) => {
|
||||
if (!clientState.isLoggedIn) {
|
||||
ui.setGameStatusMessage("Пожалуйста, войдите, чтобы присоединиться к игре.", true);
|
||||
return;
|
||||
}
|
||||
if (e.target.disabled) return; // Не обрабатывать клик по отключенной кнопке
|
||||
|
||||
ui.disableSetupButtons(); // Блокируем все кнопки выбора игры
|
||||
socket.emit('joinGame', { gameId: e.target.dataset.gameId });
|
||||
ui.setGameStatusMessage(`Присоединение к игре ${e.target.dataset.gameId.substring(0, 8)}...`);
|
||||
});
|
||||
li.appendChild(joinBtn);
|
||||
ul.appendChild(li);
|
||||
}
|
||||
});
|
||||
availableGamesDiv.appendChild(ul);
|
||||
} else {
|
||||
availableGamesDiv.innerHTML += '<p>Нет доступных игр. Создайте свою!</p>';
|
||||
}
|
||||
ui.enableSetupButtons(); // Включаем основные кнопки создания/поиска после обновления списка
|
||||
}
|
||||
|
||||
// --- Обработчики событий DOM ---
|
||||
if (createAIGameButton) {
|
||||
createAIGameButton.addEventListener('click', () => {
|
||||
if (!clientState.isLoggedIn) {
|
||||
ui.setGameStatusMessage("Пожалуйста, войдите, чтобы создать игру.", true);
|
||||
return;
|
||||
}
|
||||
ui.disableSetupButtons();
|
||||
// Для AI игры персонаж может быть фиксированным или выбираемым
|
||||
// В вашем client.js был 'elena', оставим так
|
||||
socket.emit('createGame', { mode: 'ai', characterKey: 'elena' });
|
||||
ui.setGameStatusMessage("Создание игры против AI...");
|
||||
});
|
||||
}
|
||||
|
||||
if (createPvPGameButton) {
|
||||
createPvPGameButton.addEventListener('click', () => {
|
||||
if (!clientState.isLoggedIn) {
|
||||
ui.setGameStatusMessage("Пожалуйста, войдите, чтобы создать игру.", true);
|
||||
return;
|
||||
}
|
||||
ui.disableSetupButtons();
|
||||
const characterKey = getSelectedCharacterKey();
|
||||
socket.emit('createGame', { mode: 'pvp', characterKey: characterKey });
|
||||
ui.setGameStatusMessage("Создание PvP игры...");
|
||||
});
|
||||
}
|
||||
|
||||
if (joinPvPGameButton) {
|
||||
joinPvPGameButton.addEventListener('click', () => {
|
||||
if (!clientState.isLoggedIn) {
|
||||
ui.setGameStatusMessage("Пожалуйста, войдите, чтобы присоединиться к игре.", true);
|
||||
return;
|
||||
}
|
||||
const gameId = gameIdInput ? gameIdInput.value.trim() : '';
|
||||
if (gameId) {
|
||||
ui.disableSetupButtons();
|
||||
socket.emit('joinGame', { gameId: gameId });
|
||||
ui.setGameStatusMessage(`Присоединение к игре ${gameId}...`);
|
||||
} else {
|
||||
ui.setGameStatusMessage("Введите ID игры, чтобы присоединиться.", true);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (findRandomPvPGameButton) {
|
||||
findRandomPvPGameButton.addEventListener('click', () => {
|
||||
if (!clientState.isLoggedIn) {
|
||||
ui.setGameStatusMessage("Пожалуйста, войдите, чтобы найти игру.", true);
|
||||
return;
|
||||
}
|
||||
ui.disableSetupButtons();
|
||||
const characterKey = getSelectedCharacterKey();
|
||||
socket.emit('findRandomGame', { characterKey: characterKey });
|
||||
ui.setGameStatusMessage("Поиск случайной PvP игры...");
|
||||
});
|
||||
}
|
||||
|
||||
// --- Обработчики событий Socket.IO ---
|
||||
|
||||
// gameCreated: Сервер присылает это после успешного createGame
|
||||
// Это событие может быть важным для установки currentGameId и myPlayerId
|
||||
// перед тем, как придет gameStarted или waitingForOpponent.
|
||||
socket.on('gameCreated', (data) => {
|
||||
if (!clientState.isLoggedIn) return; // Игнорируем, если не залогинены
|
||||
|
||||
console.log('[GameSetup] Game created by this client:', data);
|
||||
clientState.currentGameId = data.gameId;
|
||||
clientState.myPlayerId = data.yourPlayerId; // Сервер должен прислать роль создателя
|
||||
ui.updateGlobalWindowVariablesForUI(); // Обновляем глобальные переменные для ui.js
|
||||
|
||||
// Если это PvP игра, обычно сервер следом пришлет 'waitingForOpponent'
|
||||
// Если AI, то сразу 'gameStarted'
|
||||
// На этом этапе UI не меняем кардинально, ждем следующего события.
|
||||
// ui.setGameStatusMessage(`Игра ${data.gameId.substring(0,8)} создана. Ожидание...`);
|
||||
// Кнопки уже должны быть заблокированы ui.disableSetupButtons()
|
||||
});
|
||||
|
||||
socket.on('availablePvPGamesList', (games) => {
|
||||
if (!clientState.isLoggedIn) return; // Только для залогиненных пользователей
|
||||
updateAvailableGamesList(games);
|
||||
});
|
||||
|
||||
// Это событие приходит, когда игрок искал случайную игру, но свободных не было,
|
||||
// и сервер создал новую игру для этого игрока.
|
||||
socket.on('noPendingGamesFound', (data) => {
|
||||
if (!clientState.isLoggedIn) return;
|
||||
|
||||
ui.setGameStatusMessage(data.message || "Свободных игр не найдено. Создана новая для вас. Ожидание оппонента...");
|
||||
updateAvailableGamesList([]); // Очищаем список доступных игр, так как мы уже в созданной
|
||||
|
||||
// clientState.currentGameId и clientState.myPlayerId должны были быть установлены
|
||||
// через событие 'gameCreated', которое сервер должен прислать перед 'noPendingGamesFound'.
|
||||
// Если 'gameCreated' не присылается в этом сценарии, нужно будет получать gameId и yourPlayerId из data 'noPendingGamesFound'.
|
||||
if (data.gameId) clientState.currentGameId = data.gameId;
|
||||
if (data.yourPlayerId) clientState.myPlayerId = data.yourPlayerId;
|
||||
ui.updateGlobalWindowVariablesForUI();
|
||||
|
||||
clientState.isInGame = false; // Мы еще не в активной фазе боя, а в ожидании
|
||||
// ui.disableGameControls(); // Будет вызвано из gameplay.js, если он уже был инициализирован
|
||||
// или неактуально, так как мы не на игровом экране
|
||||
ui.disableSetupButtons(); // Мы в ожидающей игре, кнопки выбора не нужны
|
||||
// Можно оставить кнопку "Создать PvP" активной для возможности "отменить" и создать другую,
|
||||
// но это усложнит логику. Пока блокируем все.
|
||||
|
||||
// Если есть таймер, его нужно сбросить или показать "Ожидание"
|
||||
if (window.gameUI?.updateTurnTimerDisplay) {
|
||||
window.gameUI.updateTurnTimerDisplay(null, false, 'pvp'); // Таймер неактивен в ожидании
|
||||
}
|
||||
});
|
||||
|
||||
// waitingForOpponent: Когда PvP игра создана и ожидает второго игрока
|
||||
socket.on('waitingForOpponent', () => {
|
||||
if (!clientState.isLoggedIn) return;
|
||||
|
||||
ui.setGameStatusMessage("Ожидание присоединения оппонента...");
|
||||
// clientState.isInGame = false; // Уже должно быть false или будет установлено при gameStarted
|
||||
// ui.disableGameControls(); // не на игровом экране
|
||||
ui.disableSetupButtons(); // Блокируем кнопки создания/присоединения
|
||||
|
||||
// Можно оставить кнопку "Создать PvP" или добавить кнопку "Отменить ожидание",
|
||||
// но это требует дополнительной логики на сервере и клиенте.
|
||||
// if (ui.elements.createPvPGameButton) ui.elements.createPvPGameButton.disabled = false;
|
||||
|
||||
if (window.gameUI?.updateTurnTimerDisplay) {
|
||||
window.gameUI.updateTurnTimerDisplay(null, false, 'pvp');
|
||||
}
|
||||
});
|
||||
|
||||
// Примечание: gameNotFound обрабатывается в main.js, так как он может сбросить
|
||||
// игрока на экран выбора игры или даже на экран логина.
|
||||
}
|
339
public/js/gameplay.js
Normal file
339
public/js/gameplay.js
Normal file
@ -0,0 +1,339 @@
|
||||
// /public/js/gameplay.js
|
||||
|
||||
export function initGameplay(dependencies) {
|
||||
const { socket, clientState, ui } = dependencies;
|
||||
// Элементы управления боем обычно находятся внутри gameWrapper и управляются через ui.js,
|
||||
// но нам могут понадобиться ссылки на кнопки для привязки событий, если они не привязаны в ui.js
|
||||
// или если ui.js не экспортирует их напрямую.
|
||||
// В данном случае, attackButton и abilitiesGrid есть в client.js, так что получим их.
|
||||
// ui.elements из main.js содержит returnToMenuButton
|
||||
const { returnToMenuButton } = ui.elements;
|
||||
|
||||
// Получаем ссылки на кнопки атаки и способностей напрямую, как было в client.js
|
||||
// или, если бы ui.js их экспортировал, можно было бы через window.gameUI.uiElements
|
||||
const attackButton = document.getElementById('button-attack');
|
||||
const abilitiesGrid = document.getElementById('abilities-grid');
|
||||
|
||||
// --- Вспомогательные функции ---
|
||||
function enableGameControls(enableAttack = true, enableAbilities = true) {
|
||||
if (attackButton) attackButton.disabled = !enableAttack;
|
||||
if (abilitiesGrid) {
|
||||
// Предполагаем, что GAME_CONFIG доступен глобально или его нужно передать
|
||||
const config = window.GAME_CONFIG || {};
|
||||
const cls = config.CSS_CLASS_ABILITY_BUTTON || 'ability-button';
|
||||
abilitiesGrid.querySelectorAll(`.${cls}`).forEach(b => { b.disabled = !enableAbilities; });
|
||||
}
|
||||
// Если кнопка блока есть и управляется отсюда
|
||||
// if (window.gameUI?.uiElements?.controls?.buttonBlock) window.gameUI.uiElements.controls.buttonBlock.disabled = true;
|
||||
}
|
||||
|
||||
function disableGameControls() {
|
||||
enableGameControls(false, false);
|
||||
}
|
||||
|
||||
// Эта функция была в client.js, переносим сюда
|
||||
function initializeAbilityButtons() {
|
||||
if (!abilitiesGrid || !window.gameUI || !window.GAME_CONFIG) {
|
||||
if (abilitiesGrid) abilitiesGrid.innerHTML = '<p class="placeholder-text">Ошибка загрузки способностей.</p>';
|
||||
return;
|
||||
}
|
||||
abilitiesGrid.innerHTML = ''; // Очищаем предыдущие кнопки
|
||||
const config = window.GAME_CONFIG;
|
||||
// Используем данные из clientState, которые были обновлены из событий сервера
|
||||
const abilitiesToDisplay = clientState.playerAbilitiesServer;
|
||||
const baseStatsForResource = clientState.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();
|
||||
// После инициализации кнопок, их состояние (disabled/enabled) будет управляться window.gameUI.updateUI()
|
||||
}
|
||||
|
||||
function handleAbilityButtonClick(event) {
|
||||
const abilityId = event.currentTarget.dataset.abilityId;
|
||||
if (clientState.isLoggedIn &&
|
||||
clientState.isInGame &&
|
||||
clientState.currentGameId &&
|
||||
abilityId &&
|
||||
clientState.currentGameState &&
|
||||
!clientState.currentGameState.isGameOver) {
|
||||
// Перед отправкой действия можно добавить быструю проверку на клиенте (например, хватает ли ресурса),
|
||||
// но основная валидация все равно на сервере.
|
||||
socket.emit('playerAction', { actionType: 'ability', abilityId: abilityId });
|
||||
disableGameControls(); // Блокируем управление до ответа сервера или следующего хода
|
||||
} else {
|
||||
console.warn("Cannot perform ability action, invalid state:", {
|
||||
isLoggedIn: clientState.isLoggedIn,
|
||||
isInGame: clientState.isInGame,
|
||||
gameId: clientState.currentGameId,
|
||||
abilityId,
|
||||
gameState: clientState.currentGameState
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// --- Обработчики событий DOM ---
|
||||
if (attackButton) {
|
||||
attackButton.addEventListener('click', () => {
|
||||
if (clientState.isLoggedIn &&
|
||||
clientState.isInGame &&
|
||||
clientState.currentGameId &&
|
||||
clientState.currentGameState &&
|
||||
!clientState.currentGameState.isGameOver) {
|
||||
socket.emit('playerAction', { actionType: 'attack' });
|
||||
disableGameControls(); // Блокируем управление до ответа сервера или следующего хода
|
||||
} else {
|
||||
console.warn("Cannot perform attack action, invalid state.");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (returnToMenuButton) { // Кнопка из модалки GameOver
|
||||
returnToMenuButton.addEventListener('click', () => {
|
||||
if (!clientState.isLoggedIn) {
|
||||
ui.showAuthScreen(); // Если как-то оказались здесь без логина
|
||||
return;
|
||||
}
|
||||
returnToMenuButton.disabled = true; // Блокируем на время перехода
|
||||
// ui.resetGameVariables(); // Вызывается в showGameSelectionScreen
|
||||
clientState.isInGame = false; // Устанавливаем, что мы больше не в игре
|
||||
disableGameControls(); // Деактивируем игровые контролы
|
||||
// window.gameUI.showGameOver(false, "", null, { finalGameState: { isGameOver: false } }); // Скрываем модалку (делается в showGameSelectionScreen)
|
||||
|
||||
ui.showGameSelectionScreen(clientState.loggedInUsername); // Возвращаемся на экран выбора
|
||||
// Кнопка returnToMenuButton включится при следующем показе модалки GameOver (логика в ui.js или здесь при gameOver)
|
||||
});
|
||||
}
|
||||
|
||||
// --- Обработчики событий Socket.IO ---
|
||||
socket.on('gameStarted', (data) => {
|
||||
if (!clientState.isLoggedIn) return; // Игнорируем, если не залогинены
|
||||
console.log('[Gameplay] Game started:', data);
|
||||
|
||||
// Обновляем состояние клиента
|
||||
clientState.currentGameId = data.gameId;
|
||||
clientState.myPlayerId = data.yourPlayerId;
|
||||
clientState.currentGameState = data.initialGameState;
|
||||
clientState.playerBaseStatsServer = data.playerBaseStats;
|
||||
clientState.opponentBaseStatsServer = data.opponentBaseStats;
|
||||
clientState.playerAbilitiesServer = data.playerAbilities;
|
||||
clientState.opponentAbilitiesServer = data.opponentAbilities;
|
||||
clientState.myCharacterKey = data.playerBaseStats?.characterKey;
|
||||
clientState.opponentCharacterKey = data.opponentBaseStats?.characterKey;
|
||||
clientState.isInGame = true;
|
||||
|
||||
if (data.clientConfig) { // Если сервер прислал конфиг
|
||||
// Важно: GAME_CONFIG используется в ui.js и других местах
|
||||
window.GAME_CONFIG = { ...window.GAME_CONFIG, ...data.clientConfig };
|
||||
} else if (!window.GAME_CONFIG) { // Базовый конфиг, если не пришел с сервера и не определен
|
||||
window.GAME_CONFIG = { PLAYER_ID: 'player', OPPONENT_ID: 'opponent', CSS_CLASS_HIDDEN: 'hidden' /* ... другие важные ключи ... */ };
|
||||
}
|
||||
|
||||
ui.updateGlobalWindowVariablesForUI(); // Обновляем глобальные переменные для ui.js
|
||||
|
||||
ui.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));
|
||||
}
|
||||
|
||||
// Первичное обновление UI боевого экрана
|
||||
requestAnimationFrame(() => {
|
||||
if (window.gameUI && typeof window.gameUI.updateUI === 'function') {
|
||||
window.gameUI.updateUI();
|
||||
}
|
||||
});
|
||||
// ui.hideGameOverModal(); // Теперь делается в showGameScreen
|
||||
ui.setGameStatusMessage(""); // Очищаем общий статус
|
||||
// Таймер хода будет обновлен событием 'turnTimerUpdate'
|
||||
});
|
||||
|
||||
// Используется для восстановления состояния уже идущей игры (например, при реконнекте)
|
||||
socket.on('gameState', (data) => {
|
||||
if (!clientState.isLoggedIn) return;
|
||||
console.log('[Gameplay] Received full gameState (e.g. on reconnect):', data);
|
||||
|
||||
// Обновляем состояние клиента (похоже на gameStarted)
|
||||
clientState.currentGameId = data.gameId;
|
||||
clientState.myPlayerId = data.yourPlayerId;
|
||||
clientState.currentGameState = data.gameState; // Используем gameState вместо initialGameState
|
||||
clientState.playerBaseStatsServer = data.playerBaseStats;
|
||||
clientState.opponentBaseStatsServer = data.opponentBaseStats;
|
||||
clientState.playerAbilitiesServer = data.playerAbilities;
|
||||
clientState.opponentAbilitiesServer = data.opponentAbilities;
|
||||
clientState.myCharacterKey = data.playerBaseStats?.characterKey;
|
||||
clientState.opponentCharacterKey = data.opponentBaseStats?.characterKey;
|
||||
clientState.isInGame = true; // Устанавливаем, что мы в игре
|
||||
|
||||
if (data.clientConfig) {
|
||||
window.GAME_CONFIG = { ...window.GAME_CONFIG, ...data.clientConfig };
|
||||
} else if (!window.GAME_CONFIG) {
|
||||
window.GAME_CONFIG = { PLAYER_ID: 'player', OPPONENT_ID: 'opponent', CSS_CLASS_HIDDEN: 'hidden' };
|
||||
}
|
||||
ui.updateGlobalWindowVariablesForUI();
|
||||
|
||||
if (!clientState.isInGame || document.querySelector('.game-wrapper').style.display === 'none') {
|
||||
ui.showGameScreen(); // Показываем игровой экран, если еще не там
|
||||
}
|
||||
initializeAbilityButtons(); // Переинициализируем кнопки способностей
|
||||
|
||||
// Лог при 'gameState' может быть уже накопленным, очищаем и добавляем новый
|
||||
if (window.gameUI?.uiElements?.log?.list && data.log) {
|
||||
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') {
|
||||
window.gameUI.updateUI();
|
||||
}
|
||||
});
|
||||
// ui.hideGameOverModal(); // Делается в showGameScreen
|
||||
// Таймер хода будет обновлен событием 'turnTimerUpdate'
|
||||
});
|
||||
|
||||
socket.on('gameStateUpdate', (data) => {
|
||||
if (!clientState.isLoggedIn || !clientState.isInGame || !clientState.currentGameId || !window.GAME_CONFIG) return;
|
||||
|
||||
clientState.currentGameState = data.gameState;
|
||||
ui.updateGlobalWindowVariablesForUI(); // Обновляем window.gameState для ui.js
|
||||
|
||||
if (window.gameUI?.updateUI) window.gameUI.updateUI();
|
||||
|
||||
// Добавляем только новые логи, если они есть в этом частичном обновлении
|
||||
if (window.gameUI?.addToLog && data.log) {
|
||||
data.log.forEach(log => window.gameUI.addToLog(log.message, log.type));
|
||||
}
|
||||
// Логика включения/выключения контролов на основе gameState.isPlayerTurn и myPlayerId
|
||||
// обычно делается внутри window.gameUI.updateUI()
|
||||
});
|
||||
|
||||
socket.on('logUpdate', (data) => {
|
||||
if (!clientState.isLoggedIn || !clientState.isInGame || !clientState.currentGameId || !window.GAME_CONFIG) return;
|
||||
if (window.gameUI?.addToLog && data.log) {
|
||||
data.log.forEach(log => window.gameUI.addToLog(log.message, log.type));
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('gameOver', (data) => {
|
||||
if (!clientState.isLoggedIn || !clientState.currentGameId || !window.GAME_CONFIG) {
|
||||
// Если мы не в игре, но залогинены, запросим состояние (вдруг это старое событие)
|
||||
if (!clientState.currentGameId && clientState.isLoggedIn) socket.emit('requestGameState');
|
||||
else if (!clientState.isLoggedIn) ui.showAuthScreen(); // Если вообще не залогинены
|
||||
return;
|
||||
}
|
||||
|
||||
const playerWon = data.winnerId === clientState.myPlayerId;
|
||||
clientState.currentGameState = data.finalGameState; // Обновляем состояние последним
|
||||
// clientState.isInGame = false; // НЕ СБРАСЫВАЕМ ЗДЕСЬ, чтобы UI показывал экран GameOver. Сбросится при выходе в меню.
|
||||
|
||||
ui.updateGlobalWindowVariablesForUI(); // Обновляем window.gameState для ui.js
|
||||
|
||||
if (window.gameUI?.updateUI) window.gameUI.updateUI(); // Обновляем панели в последний раз
|
||||
|
||||
if (window.gameUI?.addToLog && data.log) {
|
||||
data.log.forEach(log => window.gameUI.addToLog(log.message, log.type));
|
||||
}
|
||||
|
||||
if (window.gameUI?.showGameOver) {
|
||||
const oppKey = clientState.opponentBaseStatsServer?.characterKey;
|
||||
window.gameUI.showGameOver(playerWon, data.reason, oppKey, data); // ui.js покажет модалку
|
||||
}
|
||||
|
||||
if (returnToMenuButton) returnToMenuButton.disabled = false; // Активируем кнопку "Вернуться в меню"
|
||||
|
||||
ui.setGameStatusMessage("Игра окончена. " + (playerWon ? "Вы победили!" : "Вы проиграли."));
|
||||
|
||||
// Обновляем UI таймера, чтобы показать "Конец" или скрыть
|
||||
if (window.gameUI?.updateTurnTimerDisplay) {
|
||||
window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState?.gameMode);
|
||||
}
|
||||
// Контролы должны быть заблокированы, т.к. игра окончена (ui.js->updateUI это сделает)
|
||||
});
|
||||
|
||||
socket.on('opponentDisconnected', (data) => {
|
||||
if (!clientState.isLoggedIn || !clientState.isInGame || !clientState.currentGameId || !window.GAME_CONFIG) return;
|
||||
|
||||
const name = data.disconnectedCharacterName || clientState.opponentBaseStatsServer?.name || 'Противник';
|
||||
if (window.gameUI?.addToLog) {
|
||||
window.gameUI.addToLog(`🔌 Противник (${name}) отключился.`, 'system');
|
||||
}
|
||||
|
||||
// Если игра еще не окончена, сервер может дать время на переподключение или объявить победу
|
||||
if (clientState.currentGameState && !clientState.currentGameState.isGameOver) {
|
||||
ui.setGameStatusMessage(`Противник (${name}) отключился. Ожидание...`, true);
|
||||
disableGameControls(); // Блокируем управление, пока сервер не решит исход
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('turnTimerUpdate', (data) => {
|
||||
if (!clientState.isInGame || !clientState.currentGameState || clientState.currentGameState.isGameOver) {
|
||||
// Если игра окончена или не в игре, обновляем таймер соответственно
|
||||
if (window.gameUI?.updateTurnTimerDisplay && !clientState.currentGameState?.isGameOver) {
|
||||
window.gameUI.updateTurnTimerDisplay(null, false, clientState.currentGameState?.gameMode);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (window.gameUI && typeof window.gameUI.updateTurnTimerDisplay === 'function') {
|
||||
const config = window.GAME_CONFIG || {};
|
||||
// Определяем, является ли текущий ход ходом этого клиента
|
||||
const isMyActualTurn = clientState.myPlayerId && clientState.currentGameState.isPlayerTurn === (clientState.myPlayerId === config.PLAYER_ID);
|
||||
window.gameUI.updateTurnTimerDisplay(data.remainingTime, isMyActualTurn, clientState.currentGameState.gameMode);
|
||||
}
|
||||
// Логика включения/выключения контролов на основе isMyActualTurn
|
||||
// обычно выполняется в window.gameUI.updateUI(), которая вызывается после gameStateUpdate.
|
||||
// Если turnTimerUpdate приходит отдельно и должен влиять на контролы, то нужно добавить:
|
||||
// if (isMyActualTurn) enableGameControls(); else disableGameControls();
|
||||
// Но это может конфликтовать с логикой в updateUI(). Обычно updateUI() - главный источник правды.
|
||||
});
|
||||
|
||||
// Начальная деактивация игровых контролов при загрузке модуля
|
||||
disableGameControls();
|
||||
}
|
296
public/js/main.js
Normal file
296
public/js/main.js
Normal file
@ -0,0 +1,296 @@
|
||||
// /public/js/main.js
|
||||
|
||||
import { initAuth } from './auth.js';
|
||||
import { initGameSetup } from './gameSetup.js';
|
||||
import { initGameplay } from './gameplay.js';
|
||||
// Предполагаем, что ui.js загружен перед этим скриптом (в HTML)
|
||||
// и создал глобальный объект window.gameUI
|
||||
// Также ui.js будет читать window.gameState, window.gameData, window.myPlayerId, window.GAME_CONFIG
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const socket = io({
|
||||
// Опции Socket.IO, если нужны
|
||||
});
|
||||
|
||||
// --- DOM Элементы для общего UI-управления ---
|
||||
// (Эти элементы управляют общим потоком приложения, а не деталями боя)
|
||||
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'); // Для auth.js
|
||||
|
||||
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"]'); // для gameSetup.js
|
||||
|
||||
const gameWrapper = document.querySelector('.game-wrapper');
|
||||
// Элементы, связанные с gameOver, управляются через window.gameUI.showGameOver,
|
||||
// но кнопка "Вернуться в меню" может быть здесь для общего сброса.
|
||||
const returnToMenuButton = document.getElementById('return-to-menu-button');
|
||||
const turnTimerContainer = document.getElementById('turn-timer-container');
|
||||
const turnTimerSpan = document.getElementById('turn-timer');
|
||||
|
||||
|
||||
// --- Состояние клиента (глобальное для main и передаваемое в модули) ---
|
||||
// Это состояние будет модифицироваться из разных модулей
|
||||
let clientState = {
|
||||
isLoggedIn: false,
|
||||
loggedInUsername: '',
|
||||
myUserId: null,
|
||||
isInGame: false,
|
||||
// Игровые переменные, которые ранее были глобальными в client.js
|
||||
// и от которых зависит ui.js
|
||||
currentGameId: null,
|
||||
currentGameState: null,
|
||||
myPlayerId: null,
|
||||
myCharacterKey: null,
|
||||
opponentCharacterKey: null,
|
||||
playerBaseStatsServer: null,
|
||||
opponentBaseStatsServer: null,
|
||||
playerAbilitiesServer: null,
|
||||
opponentAbilitiesServer: null,
|
||||
};
|
||||
|
||||
// Обновляем глобальные переменные window, на которые рассчитывает ui.js
|
||||
// Это временная мера. В идеале, ui.js должен получать эти данные как аргументы функций.
|
||||
function updateGlobalWindowVariablesForUI() {
|
||||
window.gameState = clientState.currentGameState;
|
||||
window.gameData = {
|
||||
playerBaseStats: clientState.playerBaseStatsServer,
|
||||
opponentBaseStats: clientState.opponentBaseStatsServer,
|
||||
playerAbilities: clientState.playerAbilitiesServer,
|
||||
opponentAbilities: clientState.opponentAbilitiesServer
|
||||
};
|
||||
window.myPlayerId = clientState.myPlayerId;
|
||||
// window.GAME_CONFIG остается как есть, если он глобальный и не меняется часто
|
||||
// Если GAME_CONFIG приходит от сервера, его тоже нужно обновлять здесь
|
||||
// if (clientState.serverConfig) window.GAME_CONFIG = { ...clientState.serverConfig };
|
||||
}
|
||||
|
||||
|
||||
// --- Функции управления UI (для переключения основных экранов и общих сообщений) ---
|
||||
function showAuthScreen() {
|
||||
authSection.style.display = 'block';
|
||||
userInfoDiv.style.display = 'none';
|
||||
gameSetupDiv.style.display = 'none';
|
||||
gameWrapper.style.display = 'none';
|
||||
if (window.gameUI?.showGameOver) { // Скрываем модалку GameOver, если была
|
||||
window.gameUI.showGameOver(false, "", null, { finalGameState: { isGameOver: false } });
|
||||
}
|
||||
setAuthMessage("Ожидание подключения к серверу...");
|
||||
statusContainer.style.display = 'block';
|
||||
clientState.isInGame = false;
|
||||
// disableGameControls(); // Вызов будет из gameplay.js
|
||||
resetGameVariables(); // Важно для сброса состояния
|
||||
updateGlobalWindowVariablesForUI(); // Обновляем глоб. переменные для ui.js
|
||||
if (turnTimerContainer) turnTimerContainer.style.display = 'none';
|
||||
if (turnTimerSpan) turnTimerSpan.textContent = '--';
|
||||
}
|
||||
|
||||
function showGameSelectionScreen(username) {
|
||||
authSection.style.display = 'none';
|
||||
userInfoDiv.style.display = 'block';
|
||||
loggedInUsernameSpan.textContent = username;
|
||||
gameSetupDiv.style.display = 'block';
|
||||
gameWrapper.style.display = 'none';
|
||||
if (window.gameUI?.showGameOver) { // Скрываем модалку GameOver
|
||||
window.gameUI.showGameOver(false, "", null, { finalGameState: { isGameOver: false } });
|
||||
}
|
||||
setGameStatusMessage("Выберите режим игры или присоединитесь к существующей.");
|
||||
statusContainer.style.display = 'block';
|
||||
socket.emit('requestPvPGameList'); // Запрашиваем список игр
|
||||
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;
|
||||
// disableGameControls(); // Вызов будет из gameplay.js
|
||||
resetGameVariables();
|
||||
updateGlobalWindowVariablesForUI();
|
||||
if (turnTimerContainer) turnTimerContainer.style.display = 'none';
|
||||
if (turnTimerSpan) turnTimerSpan.textContent = '--';
|
||||
enableSetupButtons();
|
||||
}
|
||||
|
||||
function showGameScreen() {
|
||||
if (window.gameUI?.showGameOver) { // Скрываем модалку GameOver
|
||||
window.gameUI.showGameOver(false, "", null, { finalGameState: { isGameOver: false } });
|
||||
}
|
||||
authSection.style.display = 'none';
|
||||
userInfoDiv.style.display = 'block'; // Оставляем инфо о пользователе
|
||||
gameSetupDiv.style.display = 'none';
|
||||
gameWrapper.style.display = 'flex';
|
||||
setGameStatusMessage(""); // Очищаем статус
|
||||
statusContainer.style.display = 'none'; // Скрываем общий статус контейнер
|
||||
clientState.isInGame = true;
|
||||
// disableGameControls(); // Начальная деактивация, gameplay.js включит при ходе
|
||||
updateGlobalWindowVariablesForUI(); // Убедимся, что ui.js имеет свежие данные
|
||||
if (turnTimerContainer) turnTimerContainer.style.display = 'block';
|
||||
if (turnTimerSpan) turnTimerSpan.textContent = '--';
|
||||
}
|
||||
|
||||
function resetGameVariables() {
|
||||
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;
|
||||
// Также обновляем глобальные переменные для ui.js
|
||||
updateGlobalWindowVariablesForUI();
|
||||
}
|
||||
|
||||
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'; // Скрываем другой статус
|
||||
}
|
||||
|
||||
// Функции для управления кнопками на экране выбора игры (могут быть вызваны из gameSetup)
|
||||
function disableSetupButtons() {
|
||||
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() {
|
||||
if(createAIGameButton) createAIGameButton.disabled = false;
|
||||
if(createPvPGameButton) createPvPGameButton.disabled = false;
|
||||
if(joinPvPGameButton) joinPvPGameButton.disabled = false;
|
||||
if(findRandomPvPGameButton) findRandomPvPGameButton.disabled = false;
|
||||
// Кнопки в списке игр включаются в updateAvailableGamesList (в gameSetup.js)
|
||||
}
|
||||
|
||||
// --- Сборка зависимостей для передачи в модули ---
|
||||
const dependencies = {
|
||||
socket,
|
||||
clientState, // Объект состояния, который модули могут читать и изменять
|
||||
ui: { // Функции и элементы для управления общим UI и состоянием
|
||||
showAuthScreen,
|
||||
showGameSelectionScreen,
|
||||
showGameScreen,
|
||||
setAuthMessage,
|
||||
setGameStatusMessage,
|
||||
resetGameVariables,
|
||||
updateGlobalWindowVariablesForUI, // Важно для ui.js
|
||||
disableSetupButtons,
|
||||
enableSetupButtons,
|
||||
elements: { // Передаем элементы, нужные для специфической логики модулей
|
||||
// Для auth.js
|
||||
loginForm,
|
||||
registerForm,
|
||||
logoutButton,
|
||||
// Для gameSetup.js
|
||||
createAIGameButton,
|
||||
createPvPGameButton,
|
||||
joinPvPGameButton,
|
||||
findRandomPvPGameButton,
|
||||
gameIdInput,
|
||||
availableGamesDiv,
|
||||
pvpCharacterRadios,
|
||||
// Для gameplay.js (или для обработки gameover здесь)
|
||||
returnToMenuButton,
|
||||
}
|
||||
},
|
||||
// gameUI: window.gameUI // Можно передать, если модули должны напрямую вызывать gameUI.
|
||||
// Но пока gameplay.js будет использовать глобальный window.gameUI
|
||||
};
|
||||
|
||||
// Инициализация модулей
|
||||
initAuth(dependencies);
|
||||
initGameSetup(dependencies);
|
||||
initGameplay(dependencies);
|
||||
|
||||
|
||||
// --- Обработчики событий Socket.IO (глобальные для приложения) ---
|
||||
socket.on('connect', () => {
|
||||
console.log('[Client] Socket connected:', socket.id);
|
||||
setAuthMessage("Успешно подключено к серверу. Вход...");
|
||||
if (clientState.isLoggedIn && clientState.myUserId) {
|
||||
// Пытаемся восстановить состояние игры, если были залогинены
|
||||
socket.emit('requestGameState');
|
||||
} else {
|
||||
// Показываем экран логина, если не залогинены
|
||||
showAuthScreen();
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('disconnect', (reason) => {
|
||||
console.warn('[Client] Disconnected:', reason);
|
||||
setGameStatusMessage(`Отключено от сервера: ${reason}. Попытка переподключения...`, true);
|
||||
// Здесь можно добавить логику для UI, показывающую состояние "отключено"
|
||||
// disableGameControls(); // будет в gameplay
|
||||
if (turnTimerSpan) turnTimerSpan.textContent = 'Откл.';
|
||||
// Не сбрасываем isLoggedIn, чтобы при переподключении можно было восстановить сессию
|
||||
});
|
||||
|
||||
// Общая обработка ошибок от сервера, если они не перехвачены в модулях
|
||||
socket.on('gameError', (data) => {
|
||||
console.error('[Client] Received gameError:', data.message);
|
||||
// Показываем ошибку пользователю
|
||||
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').disabled = false;
|
||||
if(loginForm) loginForm.querySelector('button').disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Обработчик для gameNotFound, который может прийти при реконнекте, если игры нет
|
||||
socket.on('gameNotFound', (data) => {
|
||||
console.log('[Client] Main: Game not found/ended:', data?.message);
|
||||
dependencies.ui.resetGameVariables(); // Сбрасываем игровые переменные
|
||||
clientState.isInGame = false;
|
||||
// disableGameControls(); // в gameplay
|
||||
if (window.gameUI?.showGameOver) window.gameUI.showGameOver(false, "", null, { finalGameState: { isGameOver: false } }); // Скрыть модалку
|
||||
if (turnTimerContainer) turnTimerContainer.style.display = 'none';
|
||||
if (turnTimerSpan) turnTimerSpan.textContent = '--';
|
||||
|
||||
if (clientState.isLoggedIn) {
|
||||
showGameSelectionScreen(clientState.loggedInUsername);
|
||||
setGameStatusMessage(data?.message || "Активная игровая сессия не найдена.");
|
||||
} else {
|
||||
showAuthScreen();
|
||||
setAuthMessage(data?.message || "Пожалуйста, войдите.");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// --- Инициализация UI ---
|
||||
showAuthScreen(); // Показываем начальный экран аутентификации
|
||||
});
|
Loading…
x
Reference in New Issue
Block a user