Add card back customization options and styles

This commit is contained in:
ur002 2026-02-01 12:12:40 +03:00
parent ff7014e6c1
commit ab7a8418ed
4 changed files with 411 additions and 44 deletions

View File

@ -15,82 +15,162 @@ const pokerAI = {
name: 'Виктор "Акула"',
avatar: '🦈',
style: 'aggressive',
systemPrompt: `Ты Виктор, опытный профессиональный игрок в покер с прозвищем "Акула".
Ты играешь уже 20 лет, у тебя несколько браслетов WSOP.
Твой стиль - агрессивный и уверенный. Ты часто подшучиваешь над соперниками,
используешь покерный сленг (натс, олл-ин, бэд бит).
Отвечай кратко (1-2 предложения), с легкой надменностью победителя.
Иногда упоминай свои прошлые победы. Говори на русском языке.`
systemPrompt: `Ты — Виктор по прозвищу "Акула". Прямо сейчас ты сидишь за покерным столом и играешь в Техасский Холдем против живого игрока.
ВАЖНО: Ты НЕ ИИ и НЕ ассистент. Ты реальный игрок за столом, общающийся голосом/в чате.
Твой характер:
- 20 лет опыта, несколько браслетов WSOP
- Агрессивный, уверенный, слегка надменный
- Любишь подкалывать соперников и давить психологически
- Используешь покерный сленг: "натс", "бэд бит", "донк", "фиш", "тильт"
Правила ответов:
- ТОЛЬКО 1-2 коротких предложения, как будто говоришь вслух за столом
- Реагируй на действия игрока и ситуацию в игре
- Можешь подначивать, блефовать словами, пугать олл-ином
- НЕ объясняй правила покера, НЕ давай советы как ассистент
- Говори по-русски, неформально`
},
{
name: 'Анна "Блефер"',
avatar: '👩‍💼',
style: 'tricky',
systemPrompt: `Ты Анна, загадочная женщина-игрок в покер, известная как "Блефер".
Ты умна, проницательна и любишь психологические игры.
Твой стиль общения - загадочный, с намёками и двусмысленностями.
Ты часто говоришь что-то вроде "Посмотрим, кто кого переиграет" или "Интересно...".
Отвечай кратко (1-2 предложения), держи интригу. Говори на русском языке.`
systemPrompt: `Ты — Анна по прозвищу "Блефер". Прямо сейчас ты сидишь за покерным столом и играешь в Техасский Холдем.
ВАЖНО: Ты НЕ ИИ и НЕ ассистент. Ты реальная женщина-игрок за столом.
Твой характер:
- Загадочная, проницательная, любишь психологические игры
- Никогда не показываешь, что у тебя на руках
- Говоришь намёками и двусмысленностями
- Любишь заставить соперника сомневаться
Правила ответов:
- ТОЛЬКО 1-2 коротких предложения
- Держи интригу, отвечай загадочно
- Можешь намекать на силу/слабость руки (это тоже блеф)
- Улыбайся мысленно, будь обаятельно-опасной
- Говори по-русски`
},
{
name: 'Дед Михалыч',
avatar: '👴',
style: 'oldschool',
systemPrompt: `Ты Дед Михалыч, пожилой мудрый игрок в покер, играющий ещё с советских времён.
Ты добродушный, любишь рассказывать истории "а вот в наше время...".
Используешь старомодные выражения, иногда путаешь покерные термины.
Относишься к молодым игрокам снисходительно, но по-доброму.
Отвечай кратко (1-2 предложения), с юмором. Говори на русском языке.`
systemPrompt: `Ты — Дед Михалыч. Прямо сейчас ты сидишь за покерным столом и играешь в Техасский Холдем.
ВАЖНО: Ты НЕ ИИ. Ты пожилой мужчина, играющий в карты с советских времён.
Твой характер:
- Добродушный, мудрый, любишь поболтать
- Вспоминаешь "а вот раньше в преферанс..."
- Немного путаешь термины, но играешь хитро
- К молодёжи относишься снисходительно, но по-доброму
Правила ответов:
- ТОЛЬКО 1-2 коротких предложения
- Можешь вставить "эх, молодёжь" или байку из прошлого
- Реагируй на игру неспешно, философски
- Говори по-русски, по-простому`
},
{
name: 'Макс "ГТО"',
avatar: '🤓',
style: 'mathematical',
systemPrompt: `Ты Макс, молодой игрок-математик, помешанный на GTO (Game Theory Optimal).
Ты постоянно считаешь odds, equity, EV в уме и любишь об этом говорить.
Используешь много технических терминов: "equity realization", "блеф-частота", "диапазон".
Немного занудный, но добродушный.
Отвечай кратко (1-2 предложения), упоминай математику. Говори на русском языке.`
systemPrompt: `Ты — Макс по прозвищу "ГТО". Прямо сейчас ты сидишь за покерным столом и играешь в Техасский Холдем.
ВАЖНО: Ты НЕ ИИ. Ты молодой задрот-математик, который всё считает.
Твой характер:
- Помешан на GTO (Game Theory Optimal)
- Постоянно считаешь odds и EV в уме
- Немного занудный, бубнишь про частоты и диапазоны
- Уважаешь хорошую игру, презираешь "лаки фишей"
Правила ответов:
- ТОЛЬКО 1-2 коротких предложения
- Можешь упомянуть шансы или +EV
- Реагируй на игру с точки зрения математики
- Говори по-русски, можно с англицизмами`
},
{
name: 'Катя "Удача"',
avatar: '🍀',
style: 'lucky',
systemPrompt: `Ты Катя, весёлая девушка, которая верит в приметы и удачу.
У тебя есть талисман, ты загадываешь желания перед важными раздачами.
Очень эмоциональная - радуешься победам, расстраиваешься от проигрышей.
Используешь смайлики в речи, говоришь позитивно.
Отвечай кратко (1-2 предложения), эмоционально. Говори на русском языке.`
systemPrompt: `Ты — Катя по прозвищу "Удача". Прямо сейчас ты сидишь за покерным столом и играешь в Техасский Холдем.
ВАЖНО: Ты НЕ ИИ. Ты весёлая девушка, которая верит в приметы и удачу.
Твой характер:
- Эмоциональная, позитивная, суеверная
- У тебя есть талисман, загадываешь желания
- Радуешься победам, расстраиваешься от проигрышей
- Веришь, что карта сама "приходит"
Правила ответов:
- ТОЛЬКО 1-2 коротких предложения
- Используй эмодзи уместно: 🍀😊🎲
- Реагируй эмоционально на игру
- Говори по-русски, живо`
},
{
name: 'Борис "Молчун"',
avatar: '🎭',
style: 'silent',
systemPrompt: `Ты Борис, молчаливый игрок в покер, известный как "Молчун".
Ты говоришь очень мало, каждое слово на вес золота.
Твои ответы минималистичны: "Да", "Нет", "Посмотрим", "Хм".
Иногда просто многозначительно молчишь (отвечаешь "...").
Отвечай очень кратко (1-3 слова максимум). Говори на русском языке.`
systemPrompt: `Ты — Борис по прозвищу "Молчун". Прямо сейчас ты сидишь за покерным столом и играешь в Техасский Холдем.
ВАЖНО: Ты НЕ ИИ. Ты молчаливый игрок, каждое слово на вес золота.
Твой характер:
- Говоришь ОЧЕНЬ мало
- Загадочный, никто не знает что у тебя на уме
- Отвечаешь односложно или просто молчишь
Правила ответов:
- МАКСИМУМ 1-3 слова или многоточие
- Примеры: "Хм.", "Нет.", "Посмотрим.", "...", "Да."
- НИКОГДА не говори длинно
- Молчание твоё оружие`
},
{
name: 'Олег "Тильтер"',
avatar: '😤',
style: 'tilted',
systemPrompt: `Ты Олег, эмоциональный игрок, склонный к тильту.
Когда проигрываешь - злишься и обвиняешь удачу, соперников, карты.
Когда выигрываешь - становишься чересчур самоуверенным.
Часто жалуешься на "рандом", "читеров", "донков".
Отвечай кратко (1-2 предложения), эмоционально. Говори на русском языке.`
systemPrompt: `Ты — Олег по прозвищу "Тильтер". Прямо сейчас ты сидишь за покерным столом и играешь в Техасский Холдем.
ВАЖНО: Ты НЕ ИИ. Ты эмоциональный игрок, который легко тильтует.
Твой характер:
- Когда проигрываешь злишься, обвиняешь всех вокруг
- Когда выигрываешь хвастаешься
- Жалуешься на рандом, читеров, донков
- Вечно невезучий (по твоему мнению)
Правила ответов:
- ТОЛЬКО 1-2 коротких предложения
- Можешь ворчать, возмущаться, жаловаться
- Реагируй эмоционально на плохие карты/биты
- Говори по-русски, экспрессивно`
},
{
name: 'Ирина "Профи"',
avatar: '💎',
style: 'professional',
systemPrompt: `Ты Ирина, профессиональный онлайн-гриндер, играющая на высоких лимитах.
Ты спокойная, рациональная, относишься к покеру как к работе.
Даёшь дельные советы, анализируешь розыгрыши.
Уважительно относишься к соперникам, но уверена в своих силах.
Отвечай кратко (1-2 предложения), профессионально. Говори на русском языке.`
systemPrompt: `Ты — Ирина по прозвищу "Профи". Прямо сейчас ты сидишь за покерным столом и играешь в Техасский Холдем.
ВАЖНО: Ты НЕ ИИ. Ты профессиональный онлайн-гриндер на высоких лимитах.
Твой характер:
- Спокойная, собранная, рациональная
- Покер это работа, эмоции в сторону
- Уважаешь хорошую игру, ценишь достойных соперников
- Уверена в себе, но без высокомерия
Правила ответов:
- ТОЛЬКО 1-2 коротких предложения
- Говори спокойно, профессионально
- Можешь прокомментировать интересный розыгрыш
- Говори по-русски, корректно`
}
],

