This commit is contained in:
2026-02-02 19:18:25 +03:00
commit 038b307d70
21 changed files with 4987 additions and 0 deletions

52
templates/403.html Normal file
View File

@@ -0,0 +1,52 @@
{% extends "base.html" %}
{% block title %}Доступ запрещён - Капитал & Рынок{% endblock %}
{% block screen_content %}
<div class="header">
<a href="{{ url_for('rooms') }}" class="back-button"></a>
<div class="logo-container">
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Капитал & Рынок" class="logo">
</div>
</div>
<div class="container">
<div class="card text-center">
<div style="font-size: 5rem; color: var(--warning-color); margin: 20px 0;">403</div>
<h2>Доступ запрещён</h2>
<p>У вас недостаточно прав для доступа к этой странице.</p>
<div style="margin: 30px 0;">
<div style="font-size: 8rem; color: #f0f0f0; margin-bottom: 20px;">🔒💰</div>
<p style="color: var(--light-text); font-size: 0.9rem;">
Эта комната может быть приватной или игра уже началась.
</p>
</div>
<div class="flex flex-col gap-2 mt-4">
{% if current_user.is_authenticated %}
<a href="{{ url_for('rooms') }}" class="button">
Вернуться к списку комнат
</a>
{% else %}
<a href="{{ url_for('login') }}" class="button">
Войти в систему
</a>
{% endif %}
<a href="{{ url_for('index') }}" class="button secondary">
На главную
</a>
</div>
</div>
<div class="card">
<h3>Возможные причины:</h3>
<ul style="padding-left: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">Вы не авторизованы в системе</li>
<li style="margin-bottom: 8px;">У вас нет прав для доступа к этой комнате</li>
<li style="margin-bottom: 8px;">Игра уже началась и присоединение невозможно</li>
<li>Комната является приватной</li>
</ul>
</div>
</div>
{% endblock %}

46
templates/404.html Normal file
View File

@@ -0,0 +1,46 @@
{% extends "base.html" %}
{% block title %}Страница не найдена - Капитал & Рынок{% endblock %}
{% block screen_content %}
<div class="header">
<a href="{{ url_for('rooms') }}" class="back-button"></a>
<div class="logo-container">
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Капитал & Рынок" class="logo">
</div>
</div>
<div class="container">
<div class="card text-center">
<div style="font-size: 5rem; color: var(--primary-color); margin: 20px 0;">404</div>
<h2>Страница не найдена</h2>
<p>К сожалению, запрашиваемая страница не существует или была перемещена.</p>
<div style="margin: 30px 0;">
<div style="font-size: 8rem; color: #f0f0f0; margin-bottom: 20px;">💼📉</div>
<p style="color: var(--light-text); font-size: 0.9rem;">
Возможно, комната была закрыта или игра завершена.
</p>
</div>
<div class="flex flex-col gap-2 mt-4">
<a href="{{ url_for('rooms') }}" class="button">
Вернуться к списку комнат
</a>
<a href="{{ url_for('index') }}" class="button secondary">
На главную
</a>
</div>
</div>
<div class="card">
<h3>Что можно сделать?</h3>
<ul style="padding-left: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">Проверьте правильность URL-адреса</li>
<li style="margin-bottom: 8px;">Создайте новую игровую комнату</li>
<li style="margin-bottom: 8px;">Присоединитесь к другой комнате</li>
<li>Обратитесь к администратору, если это ошибка</li>
</ul>
</div>
</div>
{% endblock %}

74
templates/500.html Normal file
View File

@@ -0,0 +1,74 @@
{% extends "base.html" %}
{% block title %}Ошибка сервера - Капитал & Рынок{% endblock %}
{% block screen_content %}
<div class="header">
<a href="{{ url_for('rooms') }}" class="back-button"></a>
<div class="logo-container">
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Капитал & Рынок" class="logo">
</div>
</div>
<div class="container">
<div class="card text-center">
<div style="font-size: 5rem; color: var(--danger-color); margin: 20px 0;">500</div>
<h2>Ошибка сервера</h2>
<p>Произошла внутренняя ошибка сервера. Мы уже работаем над её устранением.</p>
<div style="margin: 30px 0;">
<div style="font-size: 8rem; color: #f0f0f0; margin-bottom: 20px;">💥📊</div>
<p style="color: var(--light-text); font-size: 0.9rem;">
Рынок временно не работает. Пожалуйста, попробуйте позже.
</p>
</div>
<div class="flex flex-col gap-2 mt-4">
<button onclick="window.location.reload()" class="button">
Попробовать снова
</button>
<a href="{{ url_for('rooms') }}" class="button secondary">
Вернуться к списку комнат
</a>
<a href="{{ url_for('index') }}" class="button">
На главную
</a>
</div>
</div>
<div class="card">
<h3>Техническая информация</h3>
<p style="margin-top: 10px; font-size: 0.9rem; color: var(--light-text);">
Если ошибка повторяется, пожалуйста, свяжитесь с администратором:
</p>
<div style="background-color: #f8f9fa; padding: 15px; border-radius: var(--border-radius); margin-top: 15px; font-family: monospace; font-size: 0.85rem;">
<div>Время ошибки: <span id="error-time">{{ now.strftime('%Y-%m-%d %H:%M:%S') if now else '' }}</span></div>
<div>Путь: <span id="error-path">{{ request.path if request else '' }}</span></div>
</div>
<div style="margin-top: 20px;">
<button onclick="copyErrorInfo()" class="button secondary">
Скопировать информацию об ошибке
</button>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function copyErrorInfo() {
const errorInfo = `Ошибка 500\nПуть: ${document.getElementById('error-path').textContent}\nВремя: ${document.getElementById('error-time').textContent}\nUser Agent: ${navigator.userAgent}`;
navigator.clipboard.writeText(errorInfo).then(() => {
alert('Информация об ошибке скопирована в буфер обмена');
});
}
// Автоматическая перезагрузка через 30 секунд
setTimeout(() => {
console.log('Пытаемся перезагрузить страницу через 30 секунд после ошибки...');
}, 30000);
</script>
{% endblock %}

