Game page

This commit is contained in:
2026-02-03 18:42:12 +03:00
parent 73b20c2c54
commit baeaea69f8
4 changed files with 2876 additions and 914 deletions

591
app.py
View File

@@ -579,12 +579,34 @@ def start_room_game(room_code):
# Меняем статус комнаты # Меняем статус комнаты
room.status = 'playing' room.status = 'playing'
# Создаем начальное состояние игры если его нет
if not room.game_state:
game_state = GameState(
room_id=room.id,
phase='action',
phase_end=datetime.utcnow() + timedelta(seconds=120), # 2 минуты на действия
market_data=json.dumps({
'initialized': True,
'assets': {
'stock_gazprom': {'price': 1000, 'volatility': 0.2},
'real_estate': {'price': 3000000, 'volatility': 0.1},
'bitcoin': {'price': 1850000, 'volatility': 0.3},
'oil': {'price': 5000, 'volatility': 0.25}
},
'last_update': datetime.utcnow().isoformat()
})
)
db.session.add(game_state)
db.session.commit() db.session.commit()
# Отправляем событие через WebSocket # Отправляем событие через WebSocket
socketio.emit('game_started', { socketio.emit('game_started', {
'room': room.code, 'room': room.code,
'message': 'Игра началась!' 'message': 'Игра началась!',
'phase': 'action',
'phase_end': room.game_state.phase_end.isoformat() if room.game_state else None
}, room=room.code) }, room=room.code)
return jsonify({'success': True}) return jsonify({'success': True})
@@ -896,6 +918,67 @@ def delete_room(room_code):
return jsonify({'success': True}) return jsonify({'success': True})
@app.route('/room/<room_code>/exit', methods=['POST'])
@login_required
def exit_lobby(room_code):
"""Выход из лобби комнаты"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
# Получаем игрока
player = GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id
).first()
if not player:
return jsonify({'error': 'Вы не находитесь в этой комнате'}), 400
# Если игрок - последний в комнате, удаляем комнату
player_count = GamePlayer.query.filter_by(room_id=room.id).count()
# Если игрок - администратор и в комнате есть другие игроки
if player.is_admin and player_count > 1:
# Назначаем нового администратора
new_admin = GamePlayer.query.filter_by(
room_id=room.id,
user_id=current_user.id
).first()
if new_admin:
new_admin.is_admin = True
# Удаляем игрока из комнаты
db.session.delete(player)
# Если игрок был последним, удаляем комнату и состояние игры
if player_count == 1:
# Удаляем всех игроков (хотя остался только текущий)
GamePlayer.query.filter_by(room_id=room.id).delete()
# Удаляем состояние игры
game_state = GameState.query.filter_by(room_id=room.id).first()
if game_state:
db.session.delete(game_state)
# Удаляем комнату
db.session.delete(room)
db.session.commit()
# Отправляем WebSocket событие
socketio.emit('player_left', {
'user_id': current_user.id,
'username': current_user.username,
'reason': 'exited'
}, room=room.code)
return jsonify({
'success': True,
'message': 'Вы вышли из лобби',
'room_deleted': player_count == 1
})
@app.route('/api/game/<room_code>/assets') @app.route('/api/game/<room_code>/assets')
@login_required @login_required
def get_assets(room_code): def get_assets(room_code):
@@ -1018,14 +1101,29 @@ def handle_join_room(data):
@socketio.on('leave_room') @socketio.on('leave_room')
def handle_leave_room(data): def handle_leave_room(data):
room_code = data.get('room') room_code = data.get('room')
if room_code: reason = data.get('reason', 'left') # 'left', 'exited', 'kicked'
if room_code and current_user.is_authenticated:
leave_room(room_code) leave_room(room_code)
logger.info(f'User {current_user.username} left room {room_code}') logger.info(f'User {current_user.username} left room {room_code} (reason: {reason})')
messages = {
'left': 'покинул комнату',
'exited': 'вышел из лобби',
'kicked': 'был выгнан'
}
emit('player_left', { emit('player_left', {
'user_id': current_user.id, 'user_id': current_user.id,
'username': current_user.username 'username': current_user.username,
}, room=room_code) 'reason': reason,
'message': f'{current_user.username} {messages.get(reason, "покинул комнату")}',
'timestamp': datetime.utcnow().isoformat()
}, room=room_code, include_self=False)
# Очищаем флаг уведомления в сессии
session_key = f'has_notified_join_{room_code}'
session.pop(session_key, None)
@socketio.on('player_ready') @socketio.on('player_ready')
@@ -1404,6 +1502,489 @@ def utility_processor():
get_ability_description=get_ability_description get_ability_description=get_ability_description
) )
# Логика игры
@app.route('/game/<room_code>')
@login_required
def game(room_code):
"""Страница игры"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
# Проверяем, что игрок в комнате
player = GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id
).first_or_404()
# Проверяем, что игра идет
if room.status != 'playing':
flash('Игра еще не началась или уже завершена', 'warning')
return redirect(url_for('lobby', room_code=room_code))
# Получаем состояние игры
game_state = room.game_state
if not game_state:
# Создаем начальное состояние игры если его нет
game_state = GameState(
room_id=room.id,
market_data=json.dumps({
'initialized': True,
'assets': {
'stock_gazprom': {'price': 1000, 'volatility': 0.2},
'real_estate': {'price': 3000000, 'volatility': 0.1},
'bitcoin': {'price': 1850000, 'volatility': 0.3},
'oil': {'price': 5000, 'volatility': 0.25}
},
'last_update': datetime.utcnow().isoformat()
})
)
db.session.add(game_state)
db.session.commit()
# Получаем всех игроков для лидерборда
players = GamePlayer.query.filter_by(room_id=room.id).all()
# Словарь названий активов для шаблона
asset_names = {
'stock_gazprom': 'Акции Газпрома',
'real_estate': 'Недвижимость',
'bitcoin': 'Биткоин',
'oil': 'Нефть'
}
return render_template('game.html',
room=room,
player=player,
players=players,
game_state=game_state,
asset_names=asset_names)
# API эндпоинты для игры
@app.route('/api/game/<room_code>/state')
@login_required
def get_game_state(room_code):
"""Получение текущего состояния игры"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
player = GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id
).first_or_404()
game_state = room.game_state
return jsonify({
'success': True,
'phase': game_state.phase if game_state else 'action',
'phase_end': game_state.phase_end.isoformat() if game_state and game_state.phase_end else None,
'current_month': room.current_month,
'total_months': room.total_months,
'player_capital': player.capital,
'room_status': room.status
})
@app.route('/api/game/<room_code>/assets')
@login_required
def get_game_assets(room_code):
"""Получение текущих цен активов"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
# Проверяем, что игрок в комнате
GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id
).first_or_404()
game_state = room.game_state
if not game_state:
return jsonify({'success': False, 'error': 'Game state not found'}), 404
market_data = json.loads(game_state.market_data)
return jsonify({
'success': True,
'assets': market_data.get('assets', {})
})
@app.route('/api/game/<room_code>/buy', methods=['POST'])
@login_required
def buy_asset(room_code):
"""Покупка актива"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
player = GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id
).first_or_404()
# Проверяем, что сейчас фаза действий
if room.game_state and room.game_state.phase != 'action':
return jsonify({'success': False, 'error': 'Не фаза действий'}), 400
data = request.json
asset_id = data.get('asset_id')
quantity = data.get('quantity', 1)
price = data.get('price')
if not asset_id or not price:
return jsonify({'success': False, 'error': 'Не указан актив или цена'}), 400
total_cost = price * quantity
if player.capital < total_cost:
return jsonify({'success': False, 'error': 'Недостаточно средств'}), 400
# Получаем текущие активы игрока
assets = json.loads(player.assets) if player.assets else []
# Добавляем новый актив
assets.append({
'id': asset_id,
'quantity': quantity,
'purchase_price': price,
'timestamp': datetime.utcnow().isoformat()
})
# Обновляем капитал и активы игрока
player.capital -= total_cost
player.assets = json.dumps(assets)
db.session.commit()
return jsonify({
'success': True,
'new_capital': player.capital,
'message': f'Куплено {quantity} {asset_id}'
})
@app.route('/api/game/<room_code>/sell', methods=['POST'])
@login_required
def sell_asset(room_code):
"""Продажа актива"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
player = GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id
).first_or_404()
# Проверяем, что сейчас фаза действий
if room.game_state and room.game_state.phase != 'action':
return jsonify({'success': False, 'error': 'Не фаза действий'}), 400
data = request.json
asset_id = data.get('asset_id')
quantity = data.get('quantity', 1)
if not asset_id:
return jsonify({'success': False, 'error': 'Не указан актив'}), 400
# Получаем текущие активы игрока
assets = json.loads(player.assets) if player.assets else []
# Ищем актив для продажи
asset_to_sell = None
for asset in assets:
if asset['id'] == asset_id and asset['quantity'] >= quantity:
asset_to_sell = asset
break
if not asset_to_sell:
return jsonify({'success': False, 'error': 'Недостаточно активов для продажи'}), 400
# Получаем текущую цену актива
game_state = room.game_state
if not game_state:
return jsonify({'success': False, 'error': 'Состояние игры не найдено'}), 404
market_data = json.loads(game_state.market_data)
current_price = market_data.get('assets', {}).get(asset_id, {}).get('price', asset_to_sell['purchase_price'])
# Рассчитываем выручку
revenue = current_price * quantity
# Обновляем активы игрока
asset_to_sell['quantity'] -= quantity
if asset_to_sell['quantity'] <= 0:
assets.remove(asset_to_sell)
# Обновляем капитал
player.capital += revenue
player.assets = json.dumps(assets)
db.session.commit()
return jsonify({
'success': True,
'new_capital': player.capital,
'revenue': revenue,
'message': f'Продано {quantity} {asset_id}'
})
@app.route('/api/game/<room_code>/use_ability', methods=['POST'])
@login_required
def use_ability(room_code):
"""Использование способности"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
player = GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id
).first_or_404()
if not player.ability:
return jsonify({'success': False, 'error': 'У вас нет способности'}), 400
# Здесь должна быть логика применения способности
# Пока просто возвращаем успех
return jsonify({
'success': True,
'ability_name': get_ability_name(player.ability),
'message': 'Способность использована'
})
@app.route('/api/game/<room_code>/end_action_phase', methods=['POST'])
@login_required
def end_action_phase(room_code):
"""Завершение фазы действий"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
player = GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id,
is_admin=True
).first()
if not player:
return jsonify({'success': False, 'error': 'Только администратор может завершать фазы'}), 403
if not room.game_state or room.game_state.phase != 'action':
return jsonify({'success': False, 'error': 'Сейчас не фаза действий'}), 400
# Переходим к следующей фазе
game_state = room.game_state
game_state.phase = 'market'
game_state.phase_end = datetime.utcnow() + timedelta(seconds=30) # 30 секунд на реакцию рынка
db.session.commit()
# Отправляем WebSocket событие
socketio.emit('game_phase_changed', {
'room': room.code,
'phase': 'market',
'phase_end': game_state.phase_end.isoformat(),
'message': 'Фаза действий завершена, начинается реакция рынка'
}, room=room.code)
# Запускаем расчет рынка
socketio.start_background_task(calculate_and_update_market, room.id)
return jsonify({'success': True})
def calculate_and_update_market(room_id):
"""Расчет реакции рынка (в фоновом потоке)"""
import time
# Ждем 5 секунд перед расчетом
time.sleep(5)
room = GameRoom.query.get(room_id)
if not room or room.status != 'playing':
return
game_state = room.game_state
if not game_state or game_state.phase != 'market':
return
# Получаем всех игроков
players = GamePlayer.query.filter_by(room_id=room_id).all()
# Получаем текущие данные рынка
market_data = json.loads(game_state.market_data) if game_state.market_data else {}
assets = market_data.get('assets', {})
# Базовые цены для расчета перегрева
base_prices = {
'stock_gazprom': 1000,
'real_estate': 3000000,
'bitcoin': 1850000,
'oil': 5000
}
# Считаем спрос по каждому активу
for asset_id in assets.keys():
demand = 0
for player in players:
player_assets = json.loads(player.assets) if player.assets else []
for player_asset in player_assets:
if player_asset.get('id') == asset_id:
demand += player_asset.get('quantity', 0)
# Формула влияния спроса
total_players = len(players)
if total_players > 0 and asset_id in assets:
volatility = assets[asset_id].get('volatility', 0.2)
price_change = volatility * (demand / (total_players * 10))
new_price = assets[asset_id]['price'] * (1 + price_change)
# Эффект перегрева
if base_prices.get(asset_id) and new_price > base_prices[asset_id] * (1 + 2 * volatility):
new_price *= random.uniform(0.7, 0.9)
assets[asset_id]['price'] = new_price
# Обновляем данные рынка
market_data['assets'] = assets
market_data['last_update'] = datetime.utcnow().isoformat()
game_state.market_data = json.dumps(market_data)
db.session.commit()
# Отправляем обновление через WebSocket
socketio.emit('market_updated', {
'room': room.code,
'assets': assets,
'timestamp': datetime.utcnow().isoformat()
}, room=room.code)
# Через 25 секунд переходим к событиям
time.sleep(25)
# Проверяем, что мы все еще в фазе рынка
room = GameRoom.query.get(room_id)
if room and room.game_state and room.game_state.phase == 'market':
game_state = room.game_state
game_state.phase = 'event'
game_state.phase_end = datetime.utcnow() + timedelta(seconds=30)
db.session.commit()
socketio.emit('game_phase_changed', {
'room': room.code,
'phase': 'event',
'phase_end': game_state.phase_end.isoformat(),
'message': 'Реакция рынка завершена, начинаются случайные события'
}, room=room.code)
@app.route('/api/game/<room_code>/leaderboard')
@login_required
def get_game_leaderboard(room_code):
"""Получение таблицы лидеров"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
# Проверяем, что игрок в комнате
GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id
).first_or_404()
# Получаем всех игроков
players = GamePlayer.query.filter_by(room_id=room.id).all()
players_data = []
for player in players:
user = User.query.get(player.user_id)
players_data.append({
'user_id': player.user_id,
'username': user.username if user else 'Игрок',
'capital': player.capital,
'ability': player.ability,
'is_admin': player.is_admin
})
return jsonify({
'success': True,
'players': players_data
})
# WebSocket обработчики для игры
@socketio.on('join_game_room')
def handle_join_game_room(data):
"""Присоединение к комнате игры"""
room_code = data.get('room')
if room_code and current_user.is_authenticated:
join_room(room_code)
logger.info(f'User {current_user.username} joined game room {room_code}')
emit('player_joined_game', {
'user_id': current_user.id,
'username': current_user.username,
'timestamp': datetime.utcnow().isoformat()
}, room=room_code, include_self=False)
@socketio.on('leave_game_room')
def handle_leave_game_room(data):
"""Выход из игровой комнаты"""
room_code = data.get('room')
reason = data.get('reason', 'left')
if room_code and current_user.is_authenticated:
leave_room(room_code)
logger.info(f'User {current_user.username} left game room {room_code} (reason: {reason})')
emit('player_left_game', {
'user_id': current_user.id,
'username': current_user.username,
'reason': reason,
'timestamp': datetime.utcnow().isoformat()
}, room=room_code, include_self=False)
@socketio.on('game_chat_message')
def handle_game_chat_message(data):
"""Обработка сообщений в игровом чате"""
room_code = data.get('room')
message = data.get('message', '').strip()
if message and room_code and current_user.is_authenticated:
emit('game_chat_message', {
'user_id': current_user.id,
'username': current_user.username,
'message': message,
'timestamp': datetime.utcnow().isoformat()
}, room=room_code)
@socketio.on('player_bought_asset')
def handle_player_bought_asset(data):
"""Игрок купил актив"""
room_code = data.get('room')
if room_code:
emit('player_action', {
'player_id': current_user.id,
'player_name': current_user.username,
'action': 'buy',
'asset_id': data.get('asset_id'),
'quantity': data.get('quantity'),
'price': data.get('price'),
'timestamp': datetime.utcnow().isoformat()
}, room=room_code, include_self=False)
@socketio.on('player_used_ability')
def handle_player_used_ability(data):
"""Игрок использовал способность"""
room_code = data.get('room')
if room_code:
emit('player_action', {
'player_id': current_user.id,
'player_name': current_user.username,
'action': 'ability',
'ability': data.get('ability'),
'timestamp': datetime.utcnow().isoformat()
}, room=room_code, include_self=False)
# --- ТОЧКА ВХОДА --- # --- ТОЧКА ВХОДА ---
if __name__ == '__main__': if __name__ == '__main__':

View File

@@ -62,6 +62,22 @@
socket.on('player_left', function(data) { socket.on('player_left', function(data) {
console.log('Player left:', data.username); console.log('Player left:', data.username);
}); });
socket.on('player_joined_game', function(data) {
console.log('Player joined game:', data.username);
});
socket.on('player_left_game', function(data) {
console.log('Player left game:', data.username);
});
socket.on('player_action', function(data) {
console.log('Player action in game:', data);
});
socket.on('game_chat_message', function(data) {
console.log('Game chat message:', data);
});
</script> </script>
{% block scripts %}{% endblock %} {% block scripts %}{% endblock %}

File diff suppressed because it is too large Load Diff

View File

@@ -30,7 +30,9 @@
<h3 style="margin-bottom: 5px;">{{ room.name }}</h3> <h3 style="margin-bottom: 5px;">{{ room.name }}</h3>
<div style="color: var(--light-text); font-size: 0.9rem;"> <div style="color: var(--light-text); font-size: 0.9rem;">
Код комнаты: <strong>{{ room.code }}</strong> Код комнаты: <strong>{{ room.code }}</strong>
<button onclick="copyRoomCode()" style="background: none; border: none; color: var(--primary-color); cursor: pointer; margin-left: 5px;" title="Скопировать код"> <button onclick="copyRoomCode()"
style="background: none; border: none; color: var(--primary-color); cursor: pointer; margin-left: 5px;"
title="Скопировать код">
📋 📋
</button> </button>
</div> </div>
@@ -129,7 +131,8 @@
{% endif %} {% endif %}
{% if room.status == 'waiting' %} {% if room.status == 'waiting' %}
<button id="ready-btn" onclick="toggleReady()" class="button {% if current_player.is_ready %}success{% else %}secondary{% endif %}"> <button id="ready-btn" onclick="toggleReady()"
class="button {% if current_player.is_ready %}success{% else %}secondary{% endif %}">
{% if current_player.is_ready %} {% if current_player.is_ready %}
✅ Готов ✅ Готов
{% else %} {% else %}
@@ -153,6 +156,12 @@
{% endif %} {% endif %}
</div> </div>
<div style="margin-top: 10px;">
<button onclick="exitLobby()" class="button danger" style="width: 100%;">
🚪 Выйти из лобби
</button>
</div>
<!-- Индикатор готовности --> <!-- Индикатор готовности -->
{% if room.status == 'waiting' %} {% if room.status == 'waiting' %}
<div style="margin-top: 15px; padding: 10px; background-color: #f5f5f5; border-radius: var(--border-radius);"> <div style="margin-top: 15px; padding: 10px; background-color: #f5f5f5; border-radius: var(--border-radius);">
@@ -183,7 +192,8 @@
{% for player in players %} {% for player in players %}
<li class="player-item"> <li class="player-item">
<div class="player-info"> <div class="player-info">
<div class="player-avatar" style="background-color: {% if player.is_admin %}#ff9800{% else %}var(--primary-color){% endif %};"> <div class="player-avatar"
style="background-color: {% if player.is_admin %}#ff9800{% else %}var(--primary-color){% endif %};">
{{ player.user.username[0]|upper if player.user and player.user.username else '?' }} {{ player.user.username[0]|upper if player.user and player.user.username else '?' }}
</div> </div>
<div> <div>
@@ -230,7 +240,8 @@
<!-- Чат комнаты --> <!-- Чат комнаты -->
<div class="card"> <div class="card">
<h3>💬 Чат комнаты</h3> <h3>💬 Чат комнаты</h3>
<div id="chat-messages" style="height: 200px; overflow-y: auto; margin-bottom: 10px; padding: 10px; background-color: #f8f9fa; border-radius: var(--border-radius);"> <div id="chat-messages"
style="height: 200px; overflow-y: auto; margin-bottom: 10px; padding: 10px; background-color: #f8f9fa; border-radius: var(--border-radius);">
<div style="text-align: center; color: var(--light-text); padding: 20px;"> <div style="text-align: center; color: var(--light-text); padding: 20px;">
Чат загружается... Чат загружается...
</div> </div>
@@ -250,11 +261,14 @@
<h3>📊 Статистика комнаты</h3> <h3>📊 Статистика комнаты</h3>
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px; margin-top: 15px;"> <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="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);">{{ players|length }}</div> <div style="font-size: 2rem; font-weight: bold; color: var(--primary-color);">{{ players|length }}
</div>
<div style="font-size: 0.9rem; color: var(--light-text);">Игроков</div> <div style="font-size: 0.9rem; color: var(--light-text);">Игроков</div>
</div> </div>
<div style="text-align: center; padding: 15px; background-color: #f5f5f5; border-radius: var(--border-radius);"> <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);">{{ room.current_month }}</div> <div style="font-size: 2rem; font-weight: bold; color: var(--primary-color);">{{ room.current_month
}}
</div>
<div style="font-size: 0.9rem; color: var(--light-text);">Текущий месяц</div> <div style="font-size: 0.9rem; color: var(--light-text);">Текущий месяц</div>
</div> </div>
<div style="text-align: center; padding: 15px; background-color: #f5f5f5; border-radius: var(--border-radius);"> <div style="text-align: center; padding: 15px; background-color: #f5f5f5; border-radius: var(--border-radius);">
@@ -364,11 +378,13 @@
<div id="qrcode" style="text-align: center; margin: 15px 0;"> <div id="qrcode" style="text-align: center; margin: 15px 0;">
<!-- QR-код будет сгенерирован JavaScript --> <!-- QR-код будет сгенерирован JavaScript -->
<div style="background-color: white; padding: 10px; display: inline-block; border-radius: var(--border-radius);"> <div style="background-color: white; padding: 10px; display: inline-block; border-radius: var(--border-radius);">
<div style="font-size: 0.8rem; color: var(--light-text); margin-bottom: 5px;">Отсканируйте QR-код</div> <div style="font-size: 0.8rem; color: var(--light-text); margin-bottom: 5px;">Отсканируйте QR-код
</div>
<div style="width: 150px; height: 150px; background-color: #f0f0f0; display: flex; align-items: center; justify-content: center; border-radius: 5px;"> <div style="width: 150px; height: 150px; background-color: #f0f0f0; display: flex; align-items: center; justify-content: center; border-radius: 5px;">
<div style="text-align: center;"> <div style="text-align: center;">
<div style="font-size: 3rem;">📱</div> <div style="font-size: 3rem;">📱</div>
<div style="font-size: 0.7rem; color: var(--light-text); margin-top: 5px;">{{ room.code }}</div> <div style="font-size: 0.7rem; color: var(--light-text); margin-top: 5px;">{{ room.code }}
</div>
</div> </div>
</div> </div>
</div> </div>
@@ -386,12 +402,14 @@
<div style="margin: 15px 0;"> <div style="margin: 15px 0;">
<h4>Управление игроками:</h4> <h4>Управление игроками:</h4>
<div id="admin-player-list" style="max-height: 200px; overflow-y: auto; margin: 10px 0; padding: 10px; background-color: #f5f5f5; border-radius: var(--border-radius);"> <div id="admin-player-list"
style="max-height: 200px; overflow-y: auto; margin: 10px 0; padding: 10px; background-color: #f5f5f5; border-radius: var(--border-radius);">
{% for player in players %} {% for player in players %}
{% if not player.is_admin %} {% if not player.is_admin %}
<div style="display: flex; justify-content: space-between; align-items: center; padding: 8px; border-bottom: 1px solid #ddd;"> <div style="display: flex; justify-content: space-between; align-items: center; padding: 8px; border-bottom: 1px solid #ddd;">
<span>{{ player.user.username if player.user else 'Игрок' }}</span> <span>{{ player.user.username if player.user else 'Игрок' }}</span>
<button onclick="kickPlayer({{ player.user_id }})" class="button danger" style="padding: 5px 10px; font-size: 0.8rem;"> <button onclick="kickPlayer({{ player.user_id }})" class="button danger"
style="padding: 5px 10px; font-size: 0.8rem;">
Выгнать Выгнать
</button> </button>
</div> </div>
@@ -428,6 +446,7 @@ let currentPlayerId = '{{ current_player.id }}';
let isAdmin = {{ 'true' if current_player.is_admin else 'false' }}; let isAdmin = {{ 'true' if current_player.is_admin else 'false' }};
let lastNotificationTime = 0; // Для ограничения частоты уведомлений let lastNotificationTime = 0; // Для ограничения частоты уведомлений
window.roomStatus = '{{ room.status }}'; // Добавьте эту строку window.roomStatus = '{{ room.status }}'; // Добавьте эту строку
window.userExitedLobby = false;
// Функция форматирования валюты // Функция форматирования валюты
function formatCurrency(amount) { function formatCurrency(amount) {
@@ -614,15 +633,15 @@ function startGame() {
'X-CSRFToken': '{{ csrf_token() if csrf_token else "" }}' 'X-CSRFToken': '{{ csrf_token() if csrf_token else "" }}'
} }
}) })
.then(response => { .then(response => response.json())
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => { .then(data => {
if (data.success) { if (data.success) {
showNotification('✅ Игра начинается...', 'success'); showNotification('✅ Игра начинается!', 'success');
// Ждем 2 секунды и перенаправляем в игру
setTimeout(() => {
window.location.href = '/game/{{ room.code }}';
}, 2000);
} else { } else {
showNotification('❌ Ошибка: ' + (data.error || 'Неизвестная ошибка'), 'error'); showNotification('❌ Ошибка: ' + (data.error || 'Неизвестная ошибка'), 'error');
} }
@@ -1011,10 +1030,15 @@ if (chatMessages) {
}); });
} }
window.addEventListener('beforeunload', function() { window.addEventListener('beforeunload', function(e) {
// Только если WebSocket подключен и мы в комнате
if (typeof socket !== 'undefined' && socket.connected && roomCode) { if (typeof socket !== 'undefined' && socket.connected && roomCode) {
// Не показываем подтверждение, если пользователь сам нажал выход
if (!window.userExitedLobby) {
// Отправляем событие выхода
socket.emit('leave_room', { room: roomCode }); socket.emit('leave_room', { room: roomCode });
} }
}
}); });
// Обработка видимости страницы (если переключились на другую вкладку) // Обработка видимости страницы (если переключились на другую вкладку)
@@ -1109,6 +1133,49 @@ setInterval(() => {
socket.connect(); socket.connect();
} }
}, 5000); }, 5000);
function exitLobby() {
if (!confirm('Вы уверены, что хотите выйти из лобби?')) {
return;
}
window.userExitedLobby = true;
fetch('/room/{{ room.code }}/exit', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': '{{ csrf_token() if csrf_token else "" }}'
}
})
.then(response => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
if (data.success) {
showNotification('✅ Вы вышли из лобби', 'success');
// Отправляем WebSocket событие если подключены
if (typeof socket !== 'undefined' && socket.connected) {
socket.emit('leave_room', { room: roomCode });
}
// Редирект через 1 секунду
setTimeout(() => {
window.location.href = '{{ url_for("rooms") }}';
}, 1000);
} else {
showNotification('❌ Ошибка: ' + (data.error || 'Неизвестная ошибка'), 'error');
}
})
.catch(error => {
console.error('Error:', error);
showNotification('❌ Ошибка при выходе из лобби', 'error');
});
}
</script> </script>
<style> <style>
@@ -1232,5 +1299,68 @@ setInterval(() => {
padding: 15px; padding: 15px;
} }
} }
.button.danger {
background-color: var(--danger-color);
color: white;
border: none;
}
.button.danger:hover {
background-color: #d32f2f;
transform: translateY(-2px);
}
.button.danger:active {
transform: translateY(0);
}
.exit-confirmation {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.7);
display: flex;
align-items: center;
justify-content: center;
z-index: 10000;
display: none;
}
.exit-confirmation.active {
display: flex;
}
.exit-modal {
background-color: white;
padding: 25px;
border-radius: var(--border-radius);
max-width: 400px;
width: 90%;
text-align: center;
box-shadow: 0 5px 25px rgba(0, 0, 0, 0.3);
}
.exit-modal h3 {
color: var(--danger-color);
margin-bottom: 15px;
}
.exit-modal p {
margin-bottom: 20px;
color: var(--text-color);
line-height: 1.5;
}
.exit-buttons {
display: flex;
gap: 10px;
margin-top: 20px;
}
.exit-buttons .button {
flex: 1;
}
</style> </style>
{% endblock %} {% endblock %}