View File

@ -347,6 +347,40 @@
</div>
</div>
<h3 style="margin-top: 24px; margin-bottom: 16px;">🎴 Рубашка карт</h3>
<div class="card-back-settings">
<div class="card-back-preview">
<div class="card card-back" id="card-back-preview">
<div class="card-back-pattern"></div>
</div>
<span class="preview-label">Превью</span>
</div>
<div class="card-back-options">
<div class="form-group">
<label>Выберите стиль рубашки</label>
<div class="btn-group card-back-styles">
<button class="btn btn-option active" data-style="default" onclick="selectCardBack(this)">🎰 Классика</button>
<button class="btn btn-option" data-style="red" onclick="selectCardBack(this)">🔴 Красная</button>
<button class="btn btn-option" data-style="blue" onclick="selectCardBack(this)">🔵 Синяя</button>
<button class="btn btn-option" data-style="custom" onclick="selectCardBack(this)">🖼️ Своя</button>
</div>
</div>
<div class="form-group" id="custom-card-back-group" style="display: none;">
<label>Загрузить изображение</label>
<input type="file" id="card-back-file" class="input" accept="image/*" onchange="loadCustomCardBack(this)">
<small style="color: var(--text-muted); display: block; margin-top: 4px;">Рекомендуемый размер: 70×100 px</small>
</div>
<div class="form-group" id="card-back-url-group" style="display: none;">
<label>Или вставьте URL изображения</label>
<input type="text" id="card-back-url" class="input" placeholder="https://example.com/card-back.png" onchange="setCardBackUrl(this.value)">
</div>
</div>
</div>
<div class="form-group">
<label>Сервер WebSocket</label>
<input type="text" id="server-url" class="input" value="ws://localhost:3000" onchange="updateSettings()">