69
templates/base.html Normal file
View File

@@ -0,0 +1,69 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{% block title %}Капитал & Рынок{% endblock %}</title>
<!-- Статические файлы -->
<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">
<link rel="icon" href="{{ url_for('static', filename='images/logo.png') }}">
<!-- WebSocket -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.5.4/socket.io.min.js"></script>
{% block head %}{% endblock %}
</head>
<body>
<!-- Уведомления -->
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
<div class="flash-messages">
{% for category, message in messages %}
<div class="flash-message {{ category }}">
{{ message }}
<button class="flash-close" onclick="this.parentElement.remove()">×</button>
</div>
{% endfor %}
</div>
{% endif %}
{% endwith %}
<!-- Контент страницы -->
{% block content %}{% endblock %}
<!-- Общие скрипты -->
<script src="{{ url_for('static', filename='js/main.js') }}"></script>
<!-- Инициализация WebSocket -->
<script>
// Подключение к WebSocket
const socket = io();
// Базовая обработка подключения
socket.on('connect', function() {
console.log('Connected to server');
});
socket.on('disconnect', function() {
console.log('Disconnected from server');
});
// Глобальные обработчики событий
socket.on('chat_message', function(data) {
// Обработка сообщений чата
console.log('New message:', data);
});
socket.on('player_joined', function(data) {
console.log('Player joined:', data.username);
});
socket.on('player_left', function(data) {
console.log('Player left:', data.username);
});
</script>
{% block scripts %}{% endblock %}
</body>
</html>

40
templates/game.html Normal file
View File

@@ -0,0 +1,40 @@
<!DOCTYPE html>
<html lang="ru">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Капитал & Рынок - Игра</title>
<link rel="stylesheet" href="css/style.css">
</head>
<body>
<div class="screen active">
<div class="header">
<a href="lobby.html" class="back-button" onclick="return confirmExit()"></a>
<div class="logo-container">
<img src="logo.png" alt="Капитал & Рынок" class="logo">
</div>
<span id="game-month">Месяц 1</span>
</div>
<div class="container">
<!-- Остальной код игрового экрана (табы, карточки и т.д.) -->
<!-- Используем тот же HTML из предыдущей версии, но разбиваем на отдельные страницы -->
<!-- Полный код смотри в предыдущем сообщении -->
</div>
</div>
<script src="js/main.js"></script>
<script src="js/game.js"></script>
<script>
// Подтверждение выхода из игры
function confirmExit() {
return confirm('Вы уверены, что хотите выйти из игры? Текущий прогресс может быть потерян.');
}
// Инициализация игры
document.addEventListener('DOMContentLoaded', function() {
initGame();
});
</script>
</body>
</html>

210
templates/index.html Normal file
View File

