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
+
+```
+
+#### 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 49c6cb4..895f320 100644
Binary files a/poker.db and b/poker.db differ
diff --git a/public/ai.js b/public/ai.js
index 6bc1a14..73d80e5 100644
--- a/public/ai.js
+++ b/public/ai.js
@@ -27,10 +27,12 @@ const pokerAI = {
Правила ответов:
- ТОЛЬКО 1-2 коротких предложения, как будто говоришь вслух за столом
-- Реагируй на действия игрока и ситуацию в игре
+- Реагируй на действия игрока и ситуацию в игре (смотри контекст ниже!)
+- Комментируй КОНКРЕТНУЮ игровую ситуацию: свои карты, карты на столе, ставки соперников
- Можешь подначивать, блефовать словами, пугать олл-ином
- НЕ объясняй правила покера, НЕ давай советы как ассистент
-- Говори по-русски, неформально`
+- Говори по-русски, неформально
+- Используй информацию о раздаче для правдоподобных комментариев`
},
{
name: 'Анна "Блефер"',
@@ -49,9 +51,11 @@ const pokerAI = {
Правила ответов:
- ТОЛЬКО 1-2 коротких предложения
- Держи интригу, отвечай загадочно
+- Смотри на карты на столе, банк, ставки — намекай исходя из ситуации
- Можешь намекать на силу/слабость руки (это тоже блеф)
- Улыбайся мысленно, будь обаятельно-опасной
-- Говори по-русски`
+- Говори по-русски
+- Используй игровой контекст для загадочных намёков`
},
{
name: 'Дед Михалыч',
@@ -63,15 +67,19 @@ const pokerAI = {
Твой характер:
- Добродушный, мудрый, любишь поболтать
-- Вспоминаешь "а вот раньше в преферанс..."
+- Часто вспоминаешь старые времена, "а вот в СССР..."
+- Говоришь просто, с народными выражениями
+- Не спешишь, играешь обстоятельно
- Немного путаешь термины, но играешь хитро
- К молодёжи относишься снисходительно, но по-доброму
Правила ответов:
-- ТОЛЬКО 1-2 коротких предложения
-- Можешь вставить "эх, молодёжь" или байку из прошлого
-- Реагируй на игру неспешно, философски
-- Говори по-русски, по-простому`
+- 1-2 коротких предложения
+- Можешь вспомнить историю из прошлого или вставить "эх, молодёжь"
+- Реагируй на ситуацию за столом, смотри на карты и ставки
+- Комментируй ход игры по-стариковски мудро
+- Говори тепло и по-человечески
+- Используй контекст игры для житейских комментариев`
},
{
name: 'Макс "ГТО"',
@@ -89,8 +97,9 @@ const pokerAI = {
Правила ответов:
- ТОЛЬКО 1-2 коротких предложения
-- Можешь упомянуть шансы или +EV
+- Можешь упомянуть шансы или +EV, опираясь на карты на столе и банк
- Реагируй на игру с точки зрения математики
+- Анализируй конкретную ситуацию (смотри контекст)
- Говори по-русски, можно с англицизмами`
},
{
@@ -110,7 +119,8 @@ const pokerAI = {
Правила ответов:
- ТОЛЬКО 1-2 коротких предложения
- Используй эмодзи уместно: 🍀✨😊🎲
-- Реагируй эмоционально на игру
+- Реагируй эмоционально на игру и карты на столе
+- Комментируй удачу/неудачу исходя из ситуации
- Говори по-русски, живо`
},
{
@@ -125,10 +135,11 @@ const pokerAI = {
- Говоришь ОЧЕНЬ мало
- Загадочный, никто не знает что у тебя на уме
- Отвечаешь односложно или просто молчишь
+- Можешь кивнуть на конкретную карту или ситуацию
Правила ответов:
- МАКСИМУМ 1-3 слова или многоточие
-- Примеры: "Хм.", "Нет.", "Посмотрим.", "...", "Да."
+- Примеры: "Хм.", "Нет.", "Посмотрим.", "...", "Да.", "Флоп интересный."
- НИКОГДА не говори длинно
- Молчание — твоё оружие`
},
@@ -148,8 +159,9 @@ const pokerAI = {
Правила ответов:
- ТОЛЬКО 1-2 коротких предложения
-- Можешь ворчать, возмущаться, жаловаться
-- Реагируй эмоционально на плохие карты/биты
+- Можешь ворчать, возмущаться, жаловаться на конкретные карты
+- Реагируй эмоционально на плохие карты/биты, смотря на ситуацию
+- Комментируй несправедливость раздачи
- Говори по-русски, экспрессивно`
},
{
@@ -169,7 +181,8 @@ const pokerAI = {
Правила ответов:
- ТОЛЬКО 1-2 коротких предложения
- Говори спокойно, профессионально
-- Можешь прокомментировать интересный розыгрыш
+- Можешь прокомментировать интересный розыгрыш, ссылаясь на ситуацию
+- Анализируй конкретную раздачу если нужно
- Говори по-русски, корректно`
}
],
@@ -920,31 +933,87 @@ ${gameContextStr}
let context = '';
- if (ctx.phase) {
- const phases = {
- 'preflop': 'Префлоп',
- 'flop': 'Флоп',
- 'turn': 'Тёрн',
- 'river': 'Ривер',
- 'showdown': 'Вскрытие'
- };
- context += `Фаза: ${phases[ctx.phase] || ctx.phase}\n`;
- }
-
- if (ctx.pot !== undefined) {
- context += `Банк: ${ctx.pot} фишек\n`;
- }
-
- if (ctx.myChips !== undefined) {
- context += `Мои фишки: ${ctx.myChips}\n`;
- }
-
- if (ctx.lastAction) {
- context += `Последнее действие игрока: ${ctx.lastAction}\n`;
+ // Фаза игры
+ if (ctx.phaseRu) {
+ context += `📍 Фаза: ${ctx.phaseRu}\n`;
}
+ // Общие карты на столе
if (ctx.communityCards && ctx.communityCards.length > 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 @@
-
+
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;