/** * ============================================================================= * Texas Hold'em - Главный клиентский модуль * UI, WebSocket, звуки, управление игрой * ============================================================================= */ // ============================================================================= // ГЛОБАЛЬНЫЕ ПЕРЕМЕННЫЕ // ============================================================================= let game = null; // Локальная игра (одиночный режим) let ws = null; // WebSocket соединение let currentPlayerId = null; // ID текущего игрока let currentRoomId = null; // ID текущей комнаты let isMultiplayer = false; // Режим игры let settings = {}; // Настройки let leaderboard = []; // Таблица лидеров let soundEnabled = true; // Звук включён let currentGamePhase = 'waiting'; // Текущая фаза игры для мультиплеера let wsConnecting = false; // Флаг подключения WebSocket let botPersonalities = null; // Персональности ботов (загружается из pokerAI) // Выбранные опции const selectedOptions = { 'bot-count': '1', 'ai-difficulty': '1', 'starting-stack': '1000', 'blinds': '5/10', 'max-players': '6' }; // Звуки const sounds = { deal: null, check: null, call: null, bet: null, fold: null, win: null, chip: null, message: null }; // ============================================================================= // ИНИЦИАЛИЗАЦИЯ // ============================================================================= document.addEventListener('DOMContentLoaded', () => { loadSettings(); loadLeaderboard(); initSounds(); loadCardBackSettings(); // Загружаем персональности ботов if (typeof pokerAI !== 'undefined' && pokerAI.personalities) { botPersonalities = {}; pokerAI.personalities.forEach(p => { botPersonalities[p.style] = p; }); } // Инициализируем авторизацию if (typeof initAuth === 'function') { initAuth(); } else { // Если auth.js не загружен, показываем главное меню showScreen('main-menu'); } }); /** * Инициализация звуков */ function initSounds() { // Простые звуки через Web Audio API const audioContext = new (window.AudioContext || window.webkitAudioContext)(); const createBeep = (frequency, duration) => { return () => { if (!soundEnabled) return; const oscillator = audioContext.createOscillator(); const gainNode = audioContext.createGain(); oscillator.connect(gainNode); gainNode.connect(audioContext.destination); oscillator.frequency.value = frequency; oscillator.type = 'sine'; gainNode.gain.setValueAtTime(0.1, audioContext.currentTime); gainNode.gain.exponentialRampToValueAtTime(0.01, audioContext.currentTime + duration); oscillator.start(audioContext.currentTime); oscillator.stop(audioContext.currentTime + duration); }; }; sounds.deal = createBeep(400, 0.1); sounds.check = createBeep(600, 0.1); sounds.call = createBeep(500, 0.15); sounds.bet = createBeep(700, 0.2); sounds.fold = createBeep(300, 0.2); sounds.win = createBeep(800, 0.4); sounds.chip = createBeep(1000, 0.05); sounds.message = createBeep(900, 0.1); } /** * Воспроизвести звук */ function playSound(name) { if (sounds[name] && soundEnabled) { sounds[name](); } } // ============================================================================= // НАВИГАЦИЯ // ============================================================================= /** * Показать экран */ function showScreen(screenId) { document.querySelectorAll('.screen').forEach(screen => { screen.classList.remove('active'); }); document.getElementById(screenId).classList.add('active'); // Особая логика для мультиплеера if (screenId === 'multiplayer-menu') { connectWebSocket(); } // Загрузка лидерборда if (screenId === 'leaderboard-screen') { renderLeaderboard(); } // Загрузка админских настроек if (screenId === 'admin-screen') { if (typeof loadAdminSettings === 'function') { loadAdminSettings(); } } } /** * Переключение вкладок */ function switchTab(tabId) { const parent = event.target.closest('.glass-container'); parent.querySelectorAll('.tab').forEach(tab => { tab.classList.remove('active'); }); parent.querySelectorAll('.tab-content').forEach(content => { content.classList.remove('active'); }); event.target.classList.add('active'); document.getElementById(tabId).classList.add('active'); } /** * Выбор опции */ function selectOption(button, optionGroup) { const group = button.parentElement; group.querySelectorAll('.btn-option').forEach(btn => { btn.classList.remove('active'); }); button.classList.add('active'); selectedOptions[optionGroup] = button.dataset.value; } // ============================================================================= // ОДИНОЧНАЯ ИГРА // ============================================================================= /** * Начать одиночную игру */ function startSinglePlayer() { isMultiplayer = false; const playerName = document.getElementById('sp-player-name').value || 'Игрок'; const botCount = parseInt(selectedOptions['bot-count']); const aiDifficulty = parseInt(selectedOptions['ai-difficulty']); const startingStack = parseInt(selectedOptions['starting-stack']); // Сохраняем имя localStorage.setItem('playerName', playerName); // Очищаем чат перед началом новой игры clearGameChat(); // Создаём игру game = new PokerGame({ smallBlind: 5, bigBlind: 10, onUpdate: updateGameUI, onAction: onPlayerAction, onHandEnd: onHandEnd }); // Добавляем игрока const player = new Player('player_0', playerName, startingStack, false); game.addPlayer(player); currentPlayerId = player.id; // Добавляем ботов с персональностями const personalities = typeof pokerAI !== 'undefined' && pokerAI.personalities ? pokerAI.personalities : []; // Перемешиваем персональности для разнообразия const shuffledPersonalities = personalities.length > 0 ? [...personalities].sort(() => Math.random() - 0.5) : []; for (let i = 0; i < botCount; i++) { let botName, personalityId, personality; if (shuffledPersonalities.length > 0) { // Используем персональность из списка personality = shuffledPersonalities[i % shuffledPersonalities.length]; botName = personality.name; personalityId = personality.style; } else { // Запасной вариант botName = pokerAI.getRandomName(); personalityId = 'professional'; } const bot = new Player(`bot_${i}`, botName, startingStack, true, aiDifficulty); bot.personalityId = personalityId; game.addPlayer(bot); } // Показываем игровой экран showScreen('game-screen'); // Начинаем раздачу setTimeout(() => { game.startNewHand(); }, 500); } /** * Новая раздача */ function startNewHand() { document.getElementById('new-hand-btn').style.display = 'none'; if (isMultiplayer) { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'new_hand' })); } } else { if (game && game.getPlayersWithChips().length >= 2) { game.startNewHand(); } else { showNotification('Недостаточно игроков с фишками', 'error'); } } } // ============================================================================= // МУЛЬТИПЛЕЕР // ============================================================================= /** * Подключение к WebSocket серверу */ function connectWebSocket() { const serverUrl = settings.serverUrl || 'ws://localhost:3000'; // Если уже подключены или подключаемся if ((ws && ws.readyState === WebSocket.OPEN) || wsConnecting) { return Promise.resolve(); } // Если соединение в процессе закрытия, ждём if (ws && ws.readyState === WebSocket.CONNECTING) { return new Promise((resolve) => { ws.addEventListener('open', resolve, { once: true }); }); } wsConnecting = true; return new Promise((resolve, reject) => { try { ws = new WebSocket(serverUrl); ws.onopen = () => { console.log('Подключено к серверу'); wsConnecting = false; document.getElementById('room-list').innerHTML = '
Загрузка комнат...
'; resolve(); }; ws.onmessage = (event) => { const message = JSON.parse(event.data); handleServerMessage(message); }; ws.onclose = () => { console.log('Отключено от сервера'); wsConnecting = false; ws = null; document.getElementById('room-list').innerHTML = '
Соединение потеряно.
'; }; ws.onerror = (error) => { console.error('WebSocket ошибка:', error); wsConnecting = false; document.getElementById('room-list').innerHTML = '
Ошибка подключения к серверу.
'; reject(error); }; } catch (error) { console.error('Ошибка создания WebSocket:', error); wsConnecting = false; reject(error); } }); } /** * Обработка сообщений от сервера */ function handleServerMessage(message) { switch (message.type) { case 'room_list': renderRoomList(message.rooms); break; case 'room_joined': currentPlayerId = message.playerId; currentRoomId = message.roomId; showRoomLobby(message.room); break; case 'player_joined': updateLobbyPlayers(message.room); addChatMessage('lobby', null, `${message.player.name} присоединился`, true); break; case 'player_left': case 'player_disconnected': updateLobbyPlayers(message.room); break; case 'game_started': isMultiplayer = true; currentGamePhase = message.room.gamePhase; showScreen('game-screen'); updateGameUIFromServer(message.room); break; case 'game_update': currentGamePhase = message.room.gamePhase; updateGameUIFromServer(message.room); if (message.lastAction) { playSound(message.lastAction.action); } break; case 'chat': if (document.getElementById('game-screen').classList.contains('active')) { addChatMessage('game', message.playerName, message.message); } else { addChatMessage('lobby', message.playerName, message.message); } playSound('message'); break; case 'error': showNotification(message.message, 'error'); break; } } /** * Отрисовка списка комнат */ function renderRoomList(rooms) { const container = document.getElementById('room-list'); if (rooms.length === 0) { container.innerHTML = '
Нет доступных комнат
'; return; } container.innerHTML = rooms.map(room => `