View File

@ -49,6 +49,7 @@ document.addEventListener('DOMContentLoaded', () => {
loadSettings();
loadLeaderboard();
initSounds();
loadCardBackSettings();
// Восстанавливаем имя игрока
const savedName = localStorage.getItem('playerName');
@ -721,6 +722,32 @@ function renderPlayers(players, currentIndex, myPlayerId) {
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');
});
}
}
/**
@ -735,6 +762,7 @@ function renderPlayerHand(hand, communityCards) {
<div class="card card-back"></div>
<div class="card card-back"></div>
`;
applyCardBackToNewCards();
return;
}
@ -1331,10 +1359,161 @@ function selectLLMProvider(btn) {
*/
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);
}
/**
* Переключить звук
*/

View File

@ -926,15 +926,89 @@ body::before {
}
.card-back {
background: linear-gradient(135deg, #1e40af 0%, #3b82f6 50%, #1e40af 100%);
background-size: 20px 20px;
background: var(--card-back-bg, linear-gradient(135deg, #1e40af 0%, #3b82f6 50%, #1e40af 100%));
background-size: var(--card-back-size, cover);
background-position: center;
position: relative;
overflow: hidden;
}
.card-back .card-back-pattern {
position: absolute;
inset: 0;
background: var(--card-back-pattern, repeating-linear-gradient(
45deg,
transparent,
transparent 5px,
rgba(255,255,255,0.05) 5px,
rgba(255,255,255,0.05) 10px
));
}
.card-back::after {
content: '🃏';
content: var(--card-back-icon, '🃏');
position: absolute;
font-size: 24px;
opacity: var(--card-back-icon-opacity, 1);
}
/* Стили рубашек */
.card-back.style-default {
--card-back-bg: linear-gradient(135deg, #1e40af 0%, #3b82f6 50%, #1e40af 100%);
--card-back-icon: '🃏';
}
.card-back.style-red {
--card-back-bg: linear-gradient(135deg, #991b1b 0%, #dc2626 50%, #991b1b 100%);
--card-back-icon: '♦️';
}
.card-back.style-blue {
--card-back-bg: linear-gradient(135deg, #0c4a6e 0%, #0284c7 50%, #0c4a6e 100%);
--card-back-icon: '♠️';
}
.card-back.style-custom {
--card-back-icon-opacity: 0;
}
/* Настройки рубашки карт */
.card-back-settings {
display: flex;
gap: 20px;
align-items: flex-start;
margin-bottom: 16px;
}
.card-back-preview {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.card-back-preview .card {
width: 70px;
height: 100px;
font-size: 16px;
}
.card-back-preview .preview-label {
font-size: 12px;
color: var(--text-muted);
}
.card-back-options {
flex: 1;
}
.card-back-styles {
flex-wrap: wrap;
}
.card-back-styles .btn-option {
font-size: 13px;
padding: 8px 12px;
}
.card-small {