@@ -0,0 +1,210 @@
{% extends "base.html" %}
{% block title %}Капитал & Рынок - Экономическая стратегия{% endblock %}
{% block content %}
<div class="screen active">
<!-- Шапка -->
<div class="header">
<div class="logo-container">
{% if url_for('static', filename='images/logo.png') %}
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Капитал & Рынок" class="logo"
onerror="this.style.display='none'; this.nextElementSibling.style.display='block';">
<span class="logo-text" style="display: none; color: white; font-weight: bold; font-size: 1.2rem;">💰 Капитал & Рынок</span>
{% else %}
<span class="logo-text" style="color: white; font-weight: bold; font-size: 1.2rem;">💰 Капитал & Рынок</span>
{% endif %}
</div>
</div>
<!-- Основной контент -->
<div class="container">
<!-- Приветственная карточка -->
<div class="card text-center">
<h1 style="margin-bottom: 15px;">Капитал & Рынок</h1>
<p style="margin: 15px 0; font-size: 1.1rem; color: var(--light-text);">
Стань успешным инвестором в динамичной экономической стратегии!
</p>
<div style="margin: 25px 0;">
<div style="font-size: 4rem; margin-bottom: 20px;">💰📈🏦</div>
</div>
<div class="flex flex-col gap-3" style="max-width: 300px; margin: 0 auto;">
<a href="{{ url_for('login') }}" class="button" style="font-size: 1.1rem; padding: 15px;">
Войти в игру
</a>
<a href="{{ url_for('register') }}" class="button success" style="font-size: 1.1rem; padding: 15px;">
Создать аккаунт
</a>
<a href="{{ url_for('quick_login', username='Гость') }}" class="button secondary" style="padding: 12px;">
Быстрый старт (гостевая игра)
</a>
</div>
</div>
<!-- Особенности игры -->
<div class="card">
<h3 style="text-align: center; margin-bottom: 20px;">Особенности игры</h3>
<div style="display: grid; grid-template-columns: 1fr; gap: 15px;">
<div style="display: flex; align-items: flex-start; gap: 15px; padding: 15px; background-color: #f8f9fa; border-radius: var(--border-radius);">
<div style="font-size: 2rem; flex-shrink: 0;">🎮</div>
<div>
<h4 style="margin-bottom: 5px;">Динамичный геймплей</h4>
<p style="font-size: 0.9rem; color: var(--light-text); margin: 0;">
Каждый месяц - новые решения, события и вызовы. Адаптируйтесь к меняющемуся рынку!
</p>
</div>
</div>
<div style="display: flex; align-items: flex-start; gap: 15px; padding: 15px; background-color: #f8f9fa; border-radius: var(--border-radius);">
<div style="font-size: 2rem; flex-shrink: 0;">👥</div>
<div>
<h4 style="margin-bottom: 5px;">Мультиплеер до 10 игроков</h4>
<p style="font-size: 0.9rem; color: var(--light-text); margin: 0;">
Соревнуйтесь с друзьями или случайными соперниками. Создавайте альянсы и заключайте сделки!
</p>
</div>
</div>
<div style="display: flex; align-items: flex-start; gap: 15px; padding: 15px; background-color: #f8f9fa; border-radius: var(--border-radius);">
<div style="font-size: 2rem; flex-shrink: 0;">💡</div>
<div>
<h4 style="margin-bottom: 5px;">13 уникальных способностей</h4>
<p style="font-size: 0.9rem; color: var(--light-text); margin: 0;">
Каждый игрок получает особую способность: от Кризисного инвестора до Теневого бухгалтера.
</p>
</div>
</div>
<div style="display: flex; align-items: flex-start; gap: 15px; padding: 15px; background-color: #f8f9fa; border-radius: var(--border-radius);">
<div style="font-size: 2rem; flex-shrink: 0;">📊</div>
<div>
<h4 style="margin-bottom: 5px;">Реалистичная экономика</h4>
<p style="font-size: 0.9rem; color: var(--light-text); margin: 0;">
Рынок реагирует на действия всех игроков. Ваши решения влияют на цены активов!
</p>
</div>
</div>
</div>
</div>
<!-- Как начать играть -->
<div class="card">
<h3 style="text-align: center; margin-bottom: 20px;">Как начать играть?</h3>
<div style="counter-reset: step-counter;">
<div style="display: flex; align-items: flex-start; gap: 15px; margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid #eee;">
<div style="background-color: var(--primary-color); color: white; width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-weight: bold;">
1
</div>
<div>
<h4 style="margin-bottom: 5px;">Создайте аккаунт</h4>
<p style="font-size: 0.95rem; color: var(--light-text); margin: 0;">
Зарегистрируйтесь или войдите как гость. Это займет менее минуты!
</p>
</div>
</div>
<div style="display: flex; align-items: flex-start; gap: 15px; margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid #eee;">
<div style="background-color: var(--primary-color); color: white; width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-weight: bold;">
2
</div>
<div>
<h4 style="margin-bottom: 5px;">Присоединитесь к комнате</h4>
<p style="font-size: 0.95rem; color: var(--light-text); margin: 0;">
Выберите существующую комнату или создайте свою. Настройте правила игры.
</p>
</div>
</div>
<div style="display: flex; align-items: flex-start; gap: 15px; margin-bottom: 20px; padding-bottom: 20px; border-bottom: 1px solid #eee;">
<div style="background-color: var(--primary-color); color: white; width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-weight: bold;">
3
</div>
<div>
<h4 style="margin-bottom: 5px;">Выберите способность</h4>
<p style="font-size: 0.95rem; color: var(--light-text); margin: 0;">
Получите случайную уникальную способность и стартовый капитал 100,000 ₽.
</p>
</div>
</div>
<div style="display: flex; align-items: flex-start; gap: 15px;">
<div style="background-color: var(--primary-color); color: white; width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-weight: bold;">
4
</div>
<div>
<h4 style="margin-bottom: 5px;">Станьте самым богатым!</h4>
<p style="font-size: 0.95rem; color: var(--light-text); margin: 0;">
Инвестируйте в акции, недвижимость, бизнес. Обыграйте конкурентов за 12 месяцев!
</p>
</div>
</div>
</div>
</div>
<!-- Статистика (заглушка) -->
<div class="card text-center">
<h3 style="margin-bottom: 15px;">Игровая статистика</h3>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-top: 20px;">
<div style="padding: 15px; background-color: #f0f8ff; border-radius: var(--border-radius);">
<div style="font-size: 2rem; font-weight: bold; color: var(--primary-color);">1,234</div>
<div style="font-size: 0.9rem; color: var(--light-text);">Активных игроков</div>
</div>
<div style="padding: 15px; background-color: #f0f8ff; border-radius: var(--border-radius);">
<div style="font-size: 2rem; font-weight: bold; color: var(--primary-color);">567</div>
<div style="font-size: 0.9rem; color: var(--light-text);">Игровых комнат</div>
</div>
<div style="padding: 15px; background-color: #f0f8ff; border-radius: var(--border-radius);">
<div style="font-size: 2rem; font-weight: bold; color: var(--primary-color);">89</div>
<div style="font-size: 0.9rem; color: var(--light-text);">Турниров</div>
</div>
<div style="padding: 15px; background-color: #f0f8ff; border-radius: var(--border-radius);">
<div style="font-size: 2rem; font-weight: bold; color: var(--primary-color);">12</div>
<div style="font-size: 0.9rem; color: var(--light-text);">Уникальных способностей</div>
</div>
</div>
</div>
<!-- Футер -->
<div style="text-align: center; padding: 20px 0; color: var(--light-text); font-size: 0.9rem;">
<p>© 2024 Капитал & Рынок. Экономическая стратегия в реальном времени.</p>
<p style="margin-top: 10px;">
<a href="#" style="color: var(--primary-color); text-decoration: none; margin: 0 10px;">Правила</a> |
<a href="#" style="color: var(--primary-color); text-decoration: none; margin: 0 10px;">Контакты</a> |
<a href="#" style="color: var(--primary-color); text-decoration: none; margin: 0 10px;">Поддержка</a>
</p>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
// Простая анимация для привлечения внимания
document.addEventListener('DOMContentLoaded', function() {
const emojis = document.querySelector('.text-center .flex');
if (emojis) {
emojis.style.opacity = '0';
emojis.style.transform = 'translateY(20px)';
setTimeout(() => {
emojis.style.transition = 'opacity 0.5s ease, transform 0.5s ease';
emojis.style.opacity = '1';
emojis.style.transform = 'translateY(0)';
}, 300);
}
// Подсветка кнопок при наведении
const buttons = document.querySelectorAll('.button');
buttons.forEach(button => {
button.addEventListener('mouseenter', function() {
this.style.transform = 'translateY(-2px)';
});
button.addEventListener('mouseleave', function() {
this.style.transform = 'translateY(0)';
});
});
});
</script>
{% endblock %}

