From cfb350fa8aad2a5b21c7323887064f3ea8a887ae Mon Sep 17 00:00:00 2001 From: ur002 Date: Sun, 1 Feb 2026 19:12:06 +0300 Subject: [PATCH] feat: add player balance display and congratulatory system for strong hands - Implemented a player info row displaying the player's name and balance in the game interface. - Added animations for balance changes (increase/decrease) to enhance user experience. - Introduced a congratulatory system where bots congratulate players for strong hands based on hand strength probabilities. - Updated styles for player balance display and added relevant CSS animations. - Enhanced game logic to trigger congratulatory messages from bots after a hand concludes. - Documented the new congratulatory system and player balance display features. --- CONGRATULATIONS_SYSTEM.md | 197 +++++++++++++++++++++++++++++++ LLM_CONTEXT_EXAMPLE.md | 128 ++++++++++++++++++++ PLAYER_BALANCE_DISPLAY.md | 155 +++++++++++++++++++++++++ database.js | 32 +++++ poker.db | Bin 53248 -> 53248 bytes public/ai.js | 237 ++++++++++++++++++++++++++++++++------ public/auth.js | 3 + public/index.html | 9 +- public/main.js | 213 +++++++++++++++++++++++++++++++++- public/styles.css | 72 ++++++++++++ 10 files changed, 1006 insertions(+), 40 deletions(-) create mode 100644 CONGRATULATIONS_SYSTEM.md create mode 100644 LLM_CONTEXT_EXAMPLE.md create mode 100644 PLAYER_BALANCE_DISPLAY.md diff --git a/CONGRATULATIONS_SYSTEM.md b/CONGRATULATIONS_SYSTEM.md new file mode 100644 index 0000000..65ba8dc --- /dev/null +++ b/CONGRATULATIONS_SYSTEM.md @@ -0,0 +1,197 @@ +# Система поздравлений игрока от ботов + +## 🎯 Описание + +Боты теперь **поздравляют игрока**, когда он выигрывает с сильной покерной рукой! Вероятность поздравления зависит от редкости и силы руки. + +--- + +## 📊 Вероятности поздравлений по силе руки + +| Рука | Ранг | Вероятность | Пример | +|------|------|-------------|--------| +| **Роял-флеш** | 10 | 90% | "A♠ K♠ Q♠ J♠ 10♠ - Легенда!" | +| **Стрит-флеш** | 9 | 80% | "9♥ 8♥ 7♥ 6♥ 5♥ - Невероятно!" | +| **Каре** | 8 | 60% | "K♠ K♥ K♦ K♣ - Впечатляет!" | +| **Фулл-хаус** | 7 | 40% | "Q♠ Q♥ Q♦ 5♠ 5♥ - Красиво!" | +| **Флеш** | 6 | 25% | "A♠ J♠ 9♠ 7♠ 3♠ - Неплохо!" | +| **Стрит** | 5 | 15% | "10♦ 9♠ 8♥ 7♣ 6♦ - Хорошо!" | +| Сет и ниже | ≤4 | 0% | Не поздравляют | + +--- + +## 🤖 Стили поздравлений от разных ботов + +### 1. **Виктор "Акула"** (агрессивный) +- **Обычная рука:** "Неплохо сыграно!", "Уважаю!" +- **Сильная:** "Вау, фулл-хаус! 💪", "Монстр-рука!" +- **Очень сильная:** "Роял-флеш?! Респект! 🔥", "НЕВЕРОЯТНО!" + +### 2. **Анна "Блефер"** (загадочная) +- **Обычная:** "Интересно сыграно... 😏" +- **Сильная:** "Фулл-хаус? Впечатляет! 👏" +- **Очень сильная:** "Стрит-флеш... Я в восторге! 😮" + +### 3. **Дед Михалыч** (старая школа) +- **Обычная:** "Молодец, голубчик!" +- **Сильная:** "Фулл-хаус! Красота! 👴" +- **Очень сильная:** "Роял-флеш! За 50 лет такое редко видел! 😲" + +### 4. **Макс "ГТО"** (математик) +- **Обычная:** "Хороший EV!" +- **Сильная:** "Фулл-хаус! Rare! 📊", "Топ 0.1% рук!" +- **Очень сильная:** "Роял-флеш... 0.00154%! 🤯" + +### 5. **Катя "Удача"** (суеверная) +- **Обычная:** "Ура! Молодец! 😊" +- **Сильная:** "Фулл-хаус! Удача! 🍀✨" +- **Очень сильная:** "Роял-флеш! ЭТО МАГИЯ! 🎉🍀" + +### 6. **Борис "Молчун"** (молчаливый) +- **Обычная:** "Хм. Неплохо." +- **Сильная:** "Фулл-хаус... Ого." +- **Очень сильная:** "Роял-флеш?! ..." + +### 7. **Олег "Тильтер"** (эмоциональный) +- **Обычная:** "Ну везёт же... 😒" +- **Сильная:** "Фулл-хаус?! Ладно, красиво 😤" +- **Очень сильная:** "Роял-флеш... Я не верю! 😱" + +### 8. **Ирина "Профи"** (профессионал) +- **Обычная:** "GG WP!" +- **Сильная:** "Фулл-хаус. Респект! 💎" +- **Очень сильная:** "Роял-флеш! Incredible! 🎯" + +--- + +## 🎬 Примеры игровых ситуаций + +### Ситуация 1: Флеш (25% вероятность) +``` +Игрок выигрывает с: A♠ K♠ 9♠ 7♠ 3♠ (Флеш) +Банк: 250 фишек + +🎲 Случайный бот (25% шанс): + Макс "ГТО": "Флеш! Статистически сильно! 📊" +``` + +### Ситуация 2: Фулл-хаус (40% вероятность) +``` +Игрок выигрывает с: K♠ K♥ K♦ 8♠ 8♥ (Фулл-хаус) +Банк: 500 фишек + +🎲 Случайный бот (40% шанс): + Дед Михалыч: "Фулл-хаус! Красота! Как в старые времена! 👴" +``` + +### Ситуация 3: Каре (60% вероятность) +``` +Игрок выигрывает с: A♠ A♥ A♦ A♣ Q♠ (Каре тузов) +Банк: 800 фишек + +🎲 Случайный бот (60% шанс): + Виктор "Акула": "Каре тузов! МОНСТР-РУКА! 💪🔥" +``` + +### Ситуация 4: Стрит-флеш (80% вероятность) +``` +Игрок выигрывает с: 9♥ 8♥ 7♥ 6♥ 5♥ (Стрит-флеш) +Банк: 1200 фишек + +🎲 Случайный бот (80% шанс): + Анна "Блефер": "Стрит-флеш до девятки... Я в восторге! 😮✨" +``` + +### Ситуация 5: Роял-флеш (90% вероятность) +``` +Игрок выигрывает с: A♠ K♠ Q♠ J♠ 10♠ (Роял-флеш!) +Банк: 2000 фишек + +🎲 Случайный бот (90% шанс): + Макс "ГТО": "РОЯЛ-ФЛЕШ! Математическое чудо! Вероятность 0.00154%! 🤯🎯" +``` + +--- + +## ⚙️ Технические детали + +### Алгоритм работы: + +1. **Проверка победителя** + - Игрок победил? (не бот) + - Есть информация о руке? + +2. **Определение вероятности** + - Ранг руки ≥ 5 (стрит или выше) + - Вероятность от 15% до 90% + +3. **Выбор бота** + - Случайный бот из активных (не спасовавших) + - Учитывается его личность + +4. **Генерация поздравления** + - С помощью LLM (если включён) + - Или запасные фразы по стилю бота + +5. **Отправка в чат** + - Задержка 1-2.5 секунды + - Не конфликтует с эмоциональными реакциями + +### Интеграция с LLM: + +При включенном LLM бот получает контекст: +``` +Игрок [Имя] только что ВЫИГРАЛ с рукой "Фулл-хаус"! +Банк: 500 фишек +Это сильная рука! + +Поздравь игрока КРАТКО (максимум 5-7 слов), в своём стиле. +``` + +--- + +## 🎮 Примеры с LLM (если включён) + +**Виктор "Акула":** +- Флеш: "С флешем на терне? Красиво прочитал борд! 🔥" +- Каре: "Каре на ривере?! Вот это хладнокровие! 💪" + +**Дед Михалыч:** +- Фулл-хаус: "Эх, молодость! С таким фулл-хаусом я бы в 82-м весь турнир взял!" +- Стрит-флеш: "Святые угодники! Такое в жизни раз видел!" + +**Макс "ГТО":** +- Флеш: "Флеш на этой текстуре борда - +EV колл!" +- Каре: "Четыре аута превратились в каре. Probability magic! 📊" + +--- + +## 📝 Файлы изменены + +1. **`ai.js`** + - Добавлена `generatePlayerCongratulation()` - генерация поздравления через LLM + - Добавлена `getFallbackCongratulation()` - запасные фразы для каждого стиля + +2. **`main.js`** + - Добавлена `generatePlayerCongratulations()` - логика выбора бота и отправки + - Вызывается в `onHandEnd()` после завершения раздачи + +--- + +## 🎯 Результат + +Теперь игра стала **более живой и реалистичной**: +- Боты замечают и ценят сильные руки игрока +- Редкие комбинации вызывают восхищение +- Каждый бот поздравляет в своём уникальном стиле +- LLM делает поздравления ещё более контекстуальными + +**Пример полного взаимодействия:** + +``` +[Игрок собрал каре королей и выиграл банк 800 фишек] + +Макс "ГТО": "Каре королей! Топ 0.024% рук! 📊" (через 1.2 сек) +``` + +🎰🃏 **Играйте и получайте заслуженные поздравления!** diff --git a/LLM_CONTEXT_EXAMPLE.md b/LLM_CONTEXT_EXAMPLE.md new file mode 100644 index 0000000..91c2166 --- /dev/null +++ b/LLM_CONTEXT_EXAMPLE.md @@ -0,0 +1,128 @@ +# Пример контекста для LLM + +## Что теперь видит бот при ответе на сообщение игрока + +### Пример 1: Флоп в игре + +**Игрок пишет:** "Виктор, что думаешь?" + +**LLM получает следующий контекст:** + +``` +📍 Фаза: флоп (3 карты на столе) +🃏 Карты на столе: K♥ 9♦ 2♣ +💰 Банк: 150 фишек +💵 Текущая ставка: 30 фишек + +🤖 Мои данные: + - Имя: Виктор "Акула" + - Карты: A♠ K♠ + - Фишки: 470 + - Моя ставка: 30 (всего в раздаче: 45) + - Позиция: дилер + +👥 Другие игроки: + - Игрок: 480 фишек, ставка 30 🎲 [последнее: call] + - Анна "Блефер": 350 фишек [спасовал ❌] + - Дед Михалыч: 200 фишек [последнее: fold] + +✅ Активные: Виктор "Акула", Игрок +❌ Спасовали: Анна "Блефер", Дед Михалыч + +💬 Игрок написал: "Виктор, что думаешь?" +``` + +**Возможный ответ бота:** +"С топ-парой на таком сухом флопе? Думаю, что банк мой. 😎" + +--- + +### Пример 2: Терн, напряжённая ситуация + +**Игрок пишет:** "Макс, всё плохо?" + +**LLM получает:** + +``` +📍 Фаза: терн (4 карты на столе) +🃏 Карты на столе: 7♥ 8♦ 9♣ J♠ +💰 Банк: 450 фишек +💵 Текущая ставка: 100 фишек + +🤖 Мои данные: + - Имя: Макс "ГТО" + - Карты: A♣ K♣ + - Фишки: 280 + - Моя ставка: 50 (всего в раздаче: 165) + - Позиция: обычная позиция + +👥 Другие игроки: + - Игрок: 520 фишек, ставка 100 [последнее: raise] + - Борис "Молчун": 150 фишек [спасовал ❌] + +✅ Активные: Макс "ГТО", Игрок +❌ Спасовали: Борис "Молчун" + +💬 Игрок написал: "Макс, всё плохо?" +``` + +**Возможный ответ бота:** +"На этом борде с прямым дро? Просчитываю pot odds... 🤔" + +--- + +### Пример 3: All-in ситуация + +**Игрок пишет:** "Катя, рискнёшь?" + +**LLM получает:** + +``` +📍 Фаза: ривер (5 карт на столе) +🃏 Карты на столе: 3♥ 3♦ 8♣ Q♠ A♥ +💰 Банк: 800 фишек +💵 Текущая ставка: 200 фишек + +🤖 Мои данные: + - Имя: Катя "Удача" + - Карты: 3♠ 8♠ + - Фишки: 195 + - Моя ставка: 0 (всего в раздаче: 105) + - Позиция: малый блайнд + +👥 Другие игроки: + - Игрок: 95 фишек, ставка 200 [ва-банк 💥] + +✅ Активные: Катя "Удача", Игрок +❌ Спасовали: + +💬 Игрок написал: "Катя, рискнёшь?" +``` + +**Возможный ответ бота:** +"С фулл-хаусом? Загадала желание! 🍀✨" + +--- + +## Преимущества нового контекста + +1. **Видит свои карты** - может блефовать или говорить честно +2. **Видит карты на столе** - может комментировать текстуру борда +3. **Видит действия других игроков** - может реагировать на чужие ставки +4. **Знает размер банка и ставок** - может говорить про pot odds +5. **Знает фазу игры** - может адаптировать ответ под префлоп/флоп/терн/ривер +6. **Видит кто спасовал** - может подшучивать или сочувствовать + +## Примеры улучшенных ответов + +### До изменений: +- Игрок: "Что думаешь?" +- Бот: "Удачи! Интересная игра." + +### После изменений: +- Игрок: "Что думаешь?" +- Бот: "С тремя червами на столе и у меня флеш дро? Думаю поплыть дальше. 😏" + +--- + +Теперь боты отвечают КОНТЕКСТУАЛЬНО, используя реальную информацию о раздаче! diff --git a/PLAYER_BALANCE_DISPLAY.md b/PLAYER_BALANCE_DISPLAY.md new file mode 100644 index 0000000..fd1af97 --- /dev/null +++ b/PLAYER_BALANCE_DISPLAY.md @@ -0,0 +1,155 @@ +# Отображение баланса игрока + +## ✅ Исправлено + +Теперь игрок **видит свой баланс фишек** прямо под картами! + +--- + +## 🎯 Что добавлено + +### 1. **Визуальное отображение баланса** + +На экране игры появилась **панель информации о игроке**: + +``` +┌────────────────────────────┐ +│ Игрок 💰 1000 │ ← Имя и баланс +│ [K♠] [Q♥] │ ← Карты +│ Две старших карты │ ← Сила руки +└────────────────────────────┘ +``` + +**Элементы:** +- **Имя игрока** - слева +- **💰 Баланс** - справа (зелёная подсветка) +- **Карты** - по центру +- **Сила руки** - снизу + +--- + +## 🎨 Визуальные фичи + +### **Анимация изменения баланса:** + +1. **При увеличении фишек** (выигрыш): + - Зелёная вспышка + - Увеличение на 5% + - 0.5 секунды + +2. **При уменьшении фишек** (ставка/проигрыш): + - Красная вспышка + - Уменьшение на 5% + - 0.5 секунды + +**Пример:** +``` +Выигрыш +500: 💰 500 → 💰 1000 (зелёная вспышка ✨) +Ставка -100: 💰 1000 → 💰 900 (красная вспышка 🔴) +``` + +--- + +## 💻 Технические детали + +### **Файлы изменены:** + +#### 1. `index.html` +Добавлена строка информации о игроке: +```html +
+
Игрок
+
+ 💰 + 1000 +
+
+``` + +#### 2. `styles.css` +Добавлены стили: +- `.player-info-row` - контейнер для имени и баланса +- `.player-name-display` - стиль имени +- `.player-balance-display` - значок и сумма +- `@keyframes balanceIncrease` - анимация увеличения +- `@keyframes balanceDecrease` - анимация уменьшения + +#### 3. `main.js` +Добавлена функция `updatePlayerBalance()`: +```javascript +function updatePlayerBalance(player) { + // Обновляет имя и баланс + // Добавляет анимацию при изменении + // Отслеживает предыдущее значение +} +``` + +Интеграция в: +- `updateGameUI()` - одиночная игра +- `updateGameUIFromServer()` - мультиплеер + +--- + +## 🎮 Примеры в действии + +### **Префлоп:** +``` +┌────────────────────────────┐ +│ Игрок 💰 1000 │ +│ [A♠] [K♠] │ +│ Старшие карты │ +└────────────────────────────┘ + +Ставка Big Blind 10 → + +┌────────────────────────────┐ +│ Игрок 💰 990 🔴 │ ← Красная вспышка +│ [A♠] [K♠] │ +│ Старшие карты │ +└────────────────────────────┘ +``` + +### **Выигрыш банка:** +``` +Банк: 500 фишек +Победа с флешем! + +┌────────────────────────────┐ +│ Игрок 💰 1490 ✨ │ ← Зелёная вспышка +│ [A♠] [K♠] │ +│ Флеш, туз старший │ +└────────────────────────────┘ +``` + +### **All-in:** +``` +┌────────────────────────────┐ +│ Игрок 💰 0 🔴 │ ← Баланс 0 после all-in +│ [Q♥] [Q♦] │ +│ Пара дам │ +└────────────────────────────┘ +``` + +--- + +## 📊 Отличия от ботов + +| Элемент | Бот (на столе) | Игрок (снизу) | +|---------|----------------|---------------| +| Имя | Маленькое, внутри бокса | Крупное, отдельная строка | +| Баланс | Под именем, серый текст | Отдельный блок, зелёный, с иконкой | +| Карты | Мини (40x56px) | Большие (70x98px) | +| Анимация | Нет | Есть (при изменении) | +| Позиция | По кругу стола | Внизу по центру | + +--- + +## ✅ Результат + +Теперь игрок **всегда видит**: +- ✅ Своё имя +- ✅ Актуальный баланс фишек +- ✅ Визуальную обратную связь при выигрыше/проигрыше +- ✅ Красивую анимацию изменения баланса + +🎰💰 **Следите за своим стеком!** diff --git a/database.js b/database.js index 925259c..e97c905 100644 --- a/database.js +++ b/database.js @@ -150,6 +150,38 @@ async function createDefaultAdmin() { saveDatabase(); console.log('👑 Создан администратор по умолчанию: admin / admin123'); } + + // Инициализируем дефолтные админские настройки + initDefaultAdminSettings(); +} + +/** + * Инициализировать дефолтные админские настройки + */ +function initDefaultAdminSettings() { + const settingsCheck = db.exec("SELECT COUNT(*) as count FROM admin_settings"); + const count = settingsCheck.length > 0 ? settingsCheck[0].values[0][0] : 0; + + if (count === 0) { + const defaults = { + llmEnabled: 'false', + llmProvider: 'ollama', + llmApiUrl: 'http://localhost:11434', + llmModel: 'llama3.2', + llmApiKey: '', + serverUrl: 'ws://localhost:3000' + }; + + Object.entries(defaults).forEach(([key, value]) => { + db.run(` + INSERT INTO admin_settings (key, value, updated_at) + VALUES (?, ?, datetime('now')) + `, [key, value]); + }); + + saveDatabase(); + console.log('⚙️ Инициализированы дефолтные админские настройки'); + } } /** diff --git a/poker.db b/poker.db index 49c6cb4f20014c3367a434bc70c5a56e5bf00570..895f32019fa5cfce2f65735cef4951744c2e346c 100644 GIT binary patch delta 691 zcmZozz}&Ead4e>f%0wAwMwN{TOZYjM_--)pC-L3bEGTe>ufCC;i91xhkyX@~frEp? zI628YE!EOk*Tl>sQP;%K(n!}bDb-Xr)y&+;*wnd*vYdv zq%zIKDK#X`&pE@>%rV`pN-w3v#MGz2Kfu($uZTlw6r+2 z$jHFROxM6jSJJ>x!O+agz|6|XvQe55>=bchW1v$eKg?5^%-_#Cd8T~4h@pX%iLsTT zDHj6+0~7yx2LAQ@K=&&w=GWC=4rL_9)lBNlp`0KyCmFDCF!8@-;Q!A5cC(&U0{~e_u|xm> delta 135 zcmZozz}&Ead4e>f^h6nFM(K?SOZeFs`I8v 0) { - context += `Общие карты: ${ctx.communityCards.join(', ')}\n`; + context += `🃏 Карты на столе: ${ctx.communityCards.join(' ')}\n`; + } else { + context += `🃏 Карты на столе: пока нет\n`; + } + + // Банк и ставки + if (ctx.pot !== undefined) { + context += `💰 Банк: ${ctx.pot} фишек\n`; + } + if (ctx.currentBet !== undefined && ctx.currentBet > 0) { + context += `💵 Текущая ставка: ${ctx.currentBet} фишек\n`; + } + + // Мои данные (данные бота) + if (ctx.myName) { + context += `\n🤖 Мои данные:\n`; + context += ` - Имя: ${ctx.myName}\n`; + if (ctx.myCards && ctx.myCards.length > 0) { + context += ` - Карты: ${ctx.myCards.join(' ')}\n`; + } + context += ` - Фишки: ${ctx.myChips}\n`; + if (ctx.myBet > 0) { + context += ` - Моя ставка: ${ctx.myBet} (всего в раздаче: ${ctx.myTotalBet})\n`; + } + if (ctx.myPosition) { + context += ` - Позиция: ${ctx.myPosition}\n`; + } + } + + // Другие игроки + if (ctx.players && ctx.players.length > 0) { + context += `\n👥 Другие игроки:\n`; + ctx.players.forEach(p => { + if (p.isMe) return; // Пропускаем себя + + let playerStatus = ''; + if (p.folded) { + playerStatus = ' [спасовал ❌]'; + } else if (p.allIn) { + playerStatus = ' [ва-банк 💥]'; + } else if (p.lastAction) { + const actionRu = { + 'fold': 'пас', + 'check': 'чек', + 'call': 'колл', + 'bet': 'бет', + 'raise': 'рейз', + 'allin': 'ва-банк' + }[p.lastAction] || p.lastAction; + playerStatus = ` [последнее: ${actionRu}]`; + } + + context += ` - ${p.name}: ${p.chips} фишек`; + if (p.bet > 0) { + context += `, ставка ${p.bet}`; + } + if (p.isDealer) { + context += ` 🎲`; + } + context += playerStatus + `\n`; + }); + } + + // Активные игроки + if (ctx.activePlayers && ctx.activePlayers.length > 0) { + context += `\n✅ Активные: ${ctx.activePlayers.join(', ')}\n`; + } + if (ctx.foldedPlayers && ctx.foldedPlayers.length > 0) { + context += `❌ Спасовали: ${ctx.foldedPlayers.join(', ')}\n`; + } + + // Последнее сообщение игрока + if (ctx.lastPlayerAction) { + context += `\n💬 Игрок написал: "${ctx.lastPlayerAction}"\n`; } return context || 'Идёт игра.'; @@ -1282,6 +1351,102 @@ ${isWin ? 'Покажи радость, удовлетворение или са */ clearAllHistory() { this.messageHistory.clear(); + }, + + /** + * Сгенерировать поздравление игрока с сильной рукой + */ + async generatePlayerCongratulation(bot, botPersonality, playerName, handRank, handName, potSize, gameContext = {}) { + const settings = this.getSettings(); + + // Определяем, насколько сильная рука (для редкости поздравлений) + const isStrongHand = handRank >= 7; // Фулл-хаус и выше + const isVeryStrongHand = handRank >= 9; // Стрит-флеш и роял-флеш + + // Формируем промпт для LLM + const congratsPrompt = `${botPersonality.systemPrompt} + +СИТУАЦИЯ: Игрок ${playerName} только что ВЫИГРАЛ раздачу с рукой "${handName}"! Банк: ${potSize} фишек. + +${isVeryStrongHand ? 'Это ОЧЕНЬ редкая и сильная рука!' : (isStrongHand ? 'Это сильная рука!' : 'Это хорошая рука!')} + +Поздравь игрока КРАТКО (максимум 5-7 слов), в своём стиле. +${isVeryStrongHand ? 'Покажи восхищение и уважение!' : 'Будь доброжелательным.'}`; + + // Если LLM включён, используем его + if (settings.llmEnabled && typeof this.sendToLLM === 'function') { + try { + const messages = [{ role: 'user', content: 'Поздрави игрока' }]; + const response = await this.sendToLLM(congratsPrompt, messages, settings); + if (response) return response; + } catch (error) { + console.error('Ошибка генерации поздравления:', error); + } + } + + // Запасные поздравления + return this.getFallbackCongratulation(botPersonality.style, handRank, handName); + }, + + /** + * Запасные поздравления + */ + getFallbackCongratulation(style, handRank, handName) { + const congratulations = { + aggressive: { + normal: ['Неплохо сыграно!', 'Уважаю!', 'Сильная рука!', 'Молодец!'], + strong: ['Вау, фулл-хаус! 💪', 'Монстр-рука!', 'Красавчик!', 'Вот это да!'], + veryStrong: [`${handName}?! Респект! 🔥`, 'НЕВЕРОЯТНО!', 'Легенда!', 'Вот это покер!'] + }, + tricky: { + normal: ['Интересно сыграно... 😏', 'Неплохо, неплохо', 'Красиво'], + strong: ['Фулл-хаус? Впечатляет! 👏', 'Загадочная игра...', 'Сильно!'], + veryStrong: [`${handName}... Я в восторге! 😮`, 'Невероятная рука!', 'Потрясающе!'] + }, + oldschool: { + normal: ['Молодец, голубчик!', 'Хорошо, хорошо!', 'Эх, красота!'], + strong: ['Фулл-хаус! Красота! 👴', 'Вот это рука!', 'Как в старые времена!'], + veryStrong: [`${handName}! За 50 лет такое редко видел! 😲`, 'Ух ты!', 'Легенда!'] + }, + mathematical: { + normal: ['Хороший EV!', 'Статистически сильно', '+EV рука'], + strong: ['Фулл-хаус! Rare! 📊', 'Топ 0.1% рук!', 'Вероятность низкая!'], + veryStrong: [`${handName}... 0.00154%! 🤯`, 'Математическое чудо!', 'Редчайший случай!'] + }, + lucky: { + normal: ['Ура! Молодец! 😊', 'Везунчик! 🍀', 'Здорово!'], + strong: ['Фулл-хаус! Удача! 🍀✨', 'Карты тебя любят!', 'Талисман работает! 💫'], + veryStrong: [`${handName}! ЭТО МАГИЯ! 🎉🍀`, 'Невероятная удача!', 'Чудо! ✨💚'] + }, + silent: { + normal: ['Хм. Неплохо.', 'Да.', '...👍'], + strong: ['Фулл-хаус... Ого.', 'Впечатляет.', '...!'], + veryStrong: [`${handName}?! ...`, 'Вау.', '😮'] + }, + tilted: { + normal: ['Ну везёт же... 😒', 'Тебе повезло!', 'Конечно...'], + strong: ['Фулл-хаус?! Ладно, красиво 😤', 'Повезло сильно!', 'Не может быть!'], + veryStrong: [`${handName}... Я не верю! 😱`, 'КАК?!', 'Это читерство! (шучу)'] + }, + professional: { + normal: ['GG WP!', 'Nice hand!', 'Хорошо сыграно'], + strong: ['Фулл-хаус. Респект! 💎', 'Strong hand, WP!', 'Impressive!'], + veryStrong: [`${handName}! Incredible! 🎯`, 'Amazing hand!', 'Legendary!'] + } + }; + + const styleCongrats = congratulations[style] || congratulations.professional; + + let pool; + if (handRank >= 9) { + pool = styleCongrats.veryStrong; + } else if (handRank >= 7) { + pool = styleCongrats.strong; + } else { + pool = styleCongrats.normal; + } + + return pool[Math.floor(Math.random() * pool.length)]; } }; diff --git a/public/auth.js b/public/auth.js index 6a11a81..7843631 100644 --- a/public/auth.js +++ b/public/auth.js @@ -197,6 +197,7 @@ function updateUserDisplay() { const nameEl = document.getElementById('user-display-name'); const badgeEl = document.getElementById('user-role-badge'); const adminBtn = document.getElementById('admin-btn'); + const serverUrlGroup = document.getElementById('server-url-group'); if (currentUser) { nameEl.textContent = currentUser.username; @@ -205,9 +206,11 @@ function updateUserDisplay() { badgeEl.style.display = 'inline'; badgeEl.textContent = 'ADMIN'; if (adminBtn) adminBtn.style.display = 'block'; + if (serverUrlGroup) serverUrlGroup.style.display = 'block'; } else { badgeEl.style.display = 'none'; if (adminBtn) adminBtn.style.display = 'none'; + if (serverUrlGroup) serverUrlGroup.style.display = 'none'; } // Обновляем поля имени в формах diff --git a/public/index.html b/public/index.html index 1c32e0a..5db2e67 100644 --- a/public/index.html +++ b/public/index.html @@ -275,6 +275,13 @@
+
+
Игрок
+
+ 💰 + 1000 +
+
@@ -443,7 +450,7 @@
-
+ diff --git a/public/main.js b/public/main.js index d0a3c93..79a2989 100644 --- a/public/main.js +++ b/public/main.js @@ -138,6 +138,13 @@ function showScreen(screenId) { if (screenId === 'leaderboard-screen') { renderLeaderboard(); } + + // Загрузка админских настроек + if (screenId === 'admin-screen') { + if (typeof loadAdminSettings === 'function') { + loadAdminSettings(); + } + } } /** @@ -559,6 +566,7 @@ function updateGameUI() { const player = game.players.find(p => p.id === currentPlayerId); if (player) { renderPlayerHand(player.hand, game.communityCards); + updatePlayerBalance(player); } updateActionPanel(player, game); @@ -598,6 +606,7 @@ function updateGameUIFromServer(room) { const myPlayer = players.find(p => p.id === currentPlayerId); if (myPlayer) { renderPlayerHand(myPlayer.hand, communityCards); + updatePlayerBalance(myPlayer); updateActionPanelFromServer(myPlayer, room); } @@ -780,6 +789,24 @@ function applyCardBackToNewCards() { */ 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) { @@ -805,6 +832,42 @@ function renderPlayerHand(hand, communityCards) { } } +/** + * Обновить баланс игрока + */ +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(); + } +} + /** * Обновить панель действий */ @@ -1034,6 +1097,9 @@ function onHandEnd(result) { // НОВОЕ: Генерируем эмоциональные реакции ботов if (!isMultiplayer && game) { generateBotEmotions(result); + + // НОВОЕ: Поздравления игрока от ботов при сильной руке + generatePlayerCongratulations(result); } // Показываем кнопку новой раздачи @@ -1107,6 +1173,107 @@ async function generateBotEmotions(result) { }, 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); +} + /** * Закрыть модальное окно результата */ @@ -1221,13 +1388,53 @@ function sendGameChat() { // Если обращение не найдено, выбираем случайного бота const bot = targetBot || bots[Math.floor(Math.random() * bots.length)]; - // Формируем контекст игры для LLM + // Формируем расширенный контекст игры для 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, - lastAction: message, - communityCards: game.communityCards?.map(c => `${c.rank}${c.suit}`) || [], + 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 // Указываем, что игрок обратился по имени }; diff --git a/public/styles.css b/public/styles.css index 08f318f..37c2ea0 100644 --- a/public/styles.css +++ b/public/styles.css @@ -1077,6 +1077,78 @@ body::before { gap: 8px; } +.player-info-row { + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + padding: 8px 16px; + gap: 16px; +} + +.player-name-display { + font-size: 14px; + font-weight: 600; + color: var(--text-primary); +} + +.player-balance-display { + display: flex; + align-items: center; + gap: 6px; + padding: 4px 12px; + background: rgba(16, 185, 129, 0.1); + border: 1px solid rgba(16, 185, 129, 0.3); + border-radius: 12px; + transition: all var(--transition-fast); +} + +.player-balance-display.balance-increase { + animation: balanceIncrease 0.5s ease; +} + +.player-balance-display.balance-decrease { + animation: balanceDecrease 0.5s ease; +} + +@keyframes balanceIncrease { + 0%, 100% { + background: rgba(16, 185, 129, 0.1); + border-color: rgba(16, 185, 129, 0.3); + transform: scale(1); + } + 50% { + background: rgba(16, 185, 129, 0.3); + border-color: rgba(16, 185, 129, 0.6); + transform: scale(1.05); + } +} + +@keyframes balanceDecrease { + 0%, 100% { + background: rgba(16, 185, 129, 0.1); + border-color: rgba(16, 185, 129, 0.3); + transform: scale(1); + } + 50% { + background: rgba(239, 68, 68, 0.2); + border-color: rgba(239, 68, 68, 0.4); + transform: scale(0.95); + } +} + +.balance-icon { + font-size: 16px; +} + +.balance-amount { + font-size: 16px; + font-weight: 700; + color: var(--success); + min-width: 50px; + text-align: right; +} + .player-cards { display: flex; gap: 8px;