${room.name}

Блайнды: ${room.smallBlind}/${room.bigBlind}
${room.players}/${room.maxPlayers}
`).join(''); } /** * Обновить список комнат */ function refreshRooms() { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'get_rooms' })); } else { connectWebSocket(); } } /** * Создать комнату */ async function createRoom() { // Проверяем и устанавливаем соединение if (!ws || ws.readyState !== WebSocket.OPEN) { try { await connectWebSocket(); } catch (e) { showNotification('Не удалось подключиться к серверу', 'error'); return; } } // Повторная проверка после await if (!ws || ws.readyState !== WebSocket.OPEN) { showNotification('Нет соединения с сервером', 'error'); return; } const playerName = document.getElementById('mp-player-name').value || 'Игрок'; const roomName = document.getElementById('room-name').value || `Комната ${playerName}`; const [smallBlind, bigBlind] = selectedOptions['blinds'].split('/').map(Number); const maxPlayers = parseInt(selectedOptions['max-players']); localStorage.setItem('playerName', playerName); ws.send(JSON.stringify({ type: 'create_room', roomName, playerName, smallBlind, bigBlind, maxPlayers })); } /** * Присоединиться к комнате */ async function joinRoom(roomId) { if (!ws || ws.readyState !== WebSocket.OPEN) { try { await connectWebSocket(); } catch (e) { showNotification('Не удалось подключиться к серверу', 'error'); return; } } if (!ws || ws.readyState !== WebSocket.OPEN) { showNotification('Нет соединения с сервером', 'error'); return; } const playerName = document.getElementById('mp-player-name').value || 'Игрок'; localStorage.setItem('playerName', playerName); ws.send(JSON.stringify({ type: 'join_room', roomId, playerName })); } /** * Показать лобби комнаты */ function showRoomLobby(room) { showScreen('room-lobby'); document.getElementById('lobby-room-name').textContent = room.name; document.getElementById('lobby-blinds').textContent = `${room.smallBlind}/${room.bigBlind}`; // Генерируем и отображаем ссылку на комнату updateRoomLink(room.id); updateLobbyPlayers(room); } /** * Обновить игроков в лобби */ function updateLobbyPlayers(room) { const container = document.getElementById('lobby-players'); container.innerHTML = room.players.map((player, index) => `
${player.name.charAt(0).toUpperCase()}
${player.name}
`).join(''); document.getElementById('lobby-player-count').textContent = `${room.players.length}/${room.maxPlayers || 6}`; const startBtn = document.getElementById('start-game-btn'); startBtn.style.display = room.players[0]?.id === currentPlayerId ? 'block' : 'none'; startBtn.disabled = room.players.length < 2; // Обновляем ссылку на комнату (если ID изменился) if (room.id) { updateRoomLink(room.id); } } /** * Выйти из комнаты */ function leaveRoom() { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'leave_room' })); } currentRoomId = null; showScreen('multiplayer-menu'); } /** * Начать мультиплеерную игру */ function startMultiplayerGame() { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'start_game' })); } } // ============================================================================= // ИГРОВОЙ UI // ============================================================================= /** * Обновить UI игры (локальный режим) */ function updateGameUI() { if (!game) return; document.getElementById('pot-amount').textContent = game.pot; const phaseNames = { 'waiting': 'Ожидание', 'preflop': 'Префлоп', 'flop': 'Флоп', 'turn': 'Тёрн', 'river': 'Ривер', 'showdown': 'Вскрытие' }; document.getElementById('game-phase').textContent = phaseNames[game.gamePhase] || game.gamePhase; renderCommunityCards(game.communityCards); renderPlayers(game.players, game.currentPlayerIndex, currentPlayerId); const player = game.players.find(p => p.id === currentPlayerId); if (player) { renderPlayerHand(player.hand, game.communityCards); updatePlayerBalance(player); } updateActionPanel(player, game); } /** * Обновить UI из данных сервера */ function updateGameUIFromServer(room) { document.getElementById('pot-amount').textContent = room.pot; const phaseNames = { 'waiting': 'Ожидание', 'preflop': 'Префлоп', 'flop': 'Флоп', 'turn': 'Тёрн', 'river': 'Ривер', 'showdown': 'Вскрытие' }; document.getElementById('game-phase').textContent = phaseNames[room.gamePhase] || room.gamePhase; // Конвертируем данные сервера в объекты Card const communityCards = room.communityCards.map(c => new Card(c.suit, c.rank)); renderCommunityCards(communityCards); // Конвертируем игроков const players = room.players.map((p, i) => { const player = { ...p, hand: p.hand ? p.hand.map(c => new Card(c.suit, c.rank)) : [] }; return player; }); renderPlayers(players, room.currentPlayerIndex, currentPlayerId); const myPlayer = players.find(p => p.id === currentPlayerId); if (myPlayer) { renderPlayerHand(myPlayer.hand, communityCards); updatePlayerBalance(myPlayer); updateActionPanelFromServer(myPlayer, room); } // Показываем кнопку новой раздачи если игра завершена if (room.gamePhase === 'showdown' || !room.isGameStarted) { setTimeout(() => { document.getElementById('new-hand-btn').style.display = 'block'; }, 2000); } } /** * Отрисовка общих карт */ function renderCommunityCards(cards) { const container = document.getElementById('community-cards'); container.innerHTML = ''; for (let i = 0; i < 5; i++) { if (cards[i]) { const cardEl = cards[i].toHTML ? cards[i].toHTML() : createCardElement(cards[i]); cardEl.classList.add('dealing'); cardEl.style.animationDelay = `${i * 0.1}s`; container.appendChild(cardEl); } else { const placeholder = document.createElement('div'); placeholder.className = 'card card-placeholder'; placeholder.style.opacity = '0.2'; container.appendChild(placeholder); } } playSound('deal'); } /** * Создать элемент карты из данных */ function createCardElement(cardData, isSmall = false) { const card = document.createElement('div'); card.className = `card ${cardData.suit}${isSmall ? ' card-small' : ''}`; const symbols = { hearts: '♥', diamonds: '♦', clubs: '♣', spades: '♠' }; card.innerHTML = ` ${cardData.rank} ${symbols[cardData.suit]} `; return card; } /** * Отрисовка игроков за столом */ function renderPlayers(players, currentIndex, myPlayerId) { const container = document.getElementById('player-positions'); container.innerHTML = ''; // Позиции для ставок (относительно позиции игрока) const betPositions = [ { top: '-40px', left: '50%' }, { top: '-30px', left: '80%' }, { top: '50%', left: '100%' }, { top: '100%', left: '50%' }, { top: '50%', left: '-30%' }, { top: '-30px', left: '20%' } ]; players.forEach((player, index) => { // Пропускаем текущего игрока (он отображается снизу) if (player.id === myPlayerId) return; const seat = document.createElement('div'); seat.className = 'player-seat'; seat.dataset.position = index; const isCurrentTurn = index === currentIndex; const playerBox = document.createElement('div'); playerBox.className = `player-box ${player.folded ? 'folded' : ''} ${isCurrentTurn ? 'current-turn' : ''}`; playerBox.innerHTML = `
${player.name}
${player.chips}
${player.lastAction ? `
${player.lastAction}
` : ''} `; // Определяем, нужно ли показывать карты // Показываем карты только на showdown и только не фолднувшим игрокам const gamePhase = isMultiplayer ? currentGamePhase : (game ? game.gamePhase : 'waiting'); const showCards = gamePhase === 'showdown' && !player.folded; // Карты игрока (мини) if (showCards && player.hand && player.hand.length > 0) { const cardsDiv = document.createElement('div'); cardsDiv.className = 'player-cards-mini'; player.hand.forEach(card => { const cardEl = card.toHTML ? card.toHTML(true) : createCardElement(card, true); cardsDiv.appendChild(cardEl); }); playerBox.appendChild(cardsDiv); } else if ((player.hasCards || (player.hand && player.hand.length > 0)) && !player.folded) { // Карты рубашкой (не показываем карты соперников до showdown) const cardsDiv = document.createElement('div'); cardsDiv.className = 'player-cards-mini'; cardsDiv.innerHTML = `
`; playerBox.appendChild(cardsDiv); } seat.appendChild(playerBox); // Позиционные маркеры if (player.isDealer) { const dealerBtn = document.createElement('div'); dealerBtn.className = 'dealer-button'; dealerBtn.textContent = 'D'; dealerBtn.style.cssText = 'bottom: -35px; left: 50%; transform: translateX(-50%);'; seat.appendChild(dealerBtn); } if (player.isSmallBlind || player.isBigBlind) { const blindIndicator = document.createElement('div'); blindIndicator.className = `blind-indicator ${player.isSmallBlind ? 'sb' : 'bb'}`; blindIndicator.textContent = player.isSmallBlind ? 'SB' : 'BB'; blindIndicator.style.cssText = 'bottom: -35px; right: 0;'; seat.appendChild(blindIndicator); } // Ставка игрока if (player.bet > 0) { const betDisplay = document.createElement('div'); betDisplay.className = 'player-bet-display'; betDisplay.textContent = player.bet; betDisplay.style.cssText = `${betPositions[index % 6].top}; left: ${betPositions[index % 6].left};`; seat.appendChild(betDisplay); } container.appendChild(seat); }); // Применяем стиль рубашки к новым картам applyCardBackToNewCards(); } /** * Применить стиль рубашки к новым картам на столе */ function applyCardBackToNewCards() { const style = settings.cardBackStyle || 'default'; if (style === 'custom') { const url = localStorage.getItem('customCardBack'); if (url) { document.querySelectorAll('.card-back:not(.styled)').forEach(card => { card.classList.add('style-custom', 'styled'); card.style.backgroundImage = `url(${url})`; card.style.backgroundSize = 'cover'; card.style.backgroundPosition = 'center'; }); } } else { document.querySelectorAll('.card-back:not(.styled)').forEach(card => { card.classList.add(`style-${style}`, 'styled'); }); } } /** * Отрисовка руки игрока */ function renderPlayerHand(hand, communityCards) { const container = document.getElementById('player-cards'); // Проверяем, изменились ли карты const currentHandKey = hand && hand.length > 0 ? hand.map(c => `${c.suit}-${c.rank}`).join(',') : 'empty'; // Если карты не изменились, не перерисовываем if (container.dataset.currentHand === currentHandKey) { // Только обновляем силу руки, если изменились общие карты if (settings.showHandStrength !== false && hand && hand.length > 0) { const strength = getHandStrength(hand, communityCards || []); document.getElementById('hand-strength').textContent = strength || ''; } return; } // Запоминаем текущие карты container.dataset.currentHand = currentHandKey; container.innerHTML = ''; if (!hand || hand.length === 0) { container.innerHTML = `
`; applyCardBackToNewCards(); return; } hand.forEach((card, i) => { const cardEl = card.toHTML ? card.toHTML() : createCardElement(card); cardEl.classList.add('dealing'); cardEl.style.animationDelay = `${i * 0.15}s`; container.appendChild(cardEl); }); // Показываем силу руки if (settings.showHandStrength !== false) { const strength = getHandStrength(hand, communityCards || []); document.getElementById('hand-strength').textContent = strength || ''; } } /** * Обновить баланс игрока */ function updatePlayerBalance(player) { if (!player) return; // Обновляем имя игрока const nameDisplay = document.getElementById('player-name-display'); if (nameDisplay) { nameDisplay.textContent = player.name || 'Игрок'; } // Обновляем баланс const balanceDisplay = document.getElementById('player-balance'); if (balanceDisplay) { const chips = player.chips || 0; balanceDisplay.textContent = chips; // Добавляем анимацию при изменении баланса if (balanceDisplay.dataset.lastValue && balanceDisplay.dataset.lastValue !== chips.toString()) { const oldValue = parseInt(balanceDisplay.dataset.lastValue); const newValue = chips; if (newValue > oldValue) { balanceDisplay.parentElement.classList.add('balance-increase'); setTimeout(() => balanceDisplay.parentElement.classList.remove('balance-increase'), 500); } else if (newValue < oldValue) { balanceDisplay.parentElement.classList.add('balance-decrease'); setTimeout(() => balanceDisplay.parentElement.classList.remove('balance-decrease'), 500); } } balanceDisplay.dataset.lastValue = chips.toString(); } } /** * Обновить панель действий */ function updateActionPanel(player, gameState) { if (!player || !gameState || !gameState.isGameStarted) { document.getElementById('action-panel').style.display = 'none'; return; } const isMyTurn = gameState.getCurrentPlayer()?.id === player.id; document.getElementById('action-panel').style.display = isMyTurn ? 'block' : 'none'; if (!isMyTurn) return; const toCall = gameState.currentBet - player.bet; const canCheck = toCall === 0; const canBet = gameState.currentBet === 0; document.getElementById('btn-check').style.display = canCheck ? 'inline-flex' : 'none'; document.getElementById('btn-call').style.display = !canCheck ? 'inline-flex' : 'none'; document.getElementById('btn-bet').style.display = canBet ? 'inline-flex' : 'none'; document.getElementById('btn-raise').style.display = !canBet ? 'inline-flex' : 'none'; document.getElementById('call-amount').textContent = toCall; // Настройка слайдера const slider = document.getElementById('bet-slider'); const minBet = canBet ? gameState.bigBlind : gameState.currentBet + gameState.lastRaiseAmount; slider.min = minBet; slider.max = player.chips + player.bet; slider.value = minBet; document.getElementById('bet-value').value = minBet; } /** * Обновить панель действий из данных сервера */ function updateActionPanelFromServer(player, room) { if (!player || room.gamePhase === 'showdown' || !room.isGameStarted) { document.getElementById('action-panel').style.display = 'none'; return; } const isMyTurn = room.currentPlayerId === player.id; document.getElementById('action-panel').style.display = isMyTurn ? 'block' : 'none'; if (!isMyTurn) return; const toCall = room.currentBet - player.bet; const canCheck = toCall === 0; const canBet = room.currentBet === 0; document.getElementById('btn-check').style.display = canCheck ? 'inline-flex' : 'none'; document.getElementById('btn-call').style.display = !canCheck ? 'inline-flex' : 'none'; document.getElementById('btn-bet').style.display = canBet ? 'inline-flex' : 'none'; document.getElementById('btn-raise').style.display = !canBet ? 'inline-flex' : 'none'; document.getElementById('call-amount').textContent = toCall; const slider = document.getElementById('bet-slider'); const bigBlind = room.bigBlind || 10; const minBet = canBet ? bigBlind : room.minRaise || room.currentBet * 2; slider.min = minBet; slider.max = player.chips + player.bet; slider.value = minBet; document.getElementById('bet-value').value = minBet; } // ============================================================================= // ДЕЙСТВИЯ ИГРОКА // ============================================================================= /** * Выполнить действие */ function playerAction(action) { playSound(action); if (isMultiplayer) { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'action', action: action, amount: 0 })); } } else { if (game) { game.processAction(currentPlayerId, action, 0); } } hideBetSlider(); } /** * Показать слайдер ставки */ function showBetSlider() { document.getElementById('bet-slider-container').style.display = 'block'; } /** * Скрыть слайдер ставки */ function hideBetSlider() { document.getElementById('bet-slider-container').style.display = 'none'; } /** * Обновить значение ставки из слайдера */ function updateBetValue() { const slider = document.getElementById('bet-slider'); document.getElementById('bet-value').value = slider.value; } /** * Обновить слайдер из инпута */ function updateSliderFromInput() { const input = document.getElementById('bet-value'); const slider = document.getElementById('bet-slider'); let value = parseInt(input.value); value = Math.max(parseInt(slider.min), Math.min(parseInt(slider.max), value)); slider.value = value; input.value = value; } /** * Установить пресет ставки */ function setBetPreset(multiplier) { let pot; if (isMultiplayer) { pot = parseInt(document.getElementById('pot-amount').textContent) || 0; } else { pot = game ? game.pot : 0; } const betAmount = Math.floor(pot * multiplier); const slider = document.getElementById('bet-slider'); const value = Math.max(parseInt(slider.min), Math.min(parseInt(slider.max), betAmount)); slider.value = value; document.getElementById('bet-value').value = value; } /** * Подтвердить ставку */ function confirmBet() { const amount = parseInt(document.getElementById('bet-value').value); const action = document.getElementById('btn-bet').style.display !== 'none' ? 'bet' : 'raise'; playSound('bet'); if (isMultiplayer) { if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'action', action: action, amount: amount })); } } else { if (game) { game.processAction(currentPlayerId, action, amount); } } hideBetSlider(); } // ============================================================================= // СОБЫТИЯ ИГРЫ // ============================================================================= /** * Событие действия игрока */ function onPlayerAction(player, action, amount) { // Можно добавить анимации или уведомления console.log(`${player.name}: ${action} ${amount || ''}`); } /** * Событие завершения раздачи */ function onHandEnd(result) { playSound('win'); // Показываем результат const modal = document.getElementById('hand-result-modal'); const title = document.getElementById('result-title'); const details = document.getElementById('result-details'); const winner = result.winners[0]; title.textContent = result.winners.length > 1 ? 'Сплит!' : `${winner.name} победил!`; let detailsHTML = `
+${winner.amount}
`; if (winner.hand) { detailsHTML += `
${winner.hand.name}
`; } if (result.hands && result.hands.length > 1) { detailsHTML += '
'; result.hands.forEach(h => { detailsHTML += `
${h.player.name}: ${h.hand.name}
`; }); detailsHTML += '
'; } details.innerHTML = detailsHTML; modal.classList.add('active'); // Обновляем лидерборд updateLeaderboard(result); // НОВОЕ: Генерируем эмоциональные реакции ботов if (!isMultiplayer && game) { generateBotEmotions(result); // НОВОЕ: Поздравления игрока от ботов при сильной руке generatePlayerCongratulations(result); } // Показываем кнопку новой раздачи setTimeout(() => { document.getElementById('new-hand-btn').style.display = 'block'; }, 1000); } /** * Генерировать эмоциональные реакции ботов после раздачи */ async function generateBotEmotions(result) { if (!game || !result) return; const winnerIds = result.winners.map(w => w.id); const potSize = result.pot || game.pot || 0; // Определяем, был ли олл-ин const wasAllIn = game.players.some(p => p.chips === 0 && !p.folded); // Случайно решаем, будет ли бот реагировать (30-50% вероятность) const shouldReact = Math.random() < (wasAllIn ? 0.5 : 0.3); if (!shouldReact) return; // Выбираем случайного бота для реакции const activeBots = game.players.filter(p => p.isAI && !p.folded); if (activeBots.length === 0) return; const bot = activeBots[Math.floor(Math.random() * activeBots.length)]; const isWinner = winnerIds.includes(bot.id); // Получаем личность бота let botPersonality; if (typeof botPersonalities !== 'undefined' && bot.personalityId) { botPersonality = botPersonalities[bot.personalityId]; } if (!botPersonality) { botPersonality = { style: 'professional' }; } // Задержка перед реакцией (500-1500ms) setTimeout(async () => { try { let reaction; if (typeof llmChat !== 'undefined' && llmChat.generateEmotionalReaction) { reaction = await llmChat.generateEmotionalReaction( bot, botPersonality, isWinner, potSize, wasAllIn, { phase: currentGamePhase } ); } else { // Запасной вариант const emotions = isWinner ? ['Отлично!', 'Неплохо!', 'GG', 'Да!'] : ['Эх...', 'Невезение', 'Бывает', 'Хм']; reaction = emotions[Math.floor(Math.random() * emotions.length)]; } if (reaction) { addChatMessage('game', bot.name, reaction); } } catch (error) { console.error('Ошибка генерации эмоции:', error); } }, 500 + Math.random() * 1000); } /** * Генерировать поздравления игрока с сильной рукой */ async function generatePlayerCongratulations(result) { if (!game || !result || !result.winners || !result.hands) return; const winner = result.winners[0]; const potSize = result.pot || game.pot || 0; // Проверяем, что победитель - игрок (не бот) const winnerData = game.players.find(p => p.id === winner.id); if (!winnerData || winnerData.isAI) return; // Получаем руку победителя const winnerHand = result.hands?.find(h => h.player.id === winner.id)?.hand; if (!winnerHand) return; const handRank = winnerHand.rank; const handName = winnerHand.name; // Определяем вероятность поздравления в зависимости от силы руки let congratsProbability = 0; if (handRank >= 10) { // Роял-флеш - 90% вероятность поздравления congratsProbability = 0.9; } else if (handRank >= 9) { // Стрит-флеш - 80% вероятность congratsProbability = 0.8; } else if (handRank >= 8) { // Каре - 60% вероятность congratsProbability = 0.6; } else if (handRank >= 7) { // Фулл-хаус - 40% вероятность congratsProbability = 0.4; } else if (handRank >= 6) { // Флеш - 25% вероятность congratsProbability = 0.25; } else if (handRank >= 5) { // Стрит - 15% вероятность congratsProbability = 0.15; } else { // Слабые руки - не поздравляем return; } // Решаем, будем ли поздравлять if (Math.random() > congratsProbability) return; // Выбираем случайного бота для поздравления const availableBots = game.players.filter(p => p.isAI && !p.folded); if (availableBots.length === 0) return; const bot = availableBots[Math.floor(Math.random() * availableBots.length)]; // Получаем личность бота let botPersonality; if (typeof botPersonalities !== 'undefined' && bot.personalityId) { botPersonality = botPersonalities[bot.personalityId]; } if (!botPersonality) { botPersonality = { style: 'professional' }; } // Задержка перед поздравлением (1000-2500ms, чтобы не конфликтовать с эмоциями) setTimeout(async () => { try { let congratulation; if (typeof llmChat !== 'undefined' && llmChat.generatePlayerCongratulation) { congratulation = await llmChat.generatePlayerCongratulation( bot, botPersonality, winnerData.name, handRank, handName, potSize, { phase: currentGamePhase } ); } else { // Запасной вариант const congrats = [ `${handName}! Респект!`, 'Сильная рука!', 'Впечатляет!', 'Молодец!', 'GG WP!' ]; congratulation = congrats[Math.floor(Math.random() * congrats.length)]; } if (congratulation) { addChatMessage('game', bot.name, congratulation); } } catch (error) { console.error('Ошибка генерации поздравления:', error); } }, 1000 + Math.random() * 1500); } /** * Закрыть модальное окно результата */ function closeResultModal() { document.getElementById('hand-result-modal').classList.remove('active'); } // ============================================================================= // ЧАТ // ============================================================================= /** * Добавить сообщение в чат */ function addChatMessage(chatType, sender, message, isSystem = false, isTyping = false) { const containerId = chatType === 'game' ? 'game-chat-messages' : 'lobby-chat-messages'; const container = document.getElementById(containerId); const msgDiv = document.createElement('div'); msgDiv.className = `chat-message ${isSystem ? 'system' : ''} ${isTyping ? 'typing-indicator' : ''}`; if (isTyping) { msgDiv.dataset.sender = sender; } if (isSystem) { msgDiv.textContent = message; } else { const time = new Date().toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' }); if (isTyping) { msgDiv.innerHTML = ` ${sender}: печатает... ${time} `; } else { msgDiv.innerHTML = ` ${sender}: ${message} ${time} `; } } container.appendChild(msgDiv); container.scrollTop = container.scrollHeight; } /** * Удалить индикатор "печатает..." для конкретного отправителя */ function removeTypingIndicator(sender, chatType = 'game') { const containerId = chatType === 'game' ? 'game-chat-messages' : 'lobby-chat-messages'; const container = document.getElementById(containerId); if (!container) return; // Используем querySelectorAll и проверяем dataset, чтобы избежать проблем с экранированием кавычек const typingIndicators = container.querySelectorAll('.typing-indicator'); typingIndicators.forEach(indicator => { if (indicator.dataset.sender === sender) { indicator.remove(); } }); } /** * Отправить сообщение в чат лобби */ function sendLobbyChat() { const input = document.getElementById('lobby-chat-input'); const message = input.value.trim(); if (!message) return; if (ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'chat', message: message })); } input.value = ''; } /** * Отправить сообщение в игровой чат */ function sendGameChat() { const input = document.getElementById('game-chat-input'); const message = input.value.trim(); if (!message) return; if (isMultiplayer && ws && ws.readyState === WebSocket.OPEN) { ws.send(JSON.stringify({ type: 'chat', message: message })); } else { // Одиночная игра - ИИ отвечает const playerName = localStorage.getItem('playerName') || 'Игрок'; addChatMessage('game', playerName, message); // Получаем всех ботов для ответа const bots = game.players.filter(p => p.isAI && !p.folded); if (bots.length > 0) { // Проверяем, обращается ли игрок к конкретному боту let targetBot = null; if (typeof llmChat !== 'undefined' && llmChat.detectBotMention) { targetBot = llmChat.detectBotMention(message, bots); } // Если обращение не найдено, выбираем случайного бота const bot = targetBot || bots[Math.floor(Math.random() * bots.length)]; // Формируем расширенный контекст игры для LLM const gameContext = { // Фаза игры phase: currentGamePhase, phaseRu: { 'preflop': 'префлоп (карты только что розданы)', 'flop': 'флоп (3 карты на столе)', 'turn': 'терн (4 карты на столе)', 'river': 'ривер (5 карт на столе)', 'showdown': 'вскрытие карт' }[currentGamePhase] || currentGamePhase, // Банк и текущая ставка pot: game.pot, currentBet: game.currentBet, // Общие карты на столе communityCards: game.communityCards?.map(c => `${c.rank}${SUIT_SYMBOLS[c.suit]}`) || [], communityCardsCount: game.communityCards?.length || 0, // Мои данные myName: bot.name, myChips: bot.chips, myBet: bot.bet, myTotalBet: bot.totalBet, myCards: bot.hand?.map(c => `${c.rank}${SUIT_SYMBOLS[c.suit]}`) || [], myPosition: bot.isDealer ? 'дилер' : (bot.isSmallBlind ? 'малый блайнд' : (bot.isBigBlind ? 'большой блайнд' : 'обычная позиция')), // Информация о других игроках players: game.players.map(p => ({ name: p.name, chips: p.chips, bet: p.bet, totalBet: p.totalBet, folded: p.folded, allIn: p.allIn, lastAction: p.lastAction, isDealer: p.isDealer, isMe: p.id === bot.id })), // Активные игроки (не спасовали) activePlayers: game.players.filter(p => !p.folded).map(p => p.name), foldedPlayers: game.players.filter(p => p.folded).map(p => p.name), // Последние действия lastPlayerAction: message, mentionedByName: targetBot !== null // Указываем, что игрок обратился по имени }; // Получаем личность бота const botPersonality = typeof botPersonalities !== 'undefined' ? botPersonalities[bot.personalityId] || botPersonalities.professional : { style: 'default' }; // Показываем индикатор "печатает..." addChatMessage('game', bot.name, '...', false, true); // Вызываем LLM чат (async () => { try { let response; if (typeof llmChat !== 'undefined' && llmChat.getSettings().llmEnabled) { response = await llmChat.chat(bot.id, botPersonality, message, gameContext); } else { // Запасные ответы если LLM отключён await new Promise(resolve => setTimeout(resolve, 500 + Math.random() * 1500)); response = llmChat?.getFallbackResponse(botPersonality, message, gameContext) || 'Удачи!'; } // Удаляем индикатор "печатает..." removeTypingIndicator(bot.name); // Добавляем ответ addChatMessage('game', bot.name, response); } catch (error) { console.error('Ошибка LLM чата:', error); removeTypingIndicator(bot.name); addChatMessage('game', bot.name, 'Удачи!'); } })(); } } input.value = ''; } /** * Обработка Enter в чате лобби */ function handleLobbyChatKey(event) { if (event.key === 'Enter') { sendLobbyChat(); } } /** * Обработка Enter в игровом чате */ function handleGameChatKey(event) { if (event.key === 'Enter') { sendGameChat(); } } /** * Переключить чат */ function toggleChat() { document.getElementById('game-chat').classList.toggle('expanded'); } /** * Очистить игровой чат */ function clearGameChat() { const container = document.getElementById('game-chat-messages'); if (container) { container.innerHTML = ''; } } // ============================================================================= // НАСТРОЙКИ // ============================================================================= /** * Загрузить настройки */ function loadSettings() { const saved = localStorage.getItem('pokerSettings'); if (saved) { settings = JSON.parse(saved); } else { settings = { sound: true, animations: true, showHandStrength: true, autofold: true, serverUrl: 'ws://localhost:3000', llmEnabled: false, llmProvider: 'ollama', llmApiUrl: 'http://localhost:11434', llmModel: 'llama3.2', llmApiKey: '' }; } // Применяем настройки к UI document.getElementById('setting-sound').checked = settings.sound !== false; document.getElementById('setting-animations').checked = settings.animations !== false; document.getElementById('setting-hand-strength').checked = settings.showHandStrength !== false; document.getElementById('setting-autofold').checked = settings.autofold !== false; document.getElementById('server-url').value = settings.serverUrl || 'ws://localhost:3000'; // LLM настройки const llmEnabled = document.getElementById('setting-llm-enabled'); const llmProvider = document.getElementById('llm-provider'); const llmApiUrl = document.getElementById('llm-api-url'); const llmModel = document.getElementById('llm-model'); const llmApiKey = document.getElementById('llm-api-key'); if (llmEnabled) llmEnabled.checked = settings.llmEnabled || false; if (llmProvider) llmProvider.value = settings.llmProvider || 'ollama'; if (llmApiUrl) llmApiUrl.value = settings.llmApiUrl || 'http://localhost:11434'; if (llmModel) llmModel.value = settings.llmModel || 'llama3.2'; if (llmApiKey) llmApiKey.value = settings.llmApiKey || ''; // Обновляем видимость API ключа updateLLMProviderUI(); soundEnabled = settings.sound !== false; } /** * Обновить UI для LLM провайдера */ function updateLLMProviderUI() { const provider = document.getElementById('llm-provider')?.value || 'ollama'; const apiKeyGroup = document.getElementById('llm-api-key-group'); const apiUrlLabel = document.getElementById('llm-api-url-label'); const apiUrl = document.getElementById('llm-api-url'); if (apiKeyGroup) { apiKeyGroup.style.display = (provider === 'openai') ? 'block' : 'none'; } if (apiUrlLabel && apiUrl) { switch (provider) { case 'ollama': apiUrl.placeholder = 'http://localhost:11434'; if (!apiUrl.value || apiUrl.value.includes('localhost:1234')) { apiUrl.value = 'http://localhost:11434'; } break; case 'lmstudio': apiUrl.placeholder = 'http://localhost:1234'; if (!apiUrl.value || apiUrl.value.includes('localhost:11434')) { apiUrl.value = 'http://localhost:1234'; } break; case 'openai': apiUrl.placeholder = 'https://api.openai.com'; apiUrl.value = 'https://api.openai.com'; break; } } } /** * Обновить настройки */ function updateSettings() { settings = { sound: document.getElementById('setting-sound').checked, animations: document.getElementById('setting-animations').checked, showHandStrength: document.getElementById('setting-hand-strength').checked, autofold: document.getElementById('setting-autofold').checked, serverUrl: document.getElementById('server-url').value, llmEnabled: document.getElementById('setting-llm-enabled')?.checked || false, llmProvider: document.getElementById('llm-provider')?.value || 'ollama', llmApiUrl: document.getElementById('llm-api-url')?.value || 'http://localhost:11434', llmModel: document.getElementById('llm-model')?.value || 'llama3.2', llmApiKey: document.getElementById('llm-api-key')?.value || '' }; localStorage.setItem('pokerSettings', JSON.stringify(settings)); soundEnabled = settings.sound; } /** * Тестировать подключение к LLM */ async function testLLMConnection() { const testBtn = document.getElementById('test-llm-btn'); const originalText = testBtn?.textContent; if (testBtn) { testBtn.textContent = 'Тестирую...'; testBtn.disabled = true; } // Сохраняем настройки перед тестом updateSettings(); try { if (typeof llmChat !== 'undefined') { const result = await llmChat.testConnection(); if (result.success) { showNotification('LLM подключён успешно! ' + (result.response?.substring(0, 50) || ''), 'success'); } else { showNotification('Ошибка: ' + result.error, 'error'); } } else { showNotification('LLM модуль не загружен', 'error'); } } catch (error) { showNotification('Ошибка: ' + error.message, 'error'); } if (testBtn) { testBtn.textContent = originalText; testBtn.disabled = false; } } /** * Выбрать LLM провайдера */ function selectLLMProvider(btn) { // Убираем active со всех кнопок const buttons = btn.parentElement.querySelectorAll('.btn-option'); buttons.forEach(b => b.classList.remove('active')); // Добавляем active на выбранную btn.classList.add('active'); // Устанавливаем значение в скрытый input const value = btn.dataset.value; document.getElementById('llm-provider').value = value; // Обновляем UI updateLLMProviderUI(); // Сохраняем настройки updateSettings(); } /** * Сбросить настройки */ function resetSettings() { localStorage.removeItem('pokerSettings'); localStorage.removeItem('customCardBack'); loadSettings(); applyCardBackStyle('default'); showNotification('Настройки сброшены', 'success'); } // ============================================================================= // РУБАШКА КАРТ // ============================================================================= /** * Выбрать стиль рубашки карт */ function selectCardBack(btn) { // Убираем active со всех кнопок const buttons = btn.parentElement.querySelectorAll('.btn-option'); buttons.forEach(b => b.classList.remove('active')); btn.classList.add('active'); const style = btn.dataset.style; // Показываем/скрываем поля для кастомной рубашки const customGroup = document.getElementById('custom-card-back-group'); const urlGroup = document.getElementById('card-back-url-group'); if (style === 'custom') { customGroup.style.display = 'block'; urlGroup.style.display = 'block'; } else { customGroup.style.display = 'none'; urlGroup.style.display = 'none'; } // Применяем стиль applyCardBackStyle(style); // Сохраняем в настройки settings.cardBackStyle = style; localStorage.setItem('pokerSettings', JSON.stringify(settings)); } /** * Загрузить кастомное изображение рубашки */ function loadCustomCardBack(input) { const file = input.files[0]; if (!file) return; const reader = new FileReader(); reader.onload = function(e) { const imageUrl = e.target.result; setCustomCardBack(imageUrl); }; reader.readAsDataURL(file); } /** * Установить URL рубашки */ function setCardBackUrl(url) { if (!url) return; setCustomCardBack(url); } /** * Установить кастомную рубашку */ function setCustomCardBack(imageUrl) { // Сохраняем в localStorage localStorage.setItem('customCardBack', imageUrl); // Применяем стиль applyCardBackStyle('custom', imageUrl); showNotification('Рубашка карт обновлена!', 'success'); } /** * Применить стиль рубашки ко всем картам */ function applyCardBackStyle(style, customUrl = null) { const root = document.documentElement; const preview = document.getElementById('card-back-preview'); // Убираем все стили document.querySelectorAll('.card-back').forEach(card => { card.classList.remove('style-default', 'style-red', 'style-blue', 'style-custom'); card.style.backgroundImage = ''; }); if (style === 'custom') { const url = customUrl || localStorage.getItem('customCardBack'); if (url) { root.style.setProperty('--card-back-custom-url', `url(${url})`); document.querySelectorAll('.card-back').forEach(card => { card.classList.add('style-custom'); card.style.backgroundImage = `url(${url})`; card.style.backgroundSize = 'cover'; card.style.backgroundPosition = 'center'; }); if (preview) { preview.classList.add('style-custom'); preview.style.backgroundImage = `url(${url})`; preview.style.backgroundSize = 'cover'; preview.style.backgroundPosition = 'center'; } } } else { document.querySelectorAll('.card-back').forEach(card => { card.classList.add(`style-${style}`); }); if (preview) { preview.className = 'card card-back'; preview.classList.add(`style-${style}`); preview.style.backgroundImage = ''; } } } /** * Загрузить настройки рубашки при старте */ function loadCardBackSettings() { const style = settings.cardBackStyle || 'default'; // Активируем нужную кнопку const buttons = document.querySelectorAll('.card-back-styles .btn-option'); buttons.forEach(btn => { btn.classList.remove('active'); if (btn.dataset.style === style) { btn.classList.add('active'); } }); // Показываем поля для кастомной рубашки если нужно const customGroup = document.getElementById('custom-card-back-group'); const urlGroup = document.getElementById('card-back-url-group'); if (customGroup && urlGroup) { if (style === 'custom') { customGroup.style.display = 'block'; urlGroup.style.display = 'block'; } else { customGroup.style.display = 'none'; urlGroup.style.display = 'none'; } } // Применяем стиль applyCardBackStyle(style); } /** * Переключить звук */ function toggleSound() { soundEnabled = !soundEnabled; settings.sound = soundEnabled; localStorage.setItem('pokerSettings', JSON.stringify(settings)); const btn = document.getElementById('sound-toggle'); btn.textContent = soundEnabled ? '🔊' : '🔇'; showNotification(soundEnabled ? 'Звук включён' : 'Звук выключён', 'info'); } // ============================================================================= // ЛИДЕРБОРД // ============================================================================= /** * Загрузить лидерборд */ function loadLeaderboard() { const saved = localStorage.getItem('pokerLeaderboard'); if (saved) { leaderboard = JSON.parse(saved); } else { leaderboard = []; } } /** * Обновить лидерборд */ function updateLeaderboard(result) { const playerName = localStorage.getItem('playerName') || 'Игрок'; // Находим или создаём запись let entry = leaderboard.find(e => e.name === playerName); if (!entry) { entry = { name: playerName, gamesPlayed: 0, handsWon: 0, totalWinnings: 0, biggestPot: 0 }; leaderboard.push(entry); } entry.gamesPlayed++; // Проверяем, выиграл ли текущий игрок const won = result.winners.some(w => w.id === currentPlayerId); if (won) { entry.handsWon++; const winAmount = result.winners.find(w => w.id === currentPlayerId)?.amount || 0; entry.totalWinnings += winAmount; if (result.pot > entry.biggestPot) { entry.biggestPot = result.pot; } } // Сортируем и сохраняем leaderboard.sort((a, b) => b.totalWinnings - a.totalWinnings); localStorage.setItem('pokerLeaderboard', JSON.stringify(leaderboard)); } /** * Отрисовка лидерборда */ function renderLeaderboard() { const container = document.getElementById('leaderboard-list'); if (leaderboard.length === 0) { container.innerHTML = '
Нет записей
'; return; } container.innerHTML = leaderboard.slice(0, 10).map((entry, index) => { const rankClass = index === 0 ? 'gold' : index === 1 ? 'silver' : index === 2 ? 'bronze' : ''; return `
${index + 1}
${entry.name}
Игр: ${entry.gamesPlayed} | Побед: ${entry.handsWon} | Макс. банк: ${entry.biggestPot}
${entry.totalWinnings}
`; }).join(''); } /** * Переключить вкладку лидерборда */ function switchLeaderboardTab(tab) { const parent = document.querySelector('.leaderboard-tabs'); parent.querySelectorAll('.tab').forEach(t => t.classList.remove('active')); event.target.classList.add('active'); if (tab === 'global') { document.getElementById('leaderboard-list').innerHTML = '
Глобальный лидерборд недоступен (требуется backend)
'; } else { renderLeaderboard(); } } /** * Очистить лидерборд */ function clearLeaderboard() { if (confirm('Вы уверены, что хотите очистить таблицу лидеров?')) { leaderboard = []; localStorage.removeItem('pokerLeaderboard'); renderLeaderboard(); showNotification('Таблица лидеров очищена', 'success'); } } // ============================================================================= // УТИЛИТЫ // ============================================================================= /** * Выйти из игры */ function leaveGame() { if (confirm('Вы уверены, что хотите выйти?')) { if (isMultiplayer) { leaveRoom(); } game = null; clearGameChat(); // Очищаем чат при выходе из игры showScreen('main-menu'); } } /** * Показать уведомление */ function showNotification(message, type = 'info') { const container = document.getElementById('notifications'); const notification = document.createElement('div'); notification.className = `notification ${type}`; notification.textContent = message; container.appendChild(notification); setTimeout(() => { notification.remove(); }, 3000); } // ============================================================================= // ШАРИНГ КОМНАТЫ // ============================================================================= /** * Обновить ссылку на комнату */ function updateRoomLink(roomId) { const baseUrl = window.location.origin + window.location.pathname; const roomUrl = `${baseUrl}?room=${roomId}`; const input = document.getElementById('room-link-input'); if (input) { input.value = roomUrl; } // Проверяем поддержку Web Share API const shareBtn = document.getElementById('share-generic-btn'); if (shareBtn) { if (navigator.share) { shareBtn.style.display = 'inline-flex'; } else { shareBtn.style.display = 'none'; } } } /** * Копировать ссылку на комнату */ async function copyRoomLink() { const input = document.getElementById('room-link-input'); const url = input.value; try { if (navigator.clipboard) { await navigator.clipboard.writeText(url); showNotification('Ссылка скопирована!', 'success'); } else { // Запасной вариант для старых браузеров input.select(); document.execCommand('copy'); showNotification('Ссылка скопирована!', 'success'); } } catch (error) { console.error('Ошибка копирования:', error); showNotification('Не удалось скопировать ссылку', 'error'); } } /** * Поделиться в Telegram */ function shareToTelegram() { const input = document.getElementById('room-link-input'); const url = input.value; const roomName = document.getElementById('lobby-room-name').textContent; const text = `Присоединяйся к игре в покер "${roomName}"!`; const telegramUrl = `https://t.me/share/url?url=${encodeURIComponent(url)}&text=${encodeURIComponent(text)}`; window.open(telegramUrl, '_blank'); } /** * Поделиться в WhatsApp */ function shareToWhatsApp() { const input = document.getElementById('room-link-input'); const url = input.value; const roomName = document.getElementById('lobby-room-name').textContent; const text = `Присоединяйся к игре в покер "${roomName}"!\n${url}`; const whatsappUrl = `https://wa.me/?text=${encodeURIComponent(text)}`; window.open(whatsappUrl, '_blank'); } /** * Поделиться через Web Share API */ async function shareGeneric() { const input = document.getElementById('room-link-input'); const url = input.value; const roomName = document.getElementById('lobby-room-name').textContent; if (navigator.share) { try { await navigator.share({ title: 'Texas Hold\'em Poker', text: `Присоединяйся к игре в покер "${roomName}"!`, url: url }); showNotification('Ссылка отправлена!', 'success'); } catch (error) { if (error.name !== 'AbortError') { console.error('Ошибка шаринга:', error); showNotification('Не удалось поделиться', 'error'); } } } else { showNotification('Ваш браузер не поддерживает эту функцию', 'info'); } } /** * Проверить и присоединиться к комнате из URL */ function checkRoomInUrl() { const urlParams = new URLSearchParams(window.location.search); const roomId = urlParams.get('room'); if (roomId && currentPlayerId) { // Если есть ID комнаты в URL, пытаемся присоединиться showNotification('Подключение к комнате...', 'info'); // Переходим в мультиплеер и подключаемся setTimeout(() => { showScreen('multiplayer-menu'); // Ждём подключения WebSocket if (ws && ws.readyState === WebSocket.OPEN) { joinRoom(roomId); } else { connectWebSocket().then(() => { setTimeout(() => joinRoom(roomId), 500); }); } }, 500); } }