1085
templates/lobby.html Normal file

File diff suppressed because it is too large Load Diff

114
templates/login.html Normal file
View File

@@ -0,0 +1,114 @@
{% extends "base.html" %}
{% block title %}Вход - Капитал & Рынок{% endblock %}
{% block screen_content %}
<div class="header">
<div class="logo-container">
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Капитал & Рынок" class="logo">
</div>
</div>
<div class="container">
<div class="card">
<h2>Вход в игру</h2>
<form method="POST" action="{{ url_for('login') }}" class="auth-form">
<div class="input-group">
<label for="username">Имя пользователя</label>
<input type="text" id="username" name="username"
placeholder="Введите ваш никнейм"
value="{{ request.form.username if request.form }}"
required autofocus>
</div>
<div class="input-group">
<label for="password">Пароль</label>
<input type="password" id="password" name="password"
placeholder="Введите пароль" required>
</div>
<div class="input-group" style="flex-direction: row; align-items: center; gap: 10px;">
<input type="checkbox" id="remember" name="remember" checked>
<label for="remember" style="margin: 0;">Запомнить меня</label>
</div>
<button type="submit" class="button">Войти</button>
<div class="auth-links">
<a href="{{ url_for('register') }}">Создать аккаунт</a>
<a href="#" onclick="showQuickLogin()">Быстрый вход (тест)</a>
</div>
</form>
</div>
<div class="card">
<h3>Тестовые аккаунты</h3>
<p style="margin-bottom: 15px; color: var(--light-text); font-size: 0.9rem;">
Для быстрого тестирования игры:
</p>
<div class="flex flex-col gap-2">
<a href="{{ url_for('quick_login', username='Игрок1') }}" class="button secondary">
Войти как Игрок1
</a>
<a href="{{ url_for('quick_login', username='Игрок2') }}" class="button secondary">
Войти как Игрок2
</a>
<a href="{{ url_for('quick_login', username='Инвестор') }}" class="button secondary">
Войти как Инвестор
</a>
</div>
</div>
</div>
<!-- Модальное окно быстрого входа -->
<div id="quick-login-modal" class="modal-backdrop">
<div class="modal">
<h3>Быстрый вход</h3>
<p>Введите имя для быстрого входа (будет создан аккаунт если его нет):</p>
<div class="input-group mt-3">
<input type="text" id="quick-username" placeholder="Имя пользователя" style="width: 100%;">
</div>
<div class="flex gap-2 mt-4">
<button onclick="doQuickLogin()" class="button success">Войти</button>
<button onclick="hideQuickLogin()" class="button secondary">Отмена</button>
</div>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function showQuickLogin() {
event.preventDefault();
document.getElementById('quick-login-modal').classList.add('active');
document.getElementById('quick-username').focus();
}
function hideQuickLogin() {
document.getElementById('quick-login-modal').classList.remove('active');
}
function doQuickLogin() {
const username = document.getElementById('quick-username').value.trim();
if (username) {
window.location.href = `/quick_login/${encodeURIComponent(username)}`;
} else {
alert('Введите имя пользователя');
}
}
// Автозаполнение формы для теста
document.addEventListener('DOMContentLoaded', function() {
// Если это демо-версия, можно автозаполнить форму
const urlParams = new URLSearchParams(window.location.search);
if (urlParams.get('demo') === '1') {
document.getElementById('username').value = 'test_user';
document.getElementById('password').value = 'test123';
}
});
</script>
{% endblock %}

178
templates/register.html Normal file
View File

@@ -0,0 +1,178 @@
{% extends "base.html" %}
{% block title %}Регистрация - Капитал & Рынок{% endblock %}
{% block screen_content %}
<div class="header">
<a href="{{ url_for('login') }}" class="back-button"></a>
<div class="logo-container">
<img src="{{ url_for('static', filename='images/logo.png') }}" alt="Капитал & Рынок" class="logo">
</div>
</div>
<div class="container">
<div class="card">
<h2>Создание аккаунта</h2>
<form method="POST" action="{{ url_for('register') }}" class="auth-form">
<div class="input-group">
<label for="username">Имя пользователя *</label>
<input type="text" id="username" name="username"
placeholder="Придумайте никнейм (мин. 3 символа)"
value="{{ request.form.username if request.form }}"
required autofocus>
<small style="color: var(--light-text); font-size: 0.85rem;">
Будет отображаться другим игрокам
</small>
</div>
<div class="input-group">
<label for="email">Email *</label>
<input type="email" id="email" name="email"
placeholder="Ваш email"
value="{{ request.form.email if request.form }}"
required>
<small style="color: var(--light-text); font-size: 0.85rem;">
Только для восстановления пароля
</small>
</div>
<div class="input-group">
<label for="password">Пароль *</label>
<input type="password" id="password" name="password"
placeholder="Придумайте пароль (мин. 4 символа)" required>
</div>
<div class="input-group">
<label for="password2">Повторите пароль *</label>
<input type="password" id="password2" name="password2"
placeholder="Повторите пароль" required>
</div>
<div class="input-group mt-2">
<div style="background-color: #f5f5f5; padding: 10px; border-radius: var(--border-radius);">
<label style="display: flex; align-items: flex-start; gap: 10px; cursor: pointer;">
<input type="checkbox" name="terms" required style="margin-top: 3px;">
<span style="font-size: 0.9rem;">
Я согласен с <a href="#" onclick="showTerms()">правилами игры</a> и
<a href="#" onclick="showPrivacy()">политикой конфиденциальности</a>
</span>
</label>
</div>
</div>
<button type="submit" class="button success mt-3">Зарегистрироваться</button>
<div class="auth-links">
<a href="{{ url_for('login') }}">Уже есть аккаунт? Войти</a>
</div>
</form>
</div>
<div class="card">
<h3>Почему стоит зарегистрироваться?</h3>
<ul style="padding-left: 20px; margin-top: 10px;">
<li style="margin-bottom: 8px;">🎮 Сохраняйте прогресс в играх</li>
<li style="margin-bottom: 8px;">📊 Отслеживайте статистику и рейтинг</li>
<li style="margin-bottom: 8px;">👥 Создавайте приватные комнаты</li>
<li style="margin-bottom: 8px;">🏆 Участвуйте в турнирах и соревнованиях</li>
<li>💬 Общайтесь с другими игроками</li>
</ul>
</div>
</div>
<!-- Модальные окна с правилами -->
<div id="terms-modal" class="modal-backdrop">
<div class="modal" style="max-width: 600px;">
<h3>Правила игры "Капитал & Рынок"</h3>
<div style="max-height: 400px; overflow-y: auto; padding-right: 10px;">
<h4>1. Основные правила</h4>
<p>Игра проходит в реальном времени, каждый месяц длится 2-5 минут.</p>
<h4>2. Поведение игроков</h4>
<p>Запрещены оскорбления, нецензурная лексика, мошенничество.</p>
<h4>3. Использование способностей</h4>
<p>Способности можно использовать согласно их описанию и ограничениям.</p>
<h4>4. Определение победителя</h4>
<p>Победителем становится игрок с наибольшим капиталом после 12 месяцев.</p>
<h4>5. Дисквалификация</h4>
<p>Администратор может дисквалифицировать игрока за нарушение правил.</p>
</div>
<button onclick="hideTerms()" class="button mt-3">Понятно</button>
</div>
</div>
<div id="privacy-modal" class="modal-backdrop">
<div class="modal" style="max-width: 600px;">
<h3>Политика конфиденциальности</h3>
<div style="max-height: 400px; overflow-y: auto; padding-right: 10px;">
<h4>1. Собираемые данные</h4>
<p>Мы собираем только необходимые данные: имя пользователя, email (опционально), статистику игр.</p>
<h4>2. Использование данных</h4>
<p>Данные используются только для работы игры: идентификации, статистики, рейтингов.</p>
<h4>3. Безопасность</h4>
<p>Пароли хранятся в зашифрованном виде. Мы не передаем ваши данные третьим лицам.</p>
<h4>4. Cookies</h4>
<p>Используем только технические cookies для работы сессий.</p>
</div>
<button onclick="hidePrivacy()" class="button mt-3">Понятно</button>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
function showTerms() {
event.preventDefault();
document.getElementById('terms-modal').classList.add('active');
}
function hideTerms() {
document.getElementById('terms-modal').classList.remove('active');
}
function showPrivacy() {
event.preventDefault();
document.getElementById('privacy-modal').classList.add('active');
}
function hidePrivacy() {
document.getElementById('privacy-modal').classList.remove('active');
}
// Валидация формы
document.querySelector('form').addEventListener('submit', function(e) {
const password = document.getElementById('password').value;
const password2 = document.getElementById('password2').value;
const terms = document.querySelector('input[name="terms"]');
if (password !== password2) {
e.preventDefault();
alert('Пароли не совпадают!');
document.getElementById('password2').focus();
return false;
}
if (password.length < 4) {
e.preventDefault();
alert('Пароль должен быть не менее 4 символов!');
document.getElementById('password').focus();
return false;
}
if (!terms.checked) {
e.preventDefault();
alert('Необходимо согласиться с правилами игры!');
return false;
}
return true;
});
</script>
{% endblock %}

568
templates/rooms.html Normal file
View File

@@ -0,0 +1,568 @@
{% extends "base.html" %}
{% block title %}Комнаты - Капитал & Рынок{% endblock %}
{% block content %}
<div class="screen active">
<!-- Шапка -->
<div class="header">
<a href="{{ url_for('index') }}" class="back-button"></a>
<div class="logo-container">
<span class="logo-text" style="color: white; font-weight: bold; font-size: 1.2rem;">
💰 Комнаты
</span>
</div>
<div style="margin-left: auto;">
<span style="font-size: 0.9rem;">
{% if current_user.is_authenticated %}
{{ current_user.username }}
{% endif %}
</span>
</div>
</div>
<!-- Основной контент -->
<div class="container">
<!-- Поиск и создание комнаты -->
<div class="search-bar">
<input type="text" id="room-search" placeholder="Поиск комнат по названию..."
onkeyup="searchRooms()">
<button onclick="searchRooms()">🔍</button>
</div>
<button class="button" onclick="createRoom()" style="margin-bottom: 20px;">
Создать новую комнату
</button>
<!-- Доступные комнаты -->
<div class="card">
<h3>📢 Доступные комнаты</h3>
<div style="margin: 10px 0; color: var(--light-text); font-size: 0.9rem;">
{% if rooms %}
Найдено {{ rooms|length }} комнат
{% else %}
Нет доступных комнат
{% endif %}
</div>
<ul class="room-list" id="room-list">
{% if rooms %}
{% for room in rooms %}
<li class="room-item" onclick="joinRoom('{{ room.code }}')">
<div class="room-info">
<div class="room-avatar"
style="background-color: {% if room.status == 'waiting' %}#4caf50{% elif room.status == 'playing' %}#ff9800{% else %}#9e9e9e{% endif %};">
{{ room.player_count }}
</div>
<div style="flex: 1;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<strong>{{ room.name }}</strong>
<span style="font-size: 0.8rem; color: var(--light-text);">
Месяц {{ room.current_month }}/{{ room.total_months }}
</span>
</div>
<div class="room-meta">
Создатель: {{ room.creator.username if room.creator else 'Система' }} •
Игроков: {{ room.player_count }}/{{ config.MAX_PLAYERS_PER_ROOM }}
</div>
{% if room.settings %}
<div style="margin-top: 3px;">
{% set settings = room.settings|from_json %}
{% if settings.allow_loans %}
<span style="background-color: #e3f2fd; color: #1976d2; padding: 2px 6px; border-radius: 10px; font-size: 0.75rem; margin-right: 5px;">Кредиты</span>
{% endif %}
{% if settings.allow_black_market %}
<span style="background-color: #f3e5f5; color: #7b1fa2; padding: 2px 6px; border-radius: 10px; font-size: 0.75rem;">Чёрный рынок</span>
{% endif %}
</div>
{% endif %}
</div>
</div>
<div class="room-status {{ room.status }}">
{% if room.status == 'waiting' %}
Ожидание
{% elif room.status == 'playing' %}
Игра идет
{% elif room.status == 'full' %}
Заполнена
{% else %}
{{ room.status }}
{% endif %}
</div>
</li>
{% endfor %}
{% else %}
<li style="text-align: center; padding: 20px; color: var(--light-text);">
<div style="font-size: 3rem; margin-bottom: 10px;">🏢</div>
<p>Пока нет доступных комнат</p>
<p style="font-size: 0.9rem;">Создайте первую комнату!</p>
</li>
{% endif %}
</ul>
</div>
<!-- Ваши комнаты -->
<div class="card">
<h3>⭐ Ваши комнаты</h3>
{% if user_rooms %}
<ul class="room-list" id="my-room-list">
{% for room in user_rooms %}
<li class="room-item" onclick="joinRoom('{{ room.code }}')">
<div class="room-info">
<div class="room-avatar" style="background-color: var(--primary-color);">
{{ room.player_count }}
</div>
<div style="flex: 1;">
<div style="display: flex; justify-content: space-between; align-items: center;">
<strong>{{ room.name }}</strong>
{% if room.creator_id == current_user.id %}
<span style="background-color: #fff3e0; color: #ef6c00; padding: 2px 8px; border-radius: 10px; font-size: 0.75rem;">Админ</span>
{% endif %}
</div>
<div class="room-meta">
Месяц {{ room.current_month }}/{{ room.total_months }} •
Игроков: {{ room.player_count }}/{{ config.MAX_PLAYERS_PER_ROOM }}
</div>
</div>
</div>
<div class="room-status {{ room.status }}">
{% if room.status == 'waiting' %}
Ожидание
{% elif room.status == 'playing' %}
В игре
{% else %}
{{ room.status }}
{% endif %}
</div>
</li>
{% endfor %}
</ul>
{% else %}
<div style="text-align: center; padding: 20px; color: var(--light-text);">
<div style="font-size: 3rem; margin-bottom: 10px;">👤</div>
<p>Вы пока не участвуете в комнатах</p>
<p style="font-size: 0.9rem;">Присоединитесь к существующей или создайте свою</p>
</div>
{% endif %}
</div>
<!-- Быстрые действия -->
<div class="card">
<h3>⚡ Быстрые действия</h3>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin-top: 15px;">
<button class="button secondary" onclick="createQuickRoom('Быстрая игра')">
Быстрая игра
</button>
<button class="button secondary" onclick="createQuickRoom('Для новичков')">
Для новичков
</button>
<button class="button secondary" onclick="createQuickRoom('Турнир')">
Турнир
</button>
<button class="button secondary" onclick="createQuickRoom('С друзьями')">
С друзьями
</button>
</div>
</div>
<!-- Статистика -->
{% if current_user.is_authenticated %}
<div class="card">
<h3>📊 Ваша статистика</h3>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-top: 15px;">
<div style="text-align: center; padding: 15px; background-color: #f5f5f5; border-radius: var(--border-radius);">
<div style="font-size: 2rem; font-weight: bold; color: var(--primary-color);">{{
current_user.total_games }}
</div>
<div style="font-size: 0.9rem; color: var(--light-text);">Всего игр</div>
</div>
<div style="text-align: center; padding: 15px; background-color: #f5f5f5; border-radius: var(--border-radius);">
<div style="font-size: 2rem; font-weight: bold; color: var(--primary-color);">{{
current_user.games_won }}
</div>
<div style="font-size: 0.9rem; color: var(--light-text);">Побед</div>
</div>
<div style="text-align: center; padding: 15px; background-color: #f5f5f5; border-radius: var(--border-radius);">
<div style="font-size: 2rem; font-weight: bold; color: var(--primary-color);">
{% if current_user.total_games > 0 %}
{{ "%.1f"|format(current_user.games_won / current_user.total_games * 100) }}%
{% else %}
0%
{% endif %}
</div>
<div style="font-size: 0.9rem; color: var(--light-text);">Процент побед</div>
</div>
<div style="text-align: center; padding: 15px; background-color: #f5f5f5; border-radius: var(--border-radius);">
<div style="font-size: 2rem; font-weight: bold; color: var(--primary-color);">
{{ current_user.total_earnings|format_currency }}
</div>
<div style="font-size: 0.9rem; color: var(--light-text);">Заработано</div>
</div>
</div>
</div>
{% endif %}
</div>
</div>
<!-- Модальное окно создания комнаты -->
<div id="create-room-modal" class="modal-backdrop">
<div class="modal">
<h3>Создание комнаты</h3>
<form id="create-room-form" onsubmit="return submitRoomForm(event)">
<div class="input-group">
<label for="room-name">Название комнаты *</label>
<input type="text" id="room-name" name="name"
placeholder="Например: Быки и медведи" required>
</div>
<div class="input-group">
<label for="total-months">Длительность игры</label>
<select id="total-months" name="total_months">
<option value="6">6 месяцев (быстрая игра)</option>
<option value="12" selected>12 месяцев (стандартная)</option>
<option value="18">18 месяцев (продвинутая)</option>
<option value="24">24 месяца (экспертная)</option>
</select>
</div>
<div class="input-group">
<label for="start-capital">Стартовый капитал</label>
<input type="number" id="start-capital" name="start_capital"
value="100000" min="50000" max="1000000" step="10000">
</div>
<div class="input-group">
<label style="display: flex; align-items: center; gap: 10px;">
<input type="checkbox" id="allow-loans" name="allow_loans" checked>
<span>Разрешить кредиты</span>
</label>
</div>
<div class="input-group">
<label style="display: flex; align-items: center; gap: 10px;">
<input type="checkbox" id="allow-black-market" name="allow_black_market">
<span>Разрешить чёрный рынок</span>
</label>
</div>
<div class="input-group">
<label style="display: flex; align-items: center; gap: 10px;">
<input type="checkbox" id="private-room" name="private_room">
<span>Приватная комната (по приглашению)</span>
</label>
</div>
<div class="flex gap-2 mt-4">
<button type="submit" class="button success">Создать комнату</button>
<button type="button" onclick="hideCreateRoomModal()" class="button secondary">Отмена</button>
</div>
</form>
</div>
</div>
{% endblock %}
{% block scripts %}
<script>
// Глобальные переменные
let allRooms = [];
// Инициализация при загрузке
document.addEventListener('DOMContentLoaded', function() {
console.log('Rooms page loaded');
// Сохраняем все комнаты для поиска
const roomItems = document.querySelectorAll('.room-item');
allRooms = Array.from(roomItems).map(item => ({
name: item.querySelector('strong')?.textContent.toLowerCase() || '',
element: item
}));
// Подключение к комнате через WebSocket
if (typeof socket !== 'undefined') {
console.log('Socket connected, joining global room');
socket.emit('join_global_room');
}
});
// Поиск комнат
function searchRooms() {
const searchTerm = document.getElementById('room-search').value.toLowerCase();
const roomList = document.getElementById('room-list');
if (!roomList) return;
const rooms = roomList.querySelectorAll('.room-item');
rooms.forEach(room => {
const roomName = room.querySelector('strong')?.textContent.toLowerCase() || '';
if (roomName.includes(searchTerm) || searchTerm === '') {
room.style.display = 'flex';
} else {
room.style.display = 'none';
}
});
}
// Создание комнаты
function createRoom() {
console.log('Opening create room modal');
const modal = document.getElementById('create-room-modal');
if (modal) {
modal.classList.add('active');
document.getElementById('room-name').focus();
}
}
// Быстрое создание комнаты
function createQuickRoom(name) {
console.log('Creating quick room:', name);
// Создаем форму данных
const formData = new FormData();
formData.append('name', name);
formData.append('total_months', '12');
formData.append('start_capital', '100000');
formData.append('allow_loans', 'on');
formData.append('allow_black_market', 'off');
formData.append('private_room', 'off');
// Отправляем запрос
createRoomRequest(formData);
}
// Отправка формы создания комнаты
function submitRoomForm(event) {
console.log('Submitting room form');
event.preventDefault();
const form = document.getElementById('create-room-form');
const formData = new FormData(form);
createRoomRequest(formData);
return false;
}
// Функция для отправки запроса на создание комнаты
function createRoomRequest(formData) {
console.log('Sending create room request');
fetch('/room/create', {
method: 'POST',
body: formData,
headers: {
'Accept': 'application/json',
}
})
.then(response => {
console.log('Response status:', response.status);
console.log('Response headers:', response.headers.get('content-type'));
// Проверяем тип ответа
const contentType = response.headers.get('content-type');
if (contentType && contentType.includes('application/json')) {
return response.json();
} else {
return response.text().then(text => {
console.error('Expected JSON, got:', text.substring(0, 200));
throw new Error('Server returned HTML instead of JSON');
});
}
})
.then(data => {
console.log('Create room response:', data);
if (data.error) {
alert('Ошибка: ' + data.error);
} else if (data.redirect) {
// Редирект в лобби комнаты
window.location.href = data.redirect;
} else if (data.success) {
// Редирект по коду комнаты
window.location.href = `/room/${data.room_code}`;
} else {
alert('Неизвестный ответ от сервера');
}
})
.catch(error => {
console.error('Error creating room:', error);
alert('Ошибка при создании комнаты. Проверьте консоль для деталей.');
});
}
// Скрыть модальное окно
function hideCreateRoomModal() {
const modal = document.getElementById('create-room-modal');
if (modal) {
modal.classList.remove('active');
}
}
// Присоединение к комнате
function joinRoom(roomCode) {
console.log('Joining room:', roomCode);
window.location.href = `/room/${roomCode}`;
}
// Обновление статуса онлайн
function updateOnlineStatus() {
if (typeof socket !== 'undefined' && socket.connected) {
socket.emit('user_online', {
timestamp: new Date().toISOString()
});
}
}
// Периодическое обновление статуса
setInterval(updateOnlineStatus, 30000);
updateOnlineStatus();
// Обработка клавиши Escape для закрытия модального окна
document.addEventListener('keydown', function(event) {
if (event.key === 'Escape') {
hideCreateRoomModal();
}
});
// Закрытие модального окна при клике на фон
document.addEventListener('click', function(event) {
const modal = document.getElementById('create-room-modal');
if (modal && modal.classList.contains('active') && event.target === modal) {
hideCreateRoomModal();
}
});
</script>
<style>
/* Дополнительные стили для rooms.html */
.room-item {
cursor: pointer;
transition: all 0.3s ease;
border-radius: var(--border-radius);
margin: 5px 0;
padding: 12px;
border: 1px solid transparent;
}
.room-item:hover {
background-color: #f5f5f5;
border-color: var(--primary-color);
transform: translateX(5px);
}
.room-avatar {
width: 40px;
height: 40px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-weight: bold;
font-size: 1.1rem;
margin-right: 12px;
flex-shrink: 0;
}
.room-status {
padding: 5px 10px;
border-radius: 15px;
font-size: 0.8rem;
font-weight: bold;
text-transform: uppercase;
white-space: nowrap;
}
.room-status.waiting {
background-color: #e3f2fd;
color: #1976d2;
}
.room-status.playing {
background-color: #fff3e0;
color: #ef6c00;
}
.room-status.full {
background-color: #ffebee;
color: #d32f2f;
}
.search-bar {
display: flex;
gap: 10px;
margin-bottom: 15px;
}
.search-bar input {
flex: 1;
padding: 12px;
border: 1px solid #ddd;
border-radius: var(--border-radius);
font-size: 1rem;
}
.search-bar button {
padding: 0 20px;
background-color: var(--primary-color);
color: white;
border: none;
border-radius: var(--border-radius);
cursor: pointer;
font-size: 1.1rem;
transition: background-color 0.3s;
}
.search-bar button:hover {
background-color: #0077b3;
}
.modal-backdrop {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-backdrop.active {
display: flex;
}
.modal {
background-color: white;
border-radius: var(--border-radius);
padding: 20px;
max-width: 500px;
width: 90%;
max-height: 90vh;
overflow-y: auto;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.2);
}
@media (max-width: 480px) {
.room-item {
flex-direction: column;
align-items: flex-start;
}
.room-status {
align-self: flex-end;
margin-top: 10px;
}
.search-bar {
flex-direction: column;
}
.search-bar button {
width: 100%;
padding: 12px;
}
}
</style>
{% endblock %}