24 Commits

Author SHA1 Message Date
jahlib 5431eebb01 big design update 2025-11-11 23:24:00 +03:00
jahlib a90c1eacbb some updates 2025-11-11 23:23:32 +03:00
jahlib b372674f32 Add files via upload 2025-11-11 23:19:11 +03:00
jahlib e2561a9af1 Add files via upload 2025-11-11 23:16:56 +03:00
jahlib c169cef984 Add files via upload 2025-11-11 23:13:15 +03:00
jahlib 024f20f651 Add files via upload 2025-11-11 23:04:22 +03:00
jahlib e4f45663ac Add files via upload 2025-11-11 23:00:54 +03:00
jahlib b86d506b2d Add files via upload 2025-11-11 22:55:32 +03:00
jahlib 479e51f67a Add files via upload 2025-11-11 22:51:02 +03:00
jahlib f64a0f16d9 Add files via upload 2025-11-11 22:46:16 +03:00
jahlib a03b267fb3 Add files via upload 2025-11-11 22:43:29 +03:00
jahlib 010f25f882 some changes 2025-11-11 18:48:54 +03:00
jahlib 0488e10217 some changes 2025-11-11 18:48:35 +03:00
jahlib a043f25f59 add sounds 2025-11-11 03:05:06 +03:00
jahlib 75b4a8f84b huh and pipe 2025-11-11 02:54:15 +03:00
jahlib f35c234c65 add sounds 2025-11-11 02:53:21 +03:00
jahlib ad610c5f9c add sounds 2025-11-11 02:13:46 +03:00
jahlib a26c3f79fe add sounds 2025-11-11 02:11:44 +03:00
jahlib 77668ba463 bot reaction fix 2025-11-10 17:28:08 +03:00
jahlib e5406ec333 bot fix 2025-11-10 16:56:27 +03:00
jahlib c2354d31fd bot fix 2025-11-10 16:53:14 +03:00
jahlib b75ab0a61b bot reactions 2025-11-10 16:50:44 +03:00
jahlib cb07a64dbc MORE QUICK REACTIONS mousewheel fix 2025-11-10 16:34:57 +03:00
jahlib 85f9f58680 MORE QUICK REACTIONS mousewheel fix 2025-11-10 16:32:12 +03:00
19 changed files with 1420 additions and 248 deletions
+238 -4
View File
@@ -13,6 +13,56 @@ class CardGame {
// Загружаем состояние звука из localStorage
this.soundEnabled = localStorage.getItem('soundEnabled') !== 'false';
// Маппинг звуков для реакций
this.reactionSounds = {
'🙃': 'chat',
'🙁': 'chat',
'😡': 'error',
'😈': 'chat',
'😇': 'chat',
'😠': 'chat',
'😄': 'chat',
'😎': 'chat',
'🤔': 'pogodika',
'😤': 'chat',
'😐': 'chat',
'👍': 'chat',
'👎': 'chat',
'🤝': 'chat',
'🫰': 'chat',
'🪬': 'chat',
'🤯': 'chat',
'🤡': 'chat',
'🤨': 'vine',
'😑': 'chat',
'😌': 'chat',
'😴': 'chat',
'🌚': 'cave',
'🐱': 'cat',
'🐸': 'frog',
'🐺': 'driff',
'🐊': 'dance',
'🐠': 'chat',
'🦆': 'duck',
'🌹': 'wow',
'🗿': 'huh',
'👁️': 'chat',
'💩': 'fart',
'🔩': 'pipe',
'🔪': 'okay',
'⚔️': 'sword',
'🧀': 'meme',
'🎺': 'trumpet',
'🎁': 'chat',
'🔮': 'chat',
'🎲': 'chat',
'🎯': 'chat',
'♥️': 'chat',
'♦️': 'chat',
'♣️': 'chat',
'♠️': 'chat'
};
// Инициализация звуков
this.sounds = {
playcard: new Audio('/sounds/playcard.aac'),
@@ -31,7 +81,23 @@ class CardGame {
seven: new Audio('/sounds/seven.aac'),
shuffle: new Audio('/sounds/shuffle.aac'),
ace: new Audio('/sounds/ace.aac'),
eightplace: new Audio('/sounds/eightplace.aac')
eightplace: new Audio('/sounds/eightplace.aac'),
cat: new Audio('/sounds/cat.aac'),
okay: new Audio('/sounds/okay.aac'),
sword: new Audio('/sounds/sword.aac'),
pipe: new Audio('/sounds/pipe.aac'),
huh: new Audio('/sounds/huh.aac'),
pogodika: new Audio('/sounds/pogodika.aac'),
dance: new Audio('/sounds/dance.aac'),
fart: new Audio('/sounds/fart.aac'),
wow: new Audio('/sounds/wow.aac'),
cave: new Audio('/sounds/cave.aac'),
trumpet: new Audio('/sounds/trumpet.aac'),
driff: new Audio('/sounds/driff.aac'),
meme: new Audio('/sounds/meme.aac'),
error: new Audio('/sounds/error.aac'),
vine: new Audio('/sounds/vine.aac'),
duck: new Audio('/sounds/duck.aac')
};
this.initElements();
@@ -127,8 +193,28 @@ class CardGame {
if (this.nightModeEnabled) {
document.body.classList.add('night-mode');
// Показываем настройку белых мастей
if (this.whiteSuitsSetting) {
this.whiteSuitsSetting.style.display = 'flex';
}
// Восстанавливаем состояние белых мастей если оно было сохранено
const savedWhiteSuits = localStorage.getItem('whiteSuitsEnabled');
if (savedWhiteSuits === 'true') {
this.whiteSuitsEnabled = true;
if (this.whiteSuitsToggle) {
this.whiteSuitsToggle.checked = true;
}
document.body.classList.add('white-suits');
}
} else {
document.body.classList.remove('night-mode');
// Скрываем настройку белых мастей
if (this.whiteSuitsSetting) {
this.whiteSuitsSetting.style.display = 'none';
}
// Выключаем белые масти
this.whiteSuitsEnabled = false;
document.body.classList.remove('white-suits');
}
this.updateNightModeButton();
@@ -140,8 +226,28 @@ class CardGame {
if (this.nightModeEnabled) {
document.body.classList.add('night-mode');
// Показываем настройку белых мастей
if (this.whiteSuitsSetting) {
this.whiteSuitsSetting.style.display = 'flex';
}
// Восстанавливаем состояние белых мастей если оно было сохранено
const savedWhiteSuits = localStorage.getItem('whiteSuitsEnabled');
if (savedWhiteSuits === 'true') {
this.whiteSuitsEnabled = true;
if (this.whiteSuitsToggle) {
this.whiteSuitsToggle.checked = true;
}
document.body.classList.add('white-suits');
}
} else {
document.body.classList.remove('night-mode');
// Скрываем настройку белых мастей
if (this.whiteSuitsSetting) {
this.whiteSuitsSetting.style.display = 'none';
}
// Выключаем белые масти
this.whiteSuitsEnabled = false;
document.body.classList.remove('white-suits');
}
// Синхронизируем с переключателем в настройках
@@ -168,6 +274,47 @@ class CardGame {
if (moonIcon) moonIcon.style.display = 'inline';
}
}
toggleWhiteSuits() {
this.whiteSuitsEnabled = this.whiteSuitsToggle.checked;
localStorage.setItem('whiteSuitsEnabled', this.whiteSuitsEnabled);
if (this.whiteSuitsEnabled) {
document.body.classList.add('white-suits');
} else {
document.body.classList.remove('white-suits');
}
}
setCardBackColor(color) {
this.cardBackColor = color;
localStorage.setItem('cardBackColor', color);
document.body.setAttribute('data-card-back', color);
// Обновляем выделение в селекторе
document.querySelectorAll('.card-back-option').forEach(opt => {
opt.classList.remove('selected');
});
const selectedOption = document.querySelector(`.card-back-option[data-back="${color}"]`);
if (selectedOption) {
selectedOption.classList.add('selected');
}
}
setBackgroundColor(color) {
this.backgroundColor = color;
localStorage.setItem('backgroundColor', color);
document.body.setAttribute('data-background', color);
// Обновляем выделение в селекторе
document.querySelectorAll('.background-option').forEach(opt => {
opt.classList.remove('selected');
});
const selectedOption = document.querySelector(`.background-option[data-bg="${color}"]`);
if (selectedOption) {
selectedOption.classList.add('selected');
}
}
showAlert(message) {
if (!this.alertModal || !this.alertText) return;
@@ -225,8 +372,18 @@ class CardGame {
goToLobby() {
this.clearLocalStorage();
// Закрываем WebSocket соединение перед перезагрузкой
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.ws.close();
}
window.history.pushState({}, '', '/');
location.reload();
// Небольшая задержка чтобы сообщение успело отправиться
setTimeout(() => {
location.reload();
}, 40);
}
initElements() {
@@ -315,6 +472,8 @@ class CardGame {
this.animationsToggle = document.getElementById('animations-toggle');
this.proModeToggle = document.getElementById('pro-mode-toggle');
this.nightModeToggle = document.getElementById('night-mode-toggle');
this.whiteSuitsToggle = document.getElementById('white-suits-toggle');
this.whiteSuitsSetting = document.getElementById('white-suits-setting');
this.logToggle = document.getElementById('log-toggle');
// Leave game button and modal
@@ -343,6 +502,29 @@ class CardGame {
document.body.classList.add('night-mode');
}
// Инициализируем белые масти
const savedWhiteSuits = localStorage.getItem('whiteSuitsEnabled');
this.whiteSuitsEnabled = savedWhiteSuits === 'true';
// Показываем настройку белых мастей только если ночной режим включен
if (this.nightModeEnabled && this.whiteSuitsSetting) {
this.whiteSuitsSetting.style.display = 'flex';
// Применяем белые масти если они были включены
if (this.whiteSuitsEnabled) {
document.body.classList.add('white-suits');
}
}
// Инициализируем цвет рубашки карт
const savedCardBack = localStorage.getItem('cardBackColor') || 'gray';
this.cardBackColor = savedCardBack;
document.body.setAttribute('data-card-back', savedCardBack);
// Инициализируем цвет фона
const savedBackground = localStorage.getItem('backgroundColor') || 'default';
this.backgroundColor = savedBackground;
document.body.setAttribute('data-background', savedBackground);
// Обновляем иконку кнопки ночного режима
this.updateNightModeButton();
}
@@ -392,6 +574,7 @@ class CardGame {
this.animationsToggle.addEventListener('change', () => this.toggleAnimations());
this.proModeToggle.addEventListener('change', () => this.toggleProMode());
this.nightModeToggle.addEventListener('change', () => this.toggleNightMode());
this.whiteSuitsToggle.addEventListener('change', () => this.toggleWhiteSuits());
this.logToggle.addEventListener('change', () => this.toggleLog());
// Leave game button
@@ -455,6 +638,22 @@ class CardGame {
}
});
// Card back color selector
document.querySelectorAll('.card-back-option').forEach(option => {
option.addEventListener('click', (e) => {
const backColor = e.currentTarget.dataset.back;
this.setCardBackColor(backColor);
});
});
// Background color selector
document.querySelectorAll('.background-option').forEach(option => {
option.addEventListener('click', (e) => {
const bgColor = e.currentTarget.dataset.bg;
this.setBackgroundColor(bgColor);
});
});
// Rules handlers
this.rulesBtn.addEventListener('click', () => this.openRules());
this.closeRulesBtn.addEventListener('click', () => this.closeRules());
@@ -898,8 +1097,8 @@ class CardGame {
break;
case 'reaction':
// Быстрая реакция от игрока
// Для лягушки играем специальный звук
const soundName = data.emoji === '🐸' ? 'frog' : 'chat';
// Используем маппинг звуков для реакций
const soundName = this.reactionSounds[data.emoji] || 'chat';
this.playSound(soundName);
this.showReactionBubble(data.player_id, data.emoji);
break;
@@ -1126,6 +1325,11 @@ class CardGame {
}
leaveRoom() {
// Отправляем сообщение о выходе из комнаты
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
this.send({ type: 'leave_room' });
}
// Скрываем блок с ссылкой если он был показан
if (this.inviteLinkBlock) {
this.inviteLinkBlock.style.display = 'none';
@@ -1574,8 +1778,32 @@ class CardGame {
this.animationsToggle.checked = this.animationsEnabled;
this.proModeToggle.checked = this.proModeEnabled;
this.nightModeToggle.checked = this.nightModeEnabled;
this.whiteSuitsToggle.checked = this.whiteSuitsEnabled;
this.fullscreenToggle.checked = !!document.fullscreenElement;
// Показываем/скрываем настройку белых мастей в зависимости от ночного режима
if (this.whiteSuitsSetting) {
this.whiteSuitsSetting.style.display = this.nightModeEnabled ? 'flex' : 'none';
}
// Синхронизируем выбранный цвет рубашки
document.querySelectorAll('.card-back-option').forEach(opt => {
opt.classList.remove('selected');
});
const selectedOption = document.querySelector(`.card-back-option[data-back="${this.cardBackColor}"]`);
if (selectedOption) {
selectedOption.classList.add('selected');
}
// Синхронизируем выбранный цвет фона
document.querySelectorAll('.background-option').forEach(opt => {
opt.classList.remove('selected');
});
const selectedBgOption = document.querySelector(`.background-option[data-bg="${this.backgroundColor}"]`);
if (selectedBgOption) {
selectedBgOption.classList.add('selected');
}
// Синхронизируем состояние лога (проверяем есть ли класс hidden)
if (this.gameLog) {
this.logToggle.checked = !this.gameLog.classList.contains('hidden');
@@ -1855,6 +2083,12 @@ class CardGame {
hasMoved = false;
}
}, true);
// Скролл колёсиком мыши по горизонтали
scrollContainer.addEventListener('wheel', (e) => {
e.preventDefault();
scrollContainer.scrollLeft += e.deltaY * 2.42; // Ускоряем скролл в 2 раза
}, { passive: false });
}
}
+76 -3
View File
@@ -248,17 +248,24 @@
<!-- Модальное окно быстрых реакций -->
<div id="reaction-picker" class="reaction-picker">
<div class="reaction-picker-scroll">
<button class="reaction-btn" data-reaction="😡">😡</button>
<button class="reaction-btn" data-reaction="😄">😄</button>
<button class="reaction-btn" data-reaction="😎">😎</button>
<button class="reaction-btn" data-reaction="🙃">🙃</button>
<button class="reaction-btn" data-reaction="🙁">🙁</button>
<button class="reaction-btn" data-reaction="😡">😡</button>
<button class="reaction-btn" data-reaction="😈">😈</button>
<button class="reaction-btn" data-reaction="😇">😇</button>
<button class="reaction-btn" data-reaction="😠">😠</button>
<button class="reaction-btn" data-reaction="😄">😄</button>
<button class="reaction-btn" data-reaction="😎">😎</button>
<button class="reaction-btn" data-reaction="🤔">🤔</button>
<button class="reaction-btn" data-reaction="😤">😤</button>
<button class="reaction-btn" data-reaction="😐">😐</button>
<button class="reaction-btn" data-reaction="👍">👍</button>
<button class="reaction-btn" data-reaction="👎">👎</button>
<button class="reaction-btn" data-reaction="🤝">🤝</button>
<button class="reaction-btn" data-reaction="🫰">🫰</button>
<button class="reaction-btn" data-reaction="🪬">🪬</button>
<button class="reaction-btn" data-reaction="🤯">🤯</button>
<button class="reaction-btn" data-reaction="🤡">🤡</button>
<button class="reaction-btn" data-reaction="🤨">🤨</button>
<button class="reaction-btn" data-reaction="😑">😑</button>
<button class="reaction-btn" data-reaction="😌">😌</button>
@@ -266,9 +273,18 @@
<button class="reaction-btn" data-reaction="🌚">🌚</button>
<button class="reaction-btn" data-reaction="🐱">🐱</button>
<button class="reaction-btn" data-reaction="🐸">🐸</button>
<button class="reaction-btn" data-reaction="🐺">🐺</button>
<button class="reaction-btn" data-reaction="🐊">🐊</button>
<button class="reaction-btn" data-reaction="🐠">🐠</button>
<button class="reaction-btn" data-reaction="🌹">🌹</button>
<button class="reaction-btn" data-reaction="🗿">🗿</button>
<button class="reaction-btn" data-reaction="👁️">👁️</button>
<button class="reaction-btn" data-reaction="💩">💩</button>
<button class="reaction-btn" data-reaction="🔩">🔩</button>
<button class="reaction-btn" data-reaction="🔪">🔪</button>
<button class="reaction-btn" data-reaction="⚔️">⚔️</button>
<button class="reaction-btn" data-reaction="🎺">🎺</button>
<button class="reaction-btn" data-reaction="🔮">🔮</button>
<button class="reaction-btn" data-reaction="🎲">🎲</button>
<button class="reaction-btn" data-reaction="🎯">🎯</button>
<button class="reaction-btn" data-reaction="♥️">♥️</button>
@@ -418,6 +434,19 @@
</div>
</div>
<!-- Белые масти (только в ночном режиме) -->
<div class="setting-item" id="white-suits-setting" style="display: none;">
<span class="setting-label">🤍 Белые масти</span>
<div class="deck-size-toggle">
<label>
<span class="deck-label">Выкл</span>
<input type="checkbox" id="white-suits-toggle">
<span class="toggle-slider"></span>
<span class="deck-label">Вкл</span>
</label>
</div>
</div>
<!-- Лог (только на мобильных) -->
<div class="setting-item mobile-only">
<span class="setting-label">📃 Показывать лог</span>
@@ -430,6 +459,50 @@
</label>
</div>
</div>
<!-- Выбор цвета рубашки карт -->
<div class="setting-item">
<span class="setting-label">🎴 Цвет рубашки карт</span>
<div class="card-back-selector">
<div class="card-back-option" data-back="gray">
<div class="card-back-preview card-back-gray"></div>
</div>
<div class="card-back-option" data-back="green">
<div class="card-back-preview card-back-green"></div>
</div>
<div class="card-back-option" data-back="blue">
<div class="card-back-preview card-back-blue"></div>
</div>
<div class="card-back-option" data-back="red">
<div class="card-back-preview card-back-red"></div>
</div>
<div class="card-back-option" data-back="purple">
<div class="card-back-preview card-back-purple"></div>
</div>
</div>
</div>
<!-- Выбор цвета фона -->
<div class="setting-item">
<span class="setting-label">🎨 Цвет фона</span>
<div class="background-selector">
<div class="background-option" data-bg="default">
<div class="background-preview bg-default"></div>
</div>
<div class="background-option" data-bg="green">
<div class="background-preview bg-green"></div>
</div>
<div class="background-option" data-bg="blue">
<div class="background-preview bg-blue"></div>
</div>
<div class="background-option" data-bg="purple">
<div class="background-preview bg-purple"></div>
</div>
<div class="background-option" data-bg="orange">
<div class="background-preview bg-orange"></div>
</div>
</div>
</div>
</div>
</div>
</div>
+569 -168
View File
File diff suppressed because it is too large Load Diff
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+464 -72
View File
@@ -4,14 +4,116 @@
box-sizing: border-box;
}
/* Плавные переходы для всех элементов кроме летящих карт и тряски */
*:not(.flying-card):not(.flying-card *):not(.shaking):not(.shaking *):not(.card):not(.card *):not(.deck-shuffling):not(.deck-shuffling *) {
transition: all 0.3s ease;
}
html {
/* Поддержка safe area для iOS */
padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left);
}
/* CSS переменные для тем - дефолтная зелёная тема */
:root,
body:not([data-background]),
body[data-background="default"] {
--theme-primary: #27b790;
--theme-primary-hover: #5eda93;
--theme-primary-dark: #1b6d3e;
--theme-secondary: #32a082;
--theme-secondary-dark: #28866d;
--theme-accent: #f5d742;
--theme-accent-dark: #d4af37;
--theme-bg-light: #f8f9fa;
--theme-bg-medium: #e0e0e0;
--theme-bg-dark: #626262;
--theme-border: #e0e0e0;
--theme-success: #4caf50;
--theme-success-bg: #e8f5e9;
--theme-toggle-off: #ccc;
--theme-toggle-on: #1b6d3e;
}
/* Бирюзовая тема */
body[data-background="green"] {
--theme-primary: #69b8ca;
--theme-primary-hover: #3daac2;
--theme-primary-dark: #2d5266;
--theme-secondary: #3d6a7a;
--theme-secondary-dark: #25454f;
--theme-accent: #7db3c7;
--theme-accent-dark: #5a8fa3;
--theme-bg-light: #e8f0f2;
--theme-bg-medium: #d0dee2;
--theme-bg-dark: #8fa3ab;
--theme-border: #c0d0d5;
--theme-success: #5a8fa3;
--theme-success-bg: #d8e8ed;
--theme-toggle-off: #a0b5bd;
--theme-toggle-on: #69b8ca;
}
/* Коричневая тема */
body[data-background="blue"] {
--theme-primary: #c17a5d;
--theme-primary-hover: #d89a7d;
--theme-primary-dark: #523c26;
--theme-secondary: #5d4539;
--theme-secondary-dark: #3d2b1f;
--theme-accent: #a8856a;
--theme-accent-dark: #8b6b4d;
--theme-bg-light: #f2ede8;
--theme-bg-medium: #e2d5d0;
--theme-bg-dark: #9d8a7d;
--theme-border: #d5c5bd;
--theme-success: #8b6b4d;
--theme-success-bg: #ede5dd;
--theme-toggle-off: #b5a599;
--theme-toggle-on: #c17a5d;
}
/* Фиолетовая тема */
body[data-background="purple"] {
--theme-primary: #ba9bd0;
--theme-primary-hover: #c69bde;
--theme-primary-dark: #503d6c;
--theme-secondary: #6d5580;
--theme-secondary-dark: #453560;
--theme-accent: #b899d0;
--theme-accent-dark: #9d7bb3;
--theme-bg-light: #f0ebf5;
--theme-bg-medium: #e0d5e8;
--theme-bg-dark: #a896b8;
--theme-border: #d5c5e0;
--theme-success: #9d7bb3;
--theme-success-bg: #ede5f2;
--theme-toggle-off: #bfb0cf;
--theme-toggle-on: #ba9bd0;
}
/* Оливковая тема */
body[data-background="orange"] {
--theme-primary: #c4ca8d;
--theme-primary-hover: #c8d07d;
--theme-primary-dark: #6a5d54;
--theme-secondary: #7a8254;
--theme-secondary-dark: #6a5d54;
--theme-accent: #bfcc8d;
--theme-accent-dark: #a3b075;
--theme-bg-light: #f2f0e8;
--theme-bg-medium: #e2ded0;
--theme-bg-dark: #a8a68f;
--theme-border: #d5d0bd;
--theme-success: #a3b075;
--theme-success-bg: #edeee5;
--theme-toggle-off: #c0bda8;
--theme-toggle-on: #c4ca8d;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #626262;
background: var(--theme-bg-dark);
min-height: 100vh;
color: #333;
-webkit-overflow-scrolling: touch;
@@ -69,8 +171,8 @@ h1 {
right: 0;
top: 50%;
transform: translateY(-50%);
background: #f0f0f0;
border: 2px solid #ddd;
background: var(--theme-bg-light);
border: 2px solid var(--theme-border);
border-radius: 50%;
width: 50px;
height: 50px;
@@ -84,7 +186,7 @@ h1 {
}
.night-mode-btn:hover {
background: #e0e0e0;
background: var(--theme-bg-medium);
transform: translateY(-50%) scale(1.1);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
}
@@ -116,7 +218,7 @@ input[type="text"] {
input[type="text"]:focus {
outline: none;
border-color: #1b6d3e;
border-color: var(--theme-primary-dark);
}
.btn {
@@ -130,23 +232,23 @@ input[type="text"]:focus {
}
.btn-primary {
background: #27b790;
background: var(--theme-primary);
color: white;
}
.btn-primary:hover {
background: #5eda93;
background: var(--theme-primary-hover);
transform: translateY(-2px);
box-shadow: 0 5px 15px rgba(126, 126, 126, 0.521);
}
.btn-secondary {
background: #e0e0e0;
background: var(--theme-bg-medium);
color: #333;
}
.btn-secondary:hover {
background: #d0d0d0;
background: var(--theme-bg-medium);
}
.btn:disabled {
@@ -160,11 +262,11 @@ input[type="text"]:focus {
}
.btn-primary:disabled:hover {
background: #27b790 !important;
background: var(--theme-primary) !important;
}
.btn-secondary:disabled:hover {
background: #e0e0e0 !important;
background: var(--theme-bg-medium) !important;
}
/* Create Room Section */
@@ -198,10 +300,10 @@ input[type="text"]:focus {
flex: 1;
padding: 10px;
background: white;
border: 1px solid #ddd;
border: 1px solid var(--theme-border);
border-radius: 6px;
font-size: 13px;
color: #32a082;
color: var(--theme-secondary);
word-break: break-all;
user-select: all;
}
@@ -211,16 +313,16 @@ input[type="text"]:focus {
height: 44px;
padding: 8px;
font-size: 20px;
background: #32a082;
background: var(--theme-secondary);
color: white;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s;
transition: all 0.2s;
}
.btn-copy:hover, .btn-share:hover {
background: #28866d;
background: var(--theme-secondary-dark);
transform: scale(1.05);
}
@@ -251,7 +353,7 @@ input[type="text"]:focus {
}
.btn-bot {
background: linear-gradient(135deg, #32a082 0%, #64859a 100%);
background: linear-gradient(135deg, var(--theme-secondary) 0%, var(--theme-secondary-dark) 100%);
color: white;
border: none;
padding: 12px 24px;
@@ -280,22 +382,22 @@ input[type="text"]:focus {
}
.room-card {
background: #f8f9fa;
background: var(--theme-bg-light);
padding: 20px;
border-radius: 15px;
border: 2px solid #e0e0e0;
border: 2px solid var(--theme-border);
cursor: pointer;
transition: all 0.3s;
}
.room-card:hover {
border-color: #1b6d3e;
border-color: var(--theme-primary-dark);
transform: translateY(-5px);
box-shadow: 0 10px 25px rgba(126, 126, 126, 0.2);
}
.room-card h4 {
color: #1b6d3e;
color: var(--theme-primary-dark);
margin-bottom: 10px;
}
@@ -305,7 +407,7 @@ input[type="text"]:focus {
}
.room-card.room-full {
background: #f0f0f0;
background: var(--theme-bg-light);
border-color: #ccc;
opacity: 0.6;
cursor: not-allowed;
@@ -349,20 +451,20 @@ input[type="text"]:focus {
}
.player-card {
background: #f8f9fa;
background: var(--theme-bg-light);
padding: 15px;
border-radius: 10px;
border: 2px solid #e0e0e0;
border: 2px solid var(--theme-border);
}
.player-card.ready {
border-color: #4caf50;
background: #e8f5e9;
border-color: var(--theme-success);
background: var(--theme-success-bg);
}
.player-card.current {
border-color: #1b6d3e;
background: #e8f5e9;
border-color: var(--theme-primary-dark);
background: var(--theme-success-bg);
box-shadow: 0 0 20px rgba(49, 145, 91, 0.3);
}
@@ -376,7 +478,7 @@ input[type="text"]:focus {
}
.player-card.ready .status {
color: #4caf50;
color: var(--theme-success);
font-weight: 600;
}
@@ -448,7 +550,7 @@ input[type="text"]:focus {
position: relative;
width: 60px;
height: 30px;
background: #ccc;
background: var(--theme-toggle-off);
border-radius: 15px;
transition: background 0.3s;
}
@@ -467,7 +569,7 @@ input[type="text"]:focus {
}
.deck-size-toggle input[type="checkbox"]:checked + .toggle-slider {
background: #31915b;
background: var(--theme-toggle-on);
}
.deck-size-toggle input[type="checkbox"]:checked + .toggle-slider::before {
@@ -475,11 +577,11 @@ input[type="text"]:focus {
}
.deck-size-toggle input[type="checkbox"]:checked ~ .deck-label:last-child {
color: #31915b;
color: var(--theme-primary);
}
.deck-size-toggle input[type="checkbox"]:not(:checked) ~ .deck-label:first-child {
color: #31915b;
color: var(--theme-primary);
}
/* Countdown Timer */
@@ -492,7 +594,7 @@ input[type="text"]:focus {
height: 150px;
margin: 0 auto;
border-radius: 50%;
background: linear-gradient(135deg, #32a082 0%, #64859a 100%);
background: linear-gradient(135deg, var(--theme-secondary) 0%, var(--theme-secondary-dark) 100%);
display: flex;
align-items: center;
justify-content: center;
@@ -505,7 +607,7 @@ input[type="text"]:focus {
width: 40px;
height: 40px;
border-radius: 50%;
background: linear-gradient(135deg, #32a082 0%, #64859a 100%);
background: linear-gradient(135deg, var(--theme-secondary) 0%, var(--theme-secondary-dark) 100%);
display: inline-flex;
align-items: center;
justify-content: center;
@@ -527,7 +629,7 @@ input[type="text"]:focus {
.countdown-text {
font-size: 20px;
font-weight: bold;
color: #31915b;
color: var(--theme-primary);
margin-top: 20px;
animation: fadeInOut 1s ease-in-out infinite;
}
@@ -557,7 +659,7 @@ input[type="text"]:focus {
display: flex;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #32a082 0%, #64859a 100%);
background: linear-gradient(135deg, var(--theme-secondary) 0%, var(--theme-secondary-dark) 100%);
}
.error-content {
@@ -699,7 +801,7 @@ input[type="text"]:focus {
background: rgba(49, 145, 91, 0.3);
padding: 5px 8px;
border-radius: 4px;
border-left: 3px solid #31915b;
border-left: 3px solid var(--theme-primary);
font-weight: 600;
}
@@ -714,7 +816,7 @@ input[type="text"]:focus {
/* Цвета для разных игроков */
.log-entry.chat-color-1 {
background: rgba(76, 175, 80, 0.2);
border-left-color: #4caf50;
border-left-color: var(--theme-success);
}
.log-entry.chat-color-2 {
@@ -771,8 +873,8 @@ input[type="text"]:focus {
right: 20px;
width: 50px;
height: 50px;
background: #0c3d0ace;
border: 2px solid #767676;
background: var(--theme-bg-light);
border: 2px solid var(--theme-border);
border-radius: 10px;
cursor: pointer;
display: flex;
@@ -786,7 +888,7 @@ input[type="text"]:focus {
}
.settings-btn:hover {
background: #ebebebd7;
background: var(--theme-bg-medium);
color: white;
transform: scale(1.1);
}
@@ -802,8 +904,8 @@ input[type="text"]:focus {
right: 20px;
width: 50px;
height: 50px;
background: #0c3d0ace;
border: 2px solid #767676;
background: var(--theme-bg-light);
border: 2px solid var(--theme-border);
border-radius: 10px;
cursor: pointer;
display: flex;
@@ -817,7 +919,7 @@ input[type="text"]:focus {
}
.rules-btn:hover {
background: #ebebebd7;
background: var(--theme-bg-medium);
color: white;
transform: scale(1.1);
}
@@ -833,8 +935,8 @@ input[type="text"]:focus {
right: 20px;
width: 50px;
height: 50px;
background: #0c3d0ace;
border: 2px solid #767676;
background: var(--theme-bg-light);
border: 2px solid var(--theme-border);
border-radius: 10px;
cursor: pointer;
display: flex;
@@ -848,7 +950,7 @@ input[type="text"]:focus {
}
.chat-btn:hover {
background: #ebebebd7;
background: var(--theme-bg-medium);
color: white;
transform: scale(1.1);
}
@@ -867,7 +969,6 @@ input[type="text"]:focus {
justify-content: center;
align-items: center;
padding: 10px;
background: linear-gradient(155deg, #205e36 0%, #0d3617 100%);
overflow: hidden;
user-select: none;
-webkit-user-select: none;
@@ -983,7 +1084,7 @@ input[type="text"]:focus {
}
.opponent-area.current-turn .opponent-info {
background: linear-gradient(135deg, #f5d742 0%, #e8c84d 100%);
background: linear-gradient(135deg, var(--theme-accent) 0%, var(--theme-accent-dark) 100%);
color: #333;
font-weight: 700;
border: 2px solid #d4af37;
@@ -1294,7 +1395,7 @@ border: 2px solid #ffd700;
/* Подсветка при ходе игрока */
.player-info.current-turn {
background: linear-gradient(135deg, #f5d742 0%, #e8c84d 100%);
background: linear-gradient(135deg, var(--theme-accent) 0%, var(--theme-accent-dark) 100%);
border: 0px;
border-bottom: none;
}
@@ -1379,13 +1480,13 @@ border: 2px solid #ffd700;
}
.modal-content::-webkit-scrollbar-thumb {
background: linear-gradient(135deg, #32a082 0%, #1b6d3e 100%);
background: linear-gradient(135deg, var(--theme-secondary) 0%, var(--theme-primary-dark) 100%);
border-radius: 10px;
border: 2px solid white;
}
.modal-content::-webkit-scrollbar-thumb:hover {
background: linear-gradient(135deg, #1b6d3e 0%, #31915b 100%);
background: linear-gradient(135deg, var(--theme-primary-dark) 0%, var(--theme-primary) 100%);
}
.modal-content h2, .modal-content h3 {
@@ -1440,15 +1541,14 @@ border: 2px solid #ffd700;
}
.close-modal-btn {
position: sticky;
top: 10px;
float: right;
margin: 10px 10px 0 0;
position: absolute;
top: 15px;
right: 15px;
width: 30px;
height: 30px;
border-radius: 50%;
background: #d7d7d7ce;
border: 2px solid #767676;
background: var(--theme-bg-medium);
border: 2px solid var(--theme-border);
color: #3c3c3c;
font-size: 20px;
font-weight: bold;
@@ -1464,7 +1564,7 @@ border: 2px solid #ffd700;
}
.close-modal-btn:hover {
background: #ebebebd7;
background: var(--theme-bg-light);
color: #7d2c2c;
transform: scale(1.1);
}
@@ -1497,8 +1597,24 @@ border: 2px solid #ffd700;
border-bottom: 1px solid rgba(118, 118, 118, 0.2);
}
.setting-item:nth-last-child(2) {
display: flex;
width: 100%;
border-bottom: 1px solid rgba(118, 118, 118, 0.2);
align-content: center;
flex-direction: column;
justify-content: center;
align-items: center;
}
.setting-item:last-child {
display: flex;
width: 100%;
border-bottom: none;
align-content: center;
flex-direction: column;
justify-content: center;
align-items: center;
}
.setting-label {
@@ -1580,13 +1696,26 @@ border: 2px solid #ffd700;
}
.suit-emoji {
z-index: 1012;
font-size: 32px;
filter: drop-shadow(0px 0px 10px #727272c9);
filter: drop-shadow(0px 0px 0.8px #727272c9);
}
/* Красные масти в чате (логе) */
body .suit-emoji-log.hearts,
body .suit-emoji-log.diamonds {
filter: saturate(0.8) brightness(1.1) drop-shadow(rgba(194, 90, 90, 0.79) 0px 0px 0.8px);
}
/* Чёрные масти в чате (логе) */
body .suit-emoji-log.clubs,
body .suit-emoji-log.spades {
filter: contrast(4) saturate(0.5) drop-shadow(0px 0px 0.8px #c7c7c75d);
}
.suit-btn:hover {
border-color: #767676;
background: #f0f0f0;
border-color: var(--theme-border);
background: var(--theme-bg-light);
transform: scale(1.05);
}
@@ -1607,14 +1736,14 @@ border: 2px solid #ffd700;
.result-item {
padding: 15px;
margin: 10px 0;
background: #f8f9fa;
background: var(--theme-bg-light);
border-radius: 10px;
border-left: 4px solid #1b6d3e;
border-left: 4px solid var(--theme-primary-dark);
}
.result-item.winner {
background: #e8f5e9;
border-left-color: #4caf50;
background: var(--theme-success-bg);
border-left-color: var(--theme-success);
}
.result-item h4 {
@@ -2187,15 +2316,11 @@ border: 2px solid #ffd700;
font-style: italic;
}
/* Flying card animation */
/* Flying card animation - background управляется через data-card-back */
.flying-card {
position: fixed;
width: 60px;
height: 85px;
background:
repeating-linear-gradient(45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.028) 6px, rgba(0, 0, 0, 0.037) 7px),
repeating-linear-gradient(-45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.028) 6px, rgba(0, 0, 0, 0.037) 7px),
linear-gradient(135deg, #2a5f4f, #1b4037);
border-radius: 8px;
display: flex;
align-items: center;
@@ -2390,3 +2515,270 @@ border: 2px solid #ffd700;
transform: translateX(-50%) translateY(-30px) scale(0.8);
}
}
/* Card back selector */
.card-back-selector {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 12px;
margin-top: 10px;
align-content: center;
justify-content: space-around;
align-items: center;
}
.card-back-option {
cursor: pointer;
border: 3px solid transparent;
border-radius: 8px;
padding: 3px;
transition: all 0.2s ease;
}
.card-back-option:hover {
border-color: #f5d742;
transform: scale(1.05);
}
.card-back-option.selected {
border-color: #4CAF50;
box-shadow: 0 0 10px rgba(76, 175, 80, 0.5);
}
.card-back-preview {
width: 45px;
height: 65px;
border: 2px solid #333;
border-radius: 6px;
display: flex;
justify-content: center;
align-items: center;
font-size: 18px;
color: white;
box-shadow: 0 3px 8px rgba(71, 71, 71, 0.3);
}
/* 5 вариантов цветов рубашки - светлые для дневного режима */
.card-back-gray {
background:
repeating-linear-gradient(45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
repeating-linear-gradient(-45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
linear-gradient(314deg, #686868, #9ca3af);
filter: saturate(0.8) brightness(0.95);
}
.card-back-green {
background:
repeating-linear-gradient(45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
repeating-linear-gradient(-45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
linear-gradient(314deg, #2a5a3f, #4a8a65);
filter: saturate(0.7) brightness(1.1);
}
.card-back-blue {
background:
repeating-linear-gradient(45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
repeating-linear-gradient(-45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
linear-gradient(314deg, #3a5a7f, #4a7ab0);
filter: saturate(0.7) brightness(1.1);
}
.card-back-red {
background:
repeating-linear-gradient(45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
repeating-linear-gradient(-45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
linear-gradient(314deg, #6a3a3a, #9a4a4a);
filter: saturate(0.7) brightness(1.1);
}
.card-back-purple {
background:
repeating-linear-gradient(45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
repeating-linear-gradient(-45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
linear-gradient(314deg, #6a4a8a, #8a6aaa);
filter: saturate(0.7) brightness(1.1);
}
/* Применение выбранного цвета рубашки - светлые для дневного режима */
body[data-card-back="gray"] .opponent-card,
body[data-card-back="gray"] .deck,
body[data-card-back="gray"] .card-back,
body[data-card-back="gray"] .flying-card {
background:
repeating-linear-gradient(45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
repeating-linear-gradient(-45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
linear-gradient(314deg, #686868, #9ca3af) !important;
filter: saturate(0.8) brightness(0.95);
border-color: #383838 !important;
}
body[data-card-back="green"] .opponent-card,
body[data-card-back="green"] .deck,
body[data-card-back="green"] .card-back,
body[data-card-back="green"] .flying-card {
background:
repeating-linear-gradient(45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
repeating-linear-gradient(-45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
linear-gradient(314deg, #2a5a3f, #4a8a65) !important;
filter: saturate(0.7) brightness(1.1);
border-color: #153a1a !important;
}
body[data-card-back="blue"] .opponent-card,
body[data-card-back="blue"] .deck,
body[data-card-back="blue"] .card-back,
body[data-card-back="blue"] .flying-card {
background:
repeating-linear-gradient(45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
repeating-linear-gradient(-45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
linear-gradient(314deg, #3a5a7f, #4a7ab0) !important;
filter: saturate(0.7) brightness(1.1);
border-color: #153a5a !important;
}
body[data-card-back="red"] .opponent-card,
body[data-card-back="red"] .deck,
body[data-card-back="red"] .card-back,
body[data-card-back="red"] .flying-card {
background:
repeating-linear-gradient(45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
repeating-linear-gradient(-45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
linear-gradient(314deg, #6a3a3a, #9a4a4a) !important;
filter: saturate(0.7) brightness(1.1);
border-color: #412020 !important;
}
body[data-card-back="purple"] .opponent-card,
body[data-card-back="purple"] .deck,
body[data-card-back="purple"] .card-back,
body[data-card-back="purple"] .flying-card {
background:
repeating-linear-gradient(45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
repeating-linear-gradient(-45deg, transparent, transparent 6px, rgba(0, 0, 0, 0.1) 6px, rgba(0, 0, 0, 0.15) 7px),
linear-gradient(314deg, #6a4a8a, #8a6aaa) !important;
filter: saturate(0.7) brightness(1.1);
border-color: #38234b !important;
}
/* Background selector */
.background-selector {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
margin-top: 10px;
}
.background-option {
cursor: pointer;
border: 2px solid transparent;
border-radius: 8px;
padding: 3px;
transition: all 0.2s ease;
}
.background-option:hover {
border-color: #4CAF50;
}
.background-option.selected {
border-color: #4CAF50;
box-shadow: 0 0 10px rgba(76, 175, 80, 0.5);
}
.background-preview {
width: 45px;
height: 45px;
border-radius: 6px;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.3);
}
/* 5 вариантов фонов для дневного режима - пастельные мягкие цвета */
.bg-default {
background: linear-gradient(169deg, #36724b 0%, #1d4627 90%);
}
.bg-green {
background: linear-gradient(155deg, #4a7c8f 0%, #2d5266 100%);
}
.bg-blue {
background: linear-gradient(230deg, #76564d 10%, #523c26 90%);
}
.bg-purple {
background: linear-gradient(219deg, #826994 10%, #503d6c 90%);
}
.bg-orange {
background: linear-gradient(206deg, #8f9861 10%, #847068 90%);
}
/* Применение выбранного фона для дневного режима */
body[data-background="default"] .game-table,
body:not([data-background]) .game-table {
background: linear-gradient(169deg, #36724b 0%, #1d4627 90%);
}
body[data-background="default"] .game-circle,
body:not([data-background]) .game-circle {
background: radial-gradient(circle, #4a7a2a 0%, #2d5016 100%);
border-color: #b89020;
}
body[data-background="green"] .game-table {
background: linear-gradient(155deg, #4a7c8f 0%, #2d5266 100%);
}
body[data-background="green"] .game-circle {
background: radial-gradient(circle, #5a8fa3 0%, #3d6a7a 100%);
border-color: #7db3c7;
}
body[data-background="blue"] .game-table {
background: linear-gradient(230deg, #76564d 10%, #523c26 90%);
}
body[data-background="blue"] .game-circle {
background: radial-gradient(circle, #8b6b4d 0%, #5d4539 100%);
border-color: #a8856a;
}
body[data-background="purple"] .game-table {
background: linear-gradient(219deg, #826994 10%, #503d6c 90%);
}
body[data-background="purple"] .game-circle {
background: radial-gradient(circle, #9d7bb3 0%, #6d5580 100%);
border-color: #b899d0;
}
body[data-background="orange"] .game-table {
background: linear-gradient(206deg, #8f9861 10%, #847068 90%);
}
body[data-background="orange"] .game-circle {
background: radial-gradient(circle, #a3b075 0%, #7a8254 100%);
border-color: #bfcc8d;
}
/* Рамка карт под каждый фон для дневного режима */
body[data-background="default"] .card,
body:not([data-background]) .card {
border-color: #2d5016;
}
body[data-background="green"] .card {
border-color: #2d5266;
}
body[data-background="blue"] .card {
border-color: #523c26;
}
body[data-background="purple"] .card {
border-color: #503d6c;
}
body[data-background="orange"] .card {
border-color: #6a5d54;
}
+73 -1
View File
@@ -596,7 +596,7 @@ class GameServer:
async def make_bot_move(self, room_id: str, bot_id: str):
"""Бот делает ход"""
await asyncio.sleep(1.5) # Задержка для реалистичности
await asyncio.sleep(random.uniform(0.88, 1.82)) # Задержка для реалистичности
room = self.rooms.get(room_id)
if not room or not room.game_started:
@@ -1738,12 +1738,82 @@ class GameServer:
if not room_id:
return
room = self.rooms[room_id]
# Отправляем реакцию всем игрокам в комнате
await self.broadcast_to_room(room_id, {
'type': 'reaction',
'player_id': player_id,
'emoji': emoji
})
# Если реакция отправлена боту, один случайный бот отвечает
if player_id.startswith('bot_'):
return # Боты не отвечают на реакции других ботов
# Находим всех ботов в комнате
bots = [pid for pid in room.players if pid.startswith('bot_')]
if bots:
# Запускаем ответ бота в отдельной задаче чтобы не блокировать игру
asyncio.create_task(self.bot_reaction_response(room_id, bots))
async def bot_reaction_response(self, room_id: str, bots: list):
"""Бот отвечает на реакцию с задержкой"""
# Выбираем одного случайного бота для ответа
responding_bot = random.choice(bots)
# Список всех эмодзи для реакций
reaction_emojis = ['🙃', '🙁', '😡', '😈', '😇', '😠', '😄', '😎', '🤔', '😤',
'😐', '👍', '👎', '🤝', '🫰', '🪬', '🤯', '🤡', '🤨', '😑',
'😌', '😴', '🌚', '🐱', '🐸', '🐺', '🐊', '🐠', '🌹', '🗿',
'👁️', '💩', '🔩', '🔪', '⚔️', '🎺', '🔮', '🎲', '🎯',
'♥️', '♦️', '♣️', '♠️']
# Выбираем случайный эмодзи
bot_emoji = random.choice(reaction_emojis)
# Отправляем реакцию от бота с небольшой задержкой
await asyncio.sleep(random.uniform(1.7, 3.9))
await self.broadcast_to_room(room_id, {
'type': 'reaction',
'player_id': responding_bot,
'emoji': bot_emoji
})
async def handle_leave_room(self, ws: WebSocketServerProtocol, data: dict):
"""Явный выход из комнаты"""
player_id = self.clients.get(ws)
if not player_id:
return
room_id = self.player_rooms.get(player_id)
if not room_id:
return
room = self.rooms.get(room_id)
if not room or player_id not in room.players:
return
# Удаляем игрока из комнаты
del room.players[player_id]
del self.player_rooms[player_id]
# Если это была приватная комната создателя или комната пустая - удаляем её
if (room.is_private and room.creator_id == player_id) or len(room.players) == 0:
del self.rooms[room_id]
# Удаляем из БД
await self.delete_room_from_db(room_id)
else:
# Уведомляем остальных игроков
await self.broadcast_to_room(room_id, {
'type': 'player_left',
'player_id': player_id
})
# Обновляем список комнат
await self.broadcast_rooms()
async def handle_message(self, ws: WebSocketServerProtocol, message: str):
try:
@@ -1778,6 +1848,8 @@ class GameServer:
await self.handle_shake_discard(ws, data)
elif msg_type == 'reaction':
await self.handle_reaction(ws, data)
elif msg_type == 'leave_room':
await self.handle_leave_room(ws, data)
except json.JSONDecodeError:
await ws.send(json.dumps({'type': 'error', 'message': 'Invalid JSON'}))