Game page
This commit is contained in:
591
app.py
591
app.py
@@ -579,12 +579,34 @@ def start_room_game(room_code):
|
||||
|
||||
# Меняем статус комнаты
|
||||
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': 'Игра началась!'
|
||||
'message': 'Игра началась!',
|
||||
'phase': 'action',
|
||||
'phase_end': room.game_state.phase_end.isoformat() if room.game_state else None
|
||||
}, room=room.code)
|
||||
|
||||
return jsonify({'success': True})
|
||||
@@ -896,6 +918,67 @@ def delete_room(room_code):
|
||||
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):
|
||||
@@ -1018,14 +1101,29 @@ def handle_join_room(data):
|
||||
@socketio.on('leave_room')
|
||||
def handle_leave_room(data):
|
||||
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)
|
||||
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', {
|
||||
'user_id': current_user.id,
|
||||
'username': current_user.username
|
||||
}, room=room_code)
|
||||
'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')
|
||||
@@ -1404,6 +1502,489 @@ def utility_processor():
|
||||
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__':
|
||||
|
||||
Reference in New Issue
Block a user