Files
cm/app.py
2026-02-03 18:42:12 +03:00

2000 lines
71 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import os
import json
from datetime import datetime, timedelta
from flask import Flask, render_template, request, jsonify, session, redirect, url_for, flash
from flask_sqlalchemy import SQLAlchemy
from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user
from flask_socketio import SocketIO, emit, join_room, leave_room, send
from config import config
import random
import logging
from werkzeug.security import generate_password_hash, check_password_hash
# Настройка логирования
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)
# Инициализация Flask
app = Flask(__name__)
app.config.from_object(config)
# Инициализация расширений
db = SQLAlchemy(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
socketio = SocketIO(app, cors_allowed_origins="*")
# --- МОДЕЛИ БАЗЫ ДАННЫХ ---
class GameRoom(db.Model):
__tablename__ = 'game_rooms'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(100), nullable=False)
code = db.Column(db.String(10), unique=True, nullable=False)
creator_id = db.Column(db.Integer, db.ForeignKey('users.id'))
created_at = db.Column(db.DateTime, default=datetime.utcnow)
status = db.Column(db.String(20), default='waiting') # waiting, playing, finished
current_month = db.Column(db.Integer, default=1)
total_months = db.Column(db.Integer, default=12)
start_capital = db.Column(db.Integer, default=100000)
settings = db.Column(db.Text, default='{}') # JSON с настройками
# Связи - убираем lazy='dynamic' для players
players = db.relationship('GamePlayer', backref='room', lazy='select') # Изменено
game_state = db.relationship('GameState', backref='room', uselist=False)
class GamePlayer(db.Model):
__tablename__ = 'game_players'
id = db.Column(db.Integer, primary_key=True)
user_id = db.Column(db.Integer, db.ForeignKey('users.id'))
room_id = db.Column(db.Integer, db.ForeignKey('game_rooms.id'))
joined_at = db.Column(db.DateTime, default=datetime.utcnow)
is_ready = db.Column(db.Boolean, default=False)
is_admin = db.Column(db.Boolean, default=False)
# Игровые данные
capital = db.Column(db.Float, default=100000)
ability = db.Column(db.String(50))
assets = db.Column(db.Text, default='[]') # JSON с активами
position = db.Column(db.Integer, default=0) # Позиция в рейтинге
class User(UserMixin, db.Model):
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), unique=True, nullable=False)
email = db.Column(db.String(120), unique=True, nullable=False)
password_hash = db.Column(db.String(256))
created_at = db.Column(db.DateTime, default=datetime.utcnow)
last_seen = db.Column(db.DateTime, default=datetime.utcnow)
# Статистика
total_games = db.Column(db.Integer, default=0)
games_won = db.Column(db.Integer, default=0)
total_earnings = db.Column(db.Float, default=0)
is_active = db.Column(db.Boolean, default=True)
# Связи
game_players = db.relationship('GamePlayer', backref='user', lazy='dynamic')
rooms_created = db.relationship('GameRoom', backref='creator', lazy='dynamic')
def set_password(self, password):
"""Установка хеша пароля"""
self.password_hash = generate_password_hash(password)
def check_password(self, password):
"""Проверка пароля"""
if not self.password_hash:
return False
return check_password_hash(self.password_hash, password)
def __repr__(self):
return f'<User {self.username}>'
class GameState(db.Model):
__tablename__ = 'game_states'
id = db.Column(db.Integer, primary_key=True)
room_id = db.Column(db.Integer, db.ForeignKey('game_rooms.id'), unique=True)
phase = db.Column(db.String(20), default='action') # action, market, event, results
phase_end = db.Column(db.DateTime)
market_data = db.Column(db.Text, default='{}') # JSON с данными рынка
events = db.Column(db.Text, default='[]') # JSON с событиями
history = db.Column(db.Text, default='[]') # JSON с историей
updated_at = db.Column(db.DateTime, default=datetime.utcnow)
# --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ---
def generate_room_code():
"""Генерация уникального кода комнаты"""
import string
characters = string.ascii_uppercase + string.digits
return ''.join(random.choice(characters) for _ in range(6))
def calculate_market_changes(room_id):
"""Расчет изменений на рынке (по формулам из концепции)"""
room = GameRoom.query.get(room_id)
players = GamePlayer.query.filter_by(room_id=room_id).all()
# Пример расчета (упрощенный)
market_data = json.loads(room.game_state.market_data) if room.game_state else {}
# Активы и их базовые цены
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}
}
# Считаем спрос по каждому активу
for asset_id, asset_data in assets.items():
demand = 0
for player in players:
player_assets = json.loads(player.assets)
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:
price_change = asset_data['volatility'] * (demand / (total_players * 10))
new_price = asset_data['price'] * (1 + price_change)
# Эффект перегрева
if new_price > asset_data['price'] * (1 + 2 * asset_data['volatility']):
new_price *= random.uniform(0.7, 0.9)
assets[asset_id]['price'] = new_price
# Случайные события
events = [
('boom_oil', 'Бум нефти', {'oil': 1.3}, 'positive'),
('cyber_attack', 'Кибератака', {'bitcoin': 0.5}, 'negative'),
('elections', 'Выборы президента', {'all': 0.95}, 'neutral'),
('sanctions', 'Санкции', {'stock_gazprom': 0.65, 'oil': 0.8}, 'negative')
]
event_name, event_description, event_effects, event_type = random.choice(events)
# Применяем эффекты события
for asset_id, multiplier in event_effects.items():
if asset_id == 'all':
for key in assets.keys():
assets[key]['price'] *= multiplier
elif asset_id in assets:
assets[asset_id]['price'] *= multiplier
return {
'assets': assets,
'event': {
'name': event_name,
'description': event_description,
'type': event_type,
'effects': event_effects
},
'timestamp': datetime.utcnow().isoformat()
}
# --- FLASK-LOGIN ---
@login_manager.user_loader
def load_user(user_id):
return User.query.get(int(user_id))
# --- РОУТЫ ---
# Главная страница теперь будет index.html с описанием
@app.route('/')
def index():
return render_template('index.html')
# Старый маршрут index теперь перенаправляет на главную
@app.route('/home')
def home():
return redirect(url_for('index'))
@app.route('/login', methods=['GET', 'POST'])
def login():
if current_user.is_authenticated:
return redirect(url_for('rooms'))
if request.method == 'POST':
username = request.form.get('username', '').strip()
password = request.form.get('password', '')
remember = request.form.get('remember', False)
user = User.query.filter_by(username=username).first()
if user and user.check_password(password):
if not user.is_active:
flash('Аккаунт заблокирован', 'error')
else:
login_user(user, remember=remember)
user.last_seen = datetime.utcnow()
db.session.commit()
flash(f'Добро пожаловать, {username}!', 'success')
# Редирект на следующую страницу или rooms
next_page = request.args.get('next')
return redirect(next_page or url_for('rooms'))
else:
flash('Неверное имя пользователя или пароль', 'error')
return render_template('login.html')
@app.route('/quick_login/<username>')
def quick_login(username):
"""Быстрый вход для тестирования (только для разработки!)"""
user = User.query.filter_by(username=username).first()
if user:
login_user(user)
flash(f'Быстрый вход как {username}', 'info')
return redirect(url_for('rooms'))
# Если пользователя нет, создаем его
user = User(
username=username,
email=f'{username}@test.com',
is_active=True
)
user.set_password('test123')
db.session.add(user)
db.session.commit()
login_user(user)
flash(f'Создан и вошли как {username}', 'success')
return redirect(url_for('rooms'))
@app.route('/register', methods=['GET', 'POST'])
def register():
if current_user.is_authenticated:
return redirect(url_for('rooms'))
if request.method == 'POST':
username = request.form.get('username', '').strip()
email = request.form.get('email', '').strip()
password = request.form.get('password', '')
password2 = request.form.get('password2', '')
# Простая валидация
errors = []
if not username:
errors.append('Введите имя пользователя')
elif len(username) < 3:
errors.append('Имя пользователя должно быть не менее 3 символов')
elif User.query.filter_by(username=username).first():
errors.append('Имя пользователя уже занято')
if not email:
errors.append('Введите email')
elif '@' not in email:
errors.append('Введите корректный email')
elif User.query.filter_by(email=email).first():
errors.append('Email уже используется')
if not password:
errors.append('Введите пароль')
elif len(password) < 4:
errors.append('Пароль должен быть не менее 4 символов')
elif password != password2:
errors.append('Пароли не совпадают')
if errors:
for error in errors:
flash(error, 'error')
return render_template('register.html')
try:
# Создаем пользователя
user = User(
username=username,
email=email
)
user.set_password(password)
db.session.add(user)
db.session.commit()
login_user(user, remember=True)
flash('Регистрация успешна! Добро пожаловать!', 'success')
return redirect(url_for('rooms'))
except Exception as e:
db.session.rollback()
flash(f'Ошибка при регистрации: {str(e)}', 'error')
return render_template('register.html')
@app.route('/logout')
@login_required
def logout():
logout_user()
return redirect(url_for('index'))
@app.route('/rooms')
@login_required
def rooms():
"""Страница списка комнат"""
try:
# Получаем все активные комнаты
all_rooms = GameRoom.query.filter(
GameRoom.status.in_(['waiting', 'playing'])
).order_by(GameRoom.created_at.desc()).all()
# Для каждой комнаты получаем количество игроков
rooms_with_counts = []
for room in all_rooms:
# Получаем количество игроков
player_count = GamePlayer.query.filter_by(room_id=room.id).count()
# Получаем создателя
creator = User.query.get(room.creator_id) if room.creator_id else None
# Добавляем комнату с дополнительной информацией
rooms_with_counts.append({
'id': room.id,
'name': room.name,
'code': room.code,
'status': room.status,
'creator': creator,
'creator_id': room.creator_id,
'current_month': room.current_month,
'total_months': room.total_months,
'start_capital': room.start_capital,
'settings': room.settings,
'player_count': player_count,
'players': [] # Пустой список, так как не загружаем всех игроков
})
# Комнаты текущего пользователя
user_rooms = []
user_room_ids = GamePlayer.query.filter_by(
user_id=current_user.id
).with_entities(GamePlayer.room_id).all()
user_room_ids = [rid for (rid,) in user_room_ids]
for room_data in rooms_with_counts:
if room_data['id'] in user_room_ids:
user_rooms.append(room_data)
print(f"Found {len(rooms_with_counts)} rooms, user in {len(user_rooms)} rooms")
return render_template('rooms.html',
rooms=rooms_with_counts,
user_rooms=user_rooms,
config=app.config)
except Exception as e:
print(f"Error in rooms route: {str(e)}")
import traceback
traceback.print_exc()
db.session.rollback()
flash(f'Ошибка при загрузке комнат: {str(e)}', 'error')
return render_template('rooms.html',
rooms=[],
user_rooms=[],
config=app.config)
@app.route('/room/create', methods=['POST'])
@login_required
def create_room():
"""Создание новой комнаты"""
try:
# Получаем данные из формы
room_name = request.form.get('name', 'Новая комната').strip()
total_months = int(request.form.get('total_months', 12))
start_capital = int(request.form.get('start_capital', 100000))
allow_loans = request.form.get('allow_loans') == 'on'
allow_black_market = request.form.get('allow_black_market') == 'on'
private_room = request.form.get('private_room') == 'on'
# Валидация
if not room_name:
return jsonify({'error': 'Введите название комнаты'}), 400
# Генерируем уникальный код комнаты
import string
import random
def generate_room_code():
chars = string.ascii_uppercase + string.digits
return ''.join(random.choice(chars) for _ in range(6))
room_code = generate_room_code()
# Создаем комнату
room = GameRoom(
name=room_name,
code=room_code,
creator_id=current_user.id,
total_months=total_months,
start_capital=start_capital,
settings=json.dumps({
'allow_loans': allow_loans,
'allow_black_market': allow_black_market,
'private_room': private_room
})
)
db.session.add(room)
db.session.commit()
# Добавляем создателя как игрока-администратора
player = GamePlayer(
user_id=current_user.id,
room_id=room.id,
is_admin=True,
is_ready=True,
capital=start_capital,
ability=random.choice([
'crisis_investor', 'lobbyist', 'predictor',
'golden_pillow', 'shadow_accountant', 'credit_magnate'
])
)
db.session.add(player)
db.session.commit()
# Создаем начальное состояние игры
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()
# Отправляем успешный ответ
return jsonify({
'success': True,
'room_code': room.code,
'redirect': url_for('lobby', room_code=room.code)
})
except Exception as e:
db.session.rollback()
print(f"Error creating room: {str(e)}")
import traceback
traceback.print_exc()
return jsonify({'error': f'Ошибка при создании комнаты: {str(e)}'}), 500
@app.route('/room/<room_code>')
@login_required
def lobby(room_code):
"""Лобби комнаты"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
# Получаем всех игроков комнаты
players = GamePlayer.query.filter_by(room_id=room.id).all()
# Получаем информацию о пользователях для игроков
players_with_users = []
for player in players:
user = User.query.get(player.user_id)
player.user = user # Добавляем объект пользователя к игроку
players_with_users.append(player)
# Проверяем, есть ли текущий пользователь в комнате
current_player = GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id
).first()
# Если игрока нет и комната в ожидании, добавляем его
if not current_player and room.status == 'waiting':
# Проверяем, есть ли место в комнате
if len(players) >= app.config['MAX_PLAYERS_PER_ROOM']:
flash('Комната заполнена', 'error')
return redirect(url_for('rooms'))
# Случайная способность
import random
abilities = [
'crisis_investor', 'lobbyist', 'predictor',
'golden_pillow', 'shadow_accountant', 'credit_magnate',
'bear_raid', 'fake_news', 'dividend_king',
'raider_capture', 'mafia_connections', 'economic_advisor',
'currency_speculator'
]
current_player = GamePlayer(
user_id=current_user.id,
room_id=room.id,
is_admin=False,
is_ready=False,
capital=room.start_capital,
ability=random.choice(abilities)
)
db.session.add(current_player)
db.session.commit()
players_with_users.append(current_player)
flash(f'Вы присоединились к комнате "{room.name}"', 'success')
elif not current_player:
flash('Вы не можете присоединиться к этой комнате', 'error')
return redirect(url_for('rooms'))
# Получаем создателя комнаты
creator = User.query.get(room.creator_id) if room.creator_id else None
return render_template('lobby.html',
room=room,
players=players_with_users,
current_player=current_player,
creator=creator,
config=app.config)
@app.route('/room/<room_code>/start', methods=['POST'])
@login_required
def start_room_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,
is_admin=True
).first()
if not player:
return jsonify({'error': 'Только администратор может начать игру'}), 403
if room.status != 'waiting':
return jsonify({'error': 'Игра уже начата или завершена'}), 400
# Проверяем минимальное количество игроков
player_count = GamePlayer.query.filter_by(room_id=room.id).count()
if player_count < 2:
return jsonify({'error': 'Нужно минимум 2 игрока для начала игры'}), 400
# Меняем статус комнаты
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()
# Отправляем событие через WebSocket
socketio.emit('game_started', {
'room': room.code,
'message': 'Игра началась!',
'phase': 'action',
'phase_end': room.game_state.phase_end.isoformat() if room.game_state else None
}, room=room.code)
return jsonify({'success': True})
@app.route('/room/<room_code>/ready', methods=['POST'])
@login_required
def toggle_player_ready(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()
data = request.get_json()
is_ready = data.get('ready', not player.is_ready)
player.is_ready = is_ready
db.session.commit()
# Отправляем событие через WebSocket
socketio.emit('player_ready_changed', {
'user_id': current_user.id,
'username': current_user.username,
'is_ready': is_ready
}, room=room.code)
return jsonify({'success': True, 'is_ready': is_ready})
@app.route('/api/room/<room_code>/status')
@login_required
def get_room_status(room_code):
"""Получение статуса комнаты"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
player_count = GamePlayer.query.filter_by(room_id=room.id).count()
ready_count = GamePlayer.query.filter_by(room_id=room.id, is_ready=True).count()
return jsonify({
'status': room.status,
'player_count': player_count,
'ready_count': ready_count,
'current_month': room.current_month
})
# @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':
# return redirect(url_for('lobby', room_code=room_code))
#
# # Получаем данные игры
# game_state = room.game_state
# market_data = json.loads(game_state.market_data) if game_state else {}
#
# # Получаем всех игроков для лидерборда
# players = GamePlayer.query.filter_by(room_id=room.id).order_by(GamePlayer.capital.desc()).all()
#
# return render_template('game.html',
# room=room,
# player=player,
# players=players,
# game_state=game_state,
# market_data=market_data)
@app.route('/room/<room_code>/update', methods=['POST'])
@login_required
def update_room_settings(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({'error': 'Только администратор может изменять настройки'}), 403
if room.status != 'waiting':
return jsonify({'error': 'Нельзя изменять настройки во время игры'}), 400
try:
room.name = request.form.get('name', room.name)
room.total_months = int(request.form.get('total_months', room.total_months))
room.start_capital = int(request.form.get('start_capital', room.start_capital))
# Обновляем настройки
settings = json.loads(room.settings) if room.settings else {}
settings['allow_loans'] = request.form.get('allow_loans') == 'on'
settings['allow_black_market'] = request.form.get('allow_black_market') == 'on'
room.settings = json.dumps(settings)
db.session.commit()
socketio.emit('room_updated', {
'room': room.code,
'name': room.name
}, room=room.code)
return jsonify({'success': True})
except Exception as e:
db.session.rollback()
return jsonify({'error': f'Ошибка при обновлении настроек: {str(e)}'}), 500
@app.route('/room/<room_code>/kick/<int:user_id>', methods=['POST'])
@login_required
def kick_player(room_code, user_id):
"""Выгнать игрока из комнаты"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
# Проверяем права администратора
admin_player = GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id,
is_admin=True
).first()
if not admin_player:
return jsonify({'error': 'Только администратор может выгонять игроков'}), 403
# Нельзя выгнать себя или другого администратора
if user_id == current_user.id:
return jsonify({'error': 'Нельзя выгнать самого себя'}), 400
player_to_kick = GamePlayer.query.filter_by(
user_id=user_id,
room_id=room.id
).first()
if not player_to_kick:
return jsonify({'error': 'Игрок не найден в комнате'}), 404
if player_to_kick.is_admin:
return jsonify({'error': 'Нельзя выгнать другого администратора'}), 400
# Удаляем игрока
db.session.delete(player_to_kick)
db.session.commit()
socketio.emit('player_kicked', {
'user_id': user_id,
'username': User.query.get(user_id).username if User.query.get(user_id) else 'Игрок'
}, room=room.code)
return jsonify({'success': True})
@app.route('/room/<room_code>/kick_all', methods=['POST'])
@login_required
def kick_all_players(room_code):
"""Выгнать всех игроков из комнаты"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
# Проверяем права администратора
admin_player = GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id,
is_admin=True
).first()
if not admin_player:
return jsonify({'error': 'Только администратор может выгонять игроков'}), 403
# Удаляем всех игроков кроме администратора
GamePlayer.query.filter_by(room_id=room.id).filter(
GamePlayer.user_id != current_user.id
).delete(synchronize_session=False)
db.session.commit()
socketio.emit('all_players_kicked', {
'room': room.code
}, room=room.code)
return jsonify({'success': True})
@app.route('/room/<room_code>/reset', methods=['POST'])
@login_required
def reset_room(room_code):
"""Сбросить комнату к начальному состоянию"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
# Проверяем права администратора
admin_player = GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id,
is_admin=True
).first()
if not admin_player:
return jsonify({'error': 'Только администратор может сбрасывать комнату'}), 403
# Сбрасываем статус комнаты
room.status = 'waiting'
room.current_month = 1
# Сбрасываем всех игроков
GamePlayer.query.filter_by(room_id=room.id).update({
'is_ready': False,
'capital': room.start_capital,
'assets': '[]',
'position': 0
})
# Сбрасываем состояние игры
game_state = GameState.query.filter_by(room_id=room.id).first()
if game_state:
game_state.phase = 'action'
game_state.phase_end = None
game_state.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()
})
game_state.events = '[]'
game_state.history = '[]'
db.session.commit()
socketio.emit('room_reset', {
'room': room.code
}, room=room.code)
return jsonify({'success': True})
@app.route('/api/room/<room_code>/players')
@login_required
def get_room_players(room_code):
"""Получение списка игроков комнаты без перезагрузки страницы"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
# Проверяем, состоит ли пользователь в комнате
current_player = GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id
).first()
if not current_player:
return jsonify({'error': 'Вы не в этой комнате'}), 403
# Получаем всех игроков
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 'Игрок',
'is_admin': player.is_admin,
'is_ready': player.is_ready,
'capital': player.capital,
'ability_name': get_ability_name(player.ability),
'ability_description': get_ability_description(player.ability)
})
return jsonify({
'success': True,
'players': players_data,
'room_status': room.status
})
@app.route('/room/<room_code>/delete', methods=['POST'])
@login_required
def delete_room(room_code):
"""Удалить комнату"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
# Проверяем права администратора
admin_player = GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id,
is_admin=True
).first()
if not admin_player:
return jsonify({'error': 'Только администратор может удалить комнату'}), 403
# Удаляем комнату и все связанные данные
GamePlayer.query.filter_by(room_id=room.id).delete()
GameState.query.filter_by(room_id=room.id).delete()
db.session.delete(room)
db.session.commit()
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')
@login_required
def get_assets(room_code):
room = GameRoom.query.filter_by(code=room_code).first_or_404()
game_state = room.game_state
if not game_state:
return jsonify({'error': 'Game state not found'}), 404
market_data = json.loads(game_state.market_data)
return jsonify(market_data)
@app.route('/api/game/<room_code>/action', methods=['POST'])
@login_required
def perform_action(room_code):
"""Выполнение действия игрока"""
room = GameRoom.query.filter_by(code=room_code).first_or_404()
# Проверяем, что сейчас фаза действий
if room.game_state.phase != 'action':
return jsonify({'error': 'Not action phase'}), 400
data = request.json
action_type = data.get('type')
player = GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id
).first_or_404()
# Обработка разных типов действий
if action_type == 'buy_asset':
asset_id = data.get('asset_id')
quantity = data.get('quantity', 1)
# Здесь должна быть логика покупки актива
# ...
# Обновляем капитал игрока
player.capital -= data.get('cost', 0)
# Сохраняем активы
assets = json.loads(player.assets)
assets.append({
'id': asset_id,
'quantity': quantity,
'purchase_price': data.get('price'),
'timestamp': datetime.utcnow().isoformat()
})
player.assets = json.dumps(assets)
db.session.commit()
# Отправляем обновление через WebSocket
socketio.emit('player_action', {
'player_id': current_user.id,
'player_name': current_user.username,
'action': action_type,
'asset_id': asset_id,
'quantity': quantity
}, room=room.code)
return jsonify({'success': True, 'new_capital': player.capital})
return jsonify({'error': 'Unknown action type'}), 400
@app.template_filter('from_json')
def from_json_filter(value):
"""Преобразует JSON строку в объект"""
if isinstance(value, str):
return json.loads(value)
return value
@app.template_filter('format_currency')
def format_currency_filter(value):
"""Форматирует число как валюту"""
if value is None:
return "0 ₽"
return f"{value:,.0f}".replace(",", " ")
# --- WEBSOCKET ОБРАБОТЧИКИ ---
@socketio.on('connect')
def handle_connect():
if current_user.is_authenticated:
logger.info(f'User {current_user.username} connected')
emit('connected', {'user_id': current_user.id, 'username': current_user.username})
@socketio.on('join_room')
def handle_join_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 room {room_code}')
# Проверяем, было ли уже уведомление о входе
session_key = f'has_notified_join_{room_code}'
if not session.get(session_key):
# Отправляем уведомление другим игрокам
emit('player_joined', {
'user_id': current_user.id,
'username': current_user.username,
'timestamp': datetime.utcnow().isoformat(),
'is_reconnect': False
}, room=room_code, include_self=False)
# Отмечаем в сессии, что уведомление отправлено
session[session_key] = True
else:
# Это реконнект, отправляем тихое обновление
emit('player_reconnected', {
'user_id': current_user.id,
'username': current_user.username
}, room=room_code, include_self=False)
@socketio.on('leave_room')
def handle_leave_room(data):
room_code = data.get('room')
reason = data.get('reason', 'left') # 'left', 'exited', 'kicked'
if room_code and current_user.is_authenticated:
leave_room(room_code)
logger.info(f'User {current_user.username} left room {room_code} (reason: {reason})')
messages = {
'left': 'покинул комнату',
'exited': 'вышел из лобби',
'kicked': 'был выгнан'
}
emit('player_left', {
'user_id': current_user.id,
'username': current_user.username,
'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')
def handle_player_ready(data):
room_code = data.get('room')
is_ready = data.get('ready', True)
room = GameRoom.query.filter_by(code=room_code).first()
if room:
player = GamePlayer.query.filter_by(
user_id=current_user.id,
room_id=room.id
).first()
if player:
player.is_ready = is_ready
db.session.commit()
emit('player_ready_changed', {
'user_id': current_user.id,
'username': current_user.username,
'is_ready': is_ready
}, room=room_code)
@socketio.on('chat_message')
def handle_chat_message(data):
room_code = data.get('room')
message = data.get('message', '').strip()
if message and room_code:
emit('chat_message', {
'user_id': current_user.id,
'username': current_user.username,
'message': message,
'timestamp': datetime.utcnow().isoformat()
}, room=room_code)
@socketio.on('join_global_room')
def handle_join_global_room():
"""Присоединение к глобальной комнате для обновлений"""
join_room('global_updates')
print(f'User {current_user.username if current_user else "Guest"} joined global room')
@socketio.on('user_online')
def handle_user_online(data):
"""Обработка статуса онлайн пользователя"""
if current_user.is_authenticated:
current_user.last_seen = datetime.utcnow()
db.session.commit()
print(f'User {current_user.username} is online')
# Добавим обработчик для обновления комнат
@socketio.on('get_rooms')
def handle_get_rooms():
"""Отправка списка комнат"""
rooms = GameRoom.query.filter(GameRoom.status != 'finished').all()
rooms_data = []
for room in rooms:
players_count = GamePlayer.query.filter_by(room_id=room.id).count()
rooms_data.append({
'id': room.id,
'name': room.name,
'code': room.code,
'status': room.status,
'players': players_count,
'max_players': app.config['MAX_PLAYERS_PER_ROOM'],
'month': room.current_month,
'total_months': room.total_months
})
emit('rooms_list', {'rooms': rooms_data}, room=request.sid)
# --- КОМАНДЫ УПРАВЛЕНИЯ ---
@app.cli.command('init-db')
def init_db_command():
"""Инициализация базы данных"""
db.create_all()
print('База данных инициализирована.')
@app.cli.command('create-test-data')
def create_test_data():
"""Создание тестовых данных"""
from werkzeug.security import generate_password_hash
# Создаем тестового пользователя
test_user = User(
username='test',
email='test@example.com',
password_hash=generate_password_hash('test123')
)
db.session.add(test_user)
db.session.commit()
print('Тестовые данные созданы.')
@app.cli.command('create-test-users')
def create_test_users():
"""Создание тестовых пользователей"""
test_users = [
{'username': 'Игрок1', 'email': 'player1@test.com', 'password': '123456'},
{'username': 'Игрок2', 'email': 'player2@test.com', 'password': '123456'},
{'username': 'Инвестор', 'email': 'investor@test.com', 'password': '123456'},
{'username': 'Трейдер', 'email': 'trader@test.com', 'password': '123456'},
{'username': 'Банкир', 'email': 'banker@test.com', 'password': '123456'},
{'username': 'admin', 'email': 'admin@test.com', 'password': 'admin123'},
]
created_count = 0
for user_data in test_users:
# Проверяем, существует ли пользователь
existing_user = User.query.filter_by(username=user_data['username']).first()
if not existing_user:
user = User(
username=user_data['username'],
email=user_data['email'],
is_active=True
)
user.set_password(user_data['password'])
db.session.add(user)
created_count += 1
print(f'✓ Создан пользователь: {user_data["username"]}')
else:
print(f'⏭ Пользователь уже существует: {user_data["username"]}')
try:
db.session.commit()
print(f'\n✅ Создано {created_count} тестовых пользователей!')
print('\nТестовые учетные данные:')
print('------------------------')
for user_data in test_users:
print(f'Логин: {user_data["username"]}, Пароль: {user_data["password"]}')
except Exception as e:
db.session.rollback()
print(f'❌ Ошибка при создании пользователей: {str(e)}')
@app.cli.command('create-demo-rooms')
def create_demo_rooms():
"""Создание демонстрационных комнат"""
import random
demo_rooms = [
{
'name': '🤑 Быстрая игра для новичков',
'total_months': 6,
'start_capital': 50000,
'allow_loans': False,
'allow_black_market': False
},
{
'name': '📈 Турнир профессионалов',
'total_months': 12,
'start_capital': 200000,
'allow_loans': True,
'allow_black_market': True
},
{
'name': '👥 Игра с друзьями',
'total_months': 12,
'start_capital': 100000,
'allow_loans': True,
'allow_black_market': False
},
{
'name': '⚡ Экспресс-торги',
'total_months': 3,
'start_capital': 75000,
'allow_loans': False,
'allow_black_market': True
},
]
# Получаем случайного пользователя для создания комнат
users = User.query.all()
if not users:
print('❌ Нет пользователей. Сначала создайте тестовых пользователей.')
return
created_count = 0
for room_data in demo_rooms:
creator = random.choice(users)
# Генерируем уникальный код
import string
chars = string.ascii_uppercase + string.digits
room_code = ''.join(random.choice(chars) for _ in range(6))
# Проверяем, существует ли комната с таким кодом
while GameRoom.query.filter_by(code=room_code).first():
room_code = ''.join(random.choice(chars) for _ in range(6))
# Создаем комнату
room = GameRoom(
name=room_data['name'],
code=room_code,
creator_id=creator.id,
total_months=room_data['total_months'],
start_capital=room_data['start_capital'],
settings=json.dumps({
'allow_loans': room_data['allow_loans'],
'allow_black_market': room_data['allow_black_market'],
'private_room': False
})
)
db.session.add(room)
db.session.commit()
# Добавляем создателя в комнату
player = GamePlayer(
user_id=creator.id,
room_id=room.id,
is_admin=True,
is_ready=True,
capital=room_data['start_capital'],
ability=random.choice([
'crisis_investor', 'lobbyist', 'predictor',
'golden_pillow', 'shadow_accountant', 'credit_magnate'
])
)
db.session.add(player)
# Создаем начальное состояние игры
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()
# Добавляем еще 2-3 случайных игроков в комнату
other_users = [u for u in users if u.id != creator.id]
if other_users:
for _ in range(random.randint(2, 3)):
if other_users:
random_user = random.choice(other_users)
other_users.remove(random_user)
player = GamePlayer(
user_id=random_user.id,
room_id=room.id,
is_admin=False,
is_ready=random.choice([True, False]),
capital=room_data['start_capital'],
ability=random.choice([
'crisis_investor', 'lobbyist', 'predictor',
'golden_pillow', 'shadow_accountant', 'credit_magnate'
])
)
db.session.add(player)
db.session.commit()
created_count += 1
print(f'✓ Создана демо-комната: {room_data["name"]} (код: {room_code})')
print(f'\n✅ Создано {created_count} демонстрационных комнат!')
@app.cli.command('reset-db')
def reset_db():
"""Полный сброс базы данных"""
confirmation = input('⚠️ Вы уверены, что хотите сбросить всю базу данных? (yes/no): ')
if confirmation.lower() != 'yes':
print('❌ Отменено')
return
# Удаляем все таблицы
db.drop_all()
print('🗑️ Таблицы удалены')
# Создаем заново
db.create_all()
print('✅ Таблицы созданы заново')
# Создаем тестовых пользователей
print('\n👥 Создание тестовых пользователей...')
ctx = app.test_request_context()
with ctx:
create_test_users.callback()
# Создаем демо-комнаты
print('\n🏢 Создание демонстрационных комнат...')
with ctx:
create_demo_rooms.callback()
print('\n🎉 База данных успешно сброшена и заполнена тестовыми данными!')
# --- ОБРАБОТЧИКИ ОШИБОК ---
from datetime import datetime
@app.errorhandler(404)
def not_found_error(error):
return render_template('404.html'), 404
@app.errorhandler(500)
def internal_error(error):
db.session.rollback()
return render_template('500.html', now=datetime.now()), 500
@app.errorhandler(403)
def forbidden_error(error):
return render_template('403.html'), 403
# Создадим и 403 ошибку для полноты
@app.route('/403')
def forbidden_page():
return render_template('403.html'), 403
# Дополнительные функции
def get_ability_name(ability_code):
"""Получить название способности по коду"""
abilities = {
'crisis_investor': 'Кризисный инвестор',
'lobbyist': 'Лоббист',
'predictor': 'Предсказатель',
'golden_pillow': 'Золотая подушка',
'shadow_accountant': 'Теневая бухгалтерия',
'credit_magnate': 'Кредитный магнат',
'bear_raid': 'Медвежий набег',
'fake_news': 'Фейковые новости',
'dividend_king': 'Король дивидендов',
'raider_capture': 'Рейдерский захват',
'mafia_connections': 'Мафиозные связи',
'economic_advisor': 'Экономический советник',
'currency_speculator': 'Валютный спекулянт'
}
return abilities.get(ability_code, 'Неизвестная способность')
def get_ability_description(ability_code):
"""Получить описание способности по коду"""
descriptions = {
'crisis_investor': '+20% к доходу при падении рынка',
'lobbyist': 'Может временно менять правила налогообложения',
'predictor': 'Видит следующее случайное событие',
'golden_pillow': 'Может защитить 20% капитала от кризисов',
'shadow_accountant': 'Раз в игру уменьшить налоги на 50%',
'credit_magnate': 'Может давать кредиты другим игрокам',
'bear_raid': 'Вызвать искусственное падение цены актива на 15%',
'fake_news': 'Подменить случайное событие на выгодное',
'dividend_king': 'Получать +10% дохода от всех акций',
'raider_capture': 'Попытаться отобрать 5% капитала у лидера',
'mafia_connections': 'Отменить одно негативное событие',
'economic_advisor': 'Раз в 3 месяца изменить налоговую ставку',
'currency_speculator': 'Отвязать рубль от нефти на 1 месяц'
}
return descriptions.get(ability_code, 'Описание отсутствует')
# Добавим функции в контекст шаблонов
@app.context_processor
def utility_processor():
return dict(
get_ability_name=get_ability_name,
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__':
# Создаем таблицы если их нет
with app.app_context():
db.create_all()
# Запускаем сервер
socketio.run(app,
host='0.0.0.0',
port=5000,
debug=True,
allow_unsafe_werkzeug=True)