Buy Sell
This commit is contained in:
736
app.py
736
app.py
@@ -10,6 +10,14 @@ import random
|
||||
import logging
|
||||
from werkzeug.security import generate_password_hash, check_password_hash
|
||||
|
||||
import sys
|
||||
sys.path.append('.')
|
||||
|
||||
from game_balance.config import BalanceConfig
|
||||
from game_balance.validator import BalanceValidator
|
||||
from game_balance.assets_config import AssetsConfig
|
||||
from game_balance.players_config import PlayersConfig
|
||||
|
||||
# Настройка логирования
|
||||
logging.basicConfig(level=logging.DEBUG)
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -40,6 +48,8 @@ class GameRoom(db.Model):
|
||||
start_capital = db.Column(db.Integer, default=100000)
|
||||
settings = db.Column(db.Text, default='{}') # JSON с настройками
|
||||
|
||||
balance_mode = db.Column(db.String(20), default='standard')
|
||||
|
||||
# Связи - убираем lazy='dynamic' для players
|
||||
players = db.relationship('GamePlayer', backref='room', lazy='select') # Изменено
|
||||
game_state = db.relationship('GameState', backref='room', uselist=False)
|
||||
@@ -106,6 +116,42 @@ class GameState(db.Model):
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
class AssetInventory(db.Model):
|
||||
"""Остатки активов на рынке"""
|
||||
__tablename__ = 'asset_inventories'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
room_id = db.Column(db.Integer, db.ForeignKey('game_rooms.id'))
|
||||
asset_id = db.Column(db.String(50), nullable=False)
|
||||
total_quantity = db.Column(db.Float, nullable=False)
|
||||
remaining_quantity = db.Column(db.Float, nullable=False)
|
||||
current_price = db.Column(db.Float, nullable=False)
|
||||
base_price = db.Column(db.Float, nullable=False)
|
||||
updated_at = db.Column(db.DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
__table_args__ = (
|
||||
db.UniqueConstraint('room_id', 'asset_id', name='unique_room_asset'),
|
||||
)
|
||||
|
||||
|
||||
class Transaction(db.Model):
|
||||
"""История транзакций"""
|
||||
__tablename__ = 'transactions'
|
||||
|
||||
id = db.Column(db.Integer, primary_key=True)
|
||||
room_id = db.Column(db.Integer, db.ForeignKey('game_rooms.id'))
|
||||
player_id = db.Column(db.Integer, db.ForeignKey('game_players.id'))
|
||||
asset_id = db.Column(db.String(50), nullable=False)
|
||||
transaction_type = db.Column(db.String(20), nullable=False) # buy, sell
|
||||
quantity = db.Column(db.Float, nullable=False)
|
||||
price = db.Column(db.Float, nullable=False)
|
||||
total_amount = db.Column(db.Float, nullable=False)
|
||||
created_at = db.Column(db.DateTime, default=datetime.utcnow)
|
||||
|
||||
# Связи
|
||||
player = db.relationship('GamePlayer', backref='transactions')
|
||||
|
||||
|
||||
# --- ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ ---
|
||||
|
||||
def generate_room_code():
|
||||
@@ -400,6 +446,7 @@ def create_room():
|
||||
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))
|
||||
balance_mode = request.form.get('balance_mode', 'standard')
|
||||
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'
|
||||
@@ -408,14 +455,13 @@ def create_room():
|
||||
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))
|
||||
# Загружаем баланс
|
||||
balance = BalanceConfig.load_balance_mode(balance_mode)
|
||||
players_config = balance.get_players_config()
|
||||
assets_config = balance.get_assets_config()
|
||||
economy_config = balance.get_economy_config()
|
||||
|
||||
# Генерируем код комнаты
|
||||
room_code = generate_room_code()
|
||||
|
||||
# Создаем комнату
|
||||
@@ -425,6 +471,7 @@ def create_room():
|
||||
creator_id=current_user.id,
|
||||
total_months=total_months,
|
||||
start_capital=start_capital,
|
||||
balance_mode=balance_mode,
|
||||
settings=json.dumps({
|
||||
'allow_loans': allow_loans,
|
||||
'allow_black_market': allow_black_market,
|
||||
@@ -435,41 +482,57 @@ def create_room():
|
||||
db.session.add(room)
|
||||
db.session.commit()
|
||||
|
||||
# Добавляем создателя как игрока-администратора
|
||||
# Добавляем создателя как игрока
|
||||
abilities = list(players_config.get('ABILITIES', PlayersConfig.ABILITIES).keys())
|
||||
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'
|
||||
])
|
||||
ability=random.choice(abilities) if abilities else None
|
||||
)
|
||||
|
||||
db.session.add(player)
|
||||
db.session.commit()
|
||||
|
||||
# Создаем начальное состояние игры
|
||||
# Создаем состояние игры
|
||||
market_data = {
|
||||
'initialized': True,
|
||||
'assets': assets_config,
|
||||
'balance_mode': balance_mode,
|
||||
'last_update': datetime.utcnow().isoformat(),
|
||||
'correlations': economy_config.get('ASSET_CORRELATIONS', {})
|
||||
}
|
||||
|
||||
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()
|
||||
})
|
||||
phase='waiting', # Не 'action', а 'waiting' до начала игры
|
||||
phase_end=None, # Без времени до начала игры
|
||||
market_data=json.dumps(market_data)
|
||||
)
|
||||
|
||||
db.session.add(game_state)
|
||||
|
||||
# ========== ДОБАВЛЯЕМ ИНВЕНТАРЬ ЗДЕСЬ ==========
|
||||
# Создаем инвентарь для ограниченных активов
|
||||
from game_balance.assets_config import AssetsConfig # импорт в начале файла
|
||||
|
||||
for asset_id, asset_data in assets_config.items():
|
||||
if asset_data.get('total_quantity') is not None:
|
||||
inventory = AssetInventory(
|
||||
room_id=room.id,
|
||||
asset_id=asset_id,
|
||||
total_quantity=asset_data['total_quantity'],
|
||||
remaining_quantity=asset_data['total_quantity'],
|
||||
current_price=asset_data['base_price'],
|
||||
base_price=asset_data['base_price']
|
||||
)
|
||||
db.session.add(inventory)
|
||||
# ================================================
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# Отправляем успешный ответ
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'room_code': room.code,
|
||||
@@ -580,24 +643,46 @@ def start_room_game(room_code):
|
||||
# Меняем статус комнаты
|
||||
room.status = 'playing'
|
||||
|
||||
# Создаем начальное состояние игры если его нет
|
||||
# Получаем длительность фазы из баланса
|
||||
try:
|
||||
balance = BalanceConfig.load_balance_mode(room.balance_mode)
|
||||
game_config = balance.get_game_config()
|
||||
phase_duration = game_config.get('PHASE_DURATIONS', {}).get('action', 120)
|
||||
except:
|
||||
# Если баланс не загружается, используем значение по умолчанию
|
||||
phase_duration = 120
|
||||
|
||||
# Создаем или обновляем состояние игры
|
||||
if not room.game_state:
|
||||
# Загружаем конфигурацию активов из баланса
|
||||
try:
|
||||
balance = BalanceConfig.load_balance_mode(room.balance_mode)
|
||||
assets_config = balance.get_assets_config()
|
||||
economy_config = balance.get_economy_config()
|
||||
except:
|
||||
from game_balance.assets_config import AssetsConfig
|
||||
assets_config = AssetsConfig.ASSETS
|
||||
economy_config = {'ASSET_CORRELATIONS': {}}
|
||||
|
||||
# Создаем новое состояние
|
||||
game_state = GameState(
|
||||
room_id=room.id,
|
||||
phase='action',
|
||||
phase_end=datetime.utcnow() + timedelta(seconds=120), # 2 минуты на действия
|
||||
phase_end=datetime.utcnow() + timedelta(seconds=phase_duration),
|
||||
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()
|
||||
'assets': assets_config,
|
||||
'balance_mode': room.balance_mode,
|
||||
'last_update': datetime.utcnow().isoformat(),
|
||||
'correlations': economy_config.get('ASSET_CORRELATIONS', {})
|
||||
})
|
||||
)
|
||||
db.session.add(game_state)
|
||||
else:
|
||||
# Обновляем существующее состояние
|
||||
room.game_state.phase = 'action'
|
||||
room.game_state.phase_end = datetime.utcnow() + timedelta(seconds=phase_duration)
|
||||
room.game_state.updated_at = datetime.utcnow()
|
||||
|
||||
db.session.commit()
|
||||
|
||||
@@ -606,7 +691,7 @@ def start_room_game(room_code):
|
||||
'room': room.code,
|
||||
'message': 'Игра началась!',
|
||||
'phase': 'action',
|
||||
'phase_end': room.game_state.phase_end.isoformat() if room.game_state else None
|
||||
'phase_end': room.game_state.phase_end.isoformat() if room.game_state and room.game_state.phase_end else None
|
||||
}, room=room.code)
|
||||
|
||||
return jsonify({'success': True})
|
||||
@@ -981,15 +1066,131 @@ def exit_lobby(room_code):
|
||||
|
||||
@app.route('/api/game/<room_code>/assets')
|
||||
@login_required
|
||||
def get_assets(room_code):
|
||||
def get_game_assets(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()
|
||||
|
||||
# Загружаем баланс
|
||||
balance = BalanceConfig.load_balance_mode(room.balance_mode)
|
||||
assets_config = balance.get_assets_config()
|
||||
|
||||
# Получаем текущие цены из game_state
|
||||
game_state = room.game_state
|
||||
market_data = json.loads(game_state.market_data) if game_state else {}
|
||||
market_assets = market_data.get('assets', {})
|
||||
|
||||
if not game_state:
|
||||
return jsonify({'error': 'Game state not found'}), 404
|
||||
# Получаем инвентарь для ограниченных активов
|
||||
inventories = {}
|
||||
inventory_list = AssetInventory.query.filter_by(room_id=room.id).all()
|
||||
for inv in inventory_list:
|
||||
inventories[inv.asset_id] = inv
|
||||
|
||||
market_data = json.loads(game_state.market_data)
|
||||
return jsonify(market_data)
|
||||
# Получаем активы игрока
|
||||
player_assets = json.loads(player.assets) if player.assets else []
|
||||
player_assets_dict = {}
|
||||
for pa in player_assets:
|
||||
player_assets_dict[pa['id']] = pa['quantity']
|
||||
|
||||
# Формируем ответ
|
||||
result = {}
|
||||
for asset_id, asset_config in assets_config.items():
|
||||
asset_data = asset_config.copy()
|
||||
|
||||
# Обновляем цену
|
||||
if asset_id in market_assets:
|
||||
asset_data['price'] = market_assets[asset_id].get('price', asset_config['base_price'])
|
||||
else:
|
||||
asset_data['price'] = asset_config['base_price']
|
||||
|
||||
# Добавляем информацию о доступности
|
||||
if asset_config.get('total_quantity') is not None:
|
||||
inv = inventories.get(asset_id)
|
||||
if inv:
|
||||
asset_data['available'] = inv.remaining_quantity
|
||||
asset_data['total'] = inv.total_quantity
|
||||
asset_data['current_price'] = inv.current_price
|
||||
else:
|
||||
# Если инвентаря нет, создаем его
|
||||
new_inv = AssetInventory(
|
||||
room_id=room.id,
|
||||
asset_id=asset_id,
|
||||
total_quantity=asset_config['total_quantity'],
|
||||
remaining_quantity=asset_config['total_quantity'],
|
||||
current_price=asset_data['price'],
|
||||
base_price=asset_config['base_price']
|
||||
)
|
||||
db.session.add(new_inv)
|
||||
db.session.commit()
|
||||
asset_data['available'] = asset_config['total_quantity']
|
||||
asset_data['total'] = asset_config['total_quantity']
|
||||
else:
|
||||
asset_data['available'] = None
|
||||
asset_data['total'] = None
|
||||
|
||||
# Добавляем информацию о владении игрока
|
||||
asset_data['player_quantity'] = player_assets_dict.get(asset_id, 0)
|
||||
asset_data['max_per_player'] = asset_config.get('max_per_player')
|
||||
asset_data['min_purchase'] = asset_config.get('min_purchase', 1)
|
||||
|
||||
result[asset_id] = asset_data
|
||||
|
||||
# Определяем категории активов прямо здесь
|
||||
categories = {
|
||||
'bonds': {'name': 'Облигации', 'icon': '🏦'},
|
||||
'stocks': {'name': 'Акции', 'icon': '📈'},
|
||||
'real_estate': {'name': 'Недвижимость', 'icon': '🏠'},
|
||||
'crypto': {'name': 'Криптовалюта', 'icon': '💰'},
|
||||
'commodities': {'name': 'Сырьевые товары', 'icon': '⛽'},
|
||||
'business': {'name': 'Бизнес', 'icon': '🏢'},
|
||||
'unique': {'name': 'Уникальные активы', 'icon': '🏆'}
|
||||
}
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'assets': result,
|
||||
'categories': categories
|
||||
})
|
||||
|
||||
|
||||
@app.route('/api/game/<room_code>/transactions')
|
||||
@login_required
|
||||
def get_transactions(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()
|
||||
|
||||
# Получаем последние 50 транзакций
|
||||
transactions = Transaction.query.filter_by(
|
||||
room_id=room.id,
|
||||
player_id=player.id
|
||||
).order_by(Transaction.created_at.desc()).limit(50).all()
|
||||
|
||||
result = []
|
||||
for t in transactions:
|
||||
result.append({
|
||||
'id': t.id,
|
||||
'type': t.transaction_type,
|
||||
'asset_id': t.asset_id,
|
||||
'quantity': t.quantity,
|
||||
'price': t.price,
|
||||
'total': t.total_amount,
|
||||
'created_at': t.created_at.isoformat()
|
||||
})
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'transactions': result
|
||||
})
|
||||
|
||||
|
||||
@app.route('/api/game/<room_code>/action', methods=['POST'])
|
||||
@@ -1433,6 +1634,35 @@ def reset_db():
|
||||
print('\n🎉 База данных успешно сброшена и заполнена тестовыми данными!')
|
||||
|
||||
|
||||
@app.cli.command('validate-balance')
|
||||
def validate_balance_command():
|
||||
"""Проверка всех режимов баланса"""
|
||||
from game_balance.validator import BalanceValidator
|
||||
|
||||
modes = BalanceConfig.get_available_modes()
|
||||
print(f"Проверка баланса для {len(modes)} режимов...")
|
||||
print("-" * 50)
|
||||
|
||||
all_valid = True
|
||||
for mode_key, mode_name in modes.items():
|
||||
print(f"\nРежим: {mode_name}")
|
||||
balance = BalanceConfig.load_balance_mode(mode_key)
|
||||
errors = BalanceValidator.validate_balance_mode(balance)
|
||||
|
||||
if errors:
|
||||
print(f"❌ Найдено {len(errors)} ошибок:")
|
||||
for error in errors:
|
||||
print(f" - {error}")
|
||||
all_valid = False
|
||||
else:
|
||||
print("✅ Баланс корректен")
|
||||
|
||||
if all_valid:
|
||||
print("\n✅ Все режимы баланса валидны!")
|
||||
else:
|
||||
print("\n❌ Обнаружены ошибки в балансе")
|
||||
|
||||
|
||||
# --- ОБРАБОТЧИКИ ОШИБОК ---
|
||||
from datetime import datetime
|
||||
|
||||
@@ -1506,7 +1736,7 @@ def utility_processor():
|
||||
# Логика игры
|
||||
@app.route('/game/<room_code>')
|
||||
@login_required
|
||||
def game(room_code):
|
||||
def game_page(room_code):
|
||||
"""Страница игры"""
|
||||
room = GameRoom.query.filter_by(code=room_code).first_or_404()
|
||||
|
||||
@@ -1525,17 +1755,22 @@ def game(room_code):
|
||||
game_state = room.game_state
|
||||
if not game_state:
|
||||
# Создаем начальное состояние игры если его нет
|
||||
balance = BalanceConfig.load_balance_mode(room.balance_mode)
|
||||
assets_config = balance.get_assets_config()
|
||||
economy_config = balance.get_economy_config()
|
||||
game_config = balance.get_game_config()
|
||||
phase_duration = game_config.get('PHASE_DURATIONS', {}).get('action', 120)
|
||||
|
||||
game_state = GameState(
|
||||
room_id=room.id,
|
||||
phase='action',
|
||||
phase_end=datetime.utcnow() + timedelta(seconds=phase_duration),
|
||||
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()
|
||||
'assets': assets_config,
|
||||
'balance_mode': room.balance_mode,
|
||||
'last_update': datetime.utcnow().isoformat(),
|
||||
'correlations': economy_config.get('ASSET_CORRELATIONS', {})
|
||||
})
|
||||
)
|
||||
db.session.add(game_state)
|
||||
@@ -1546,10 +1781,19 @@ def game(room_code):
|
||||
|
||||
# Словарь названий активов для шаблона
|
||||
asset_names = {
|
||||
'gov_bonds': 'Гособлигации',
|
||||
'stock_gazprom': 'Акции Газпрома',
|
||||
'real_estate': 'Недвижимость',
|
||||
'stock_sberbank': 'Акции Сбербанка',
|
||||
'stock_yandex': 'Акции Яндекса',
|
||||
'apartment_small': 'Небольшая квартира',
|
||||
'apartment_elite': 'Элитная квартира',
|
||||
'bitcoin': 'Биткоин',
|
||||
'oil': 'Нефть'
|
||||
'oil': 'Нефть Brent',
|
||||
'natural_gas': 'Природный газ',
|
||||
'coffee_shop': 'Кофейня',
|
||||
'it_startup': 'IT-стартап',
|
||||
'shopping_mall': 'Торговый центр',
|
||||
'oil_field': 'Нефтяное месторождение'
|
||||
}
|
||||
|
||||
return render_template('game.html',
|
||||
@@ -1563,7 +1807,7 @@ def game(room_code):
|
||||
# API эндпоинты для игры
|
||||
@app.route('/api/game/<room_code>/state')
|
||||
@login_required
|
||||
def get_game_state(room_code):
|
||||
def get_game_state_api(room_code): # Переименовано, чтобы не конфликтовать
|
||||
"""Получение текущего состояния игры"""
|
||||
room = GameRoom.query.filter_by(code=room_code).first_or_404()
|
||||
|
||||
@@ -1576,7 +1820,7 @@ def get_game_state(room_code):
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'phase': game_state.phase if game_state else 'action',
|
||||
'phase': game_state.phase if game_state else 'waiting',
|
||||
'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,
|
||||
@@ -1585,36 +1829,13 @@ def get_game_state(room_code):
|
||||
})
|
||||
|
||||
|
||||
@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
|
||||
@@ -1622,42 +1843,164 @@ def buy_asset(room_code):
|
||||
|
||||
# Проверяем, что сейчас фаза действий
|
||||
if room.game_state and room.game_state.phase != 'action':
|
||||
return jsonify({'success': False, 'error': 'Не фаза действий'}), 400
|
||||
return jsonify({'success': False, 'error': 'Покупка доступна только в фазу действий'}), 400
|
||||
|
||||
data = request.json
|
||||
asset_id = data.get('asset_id')
|
||||
quantity = data.get('quantity', 1)
|
||||
price = data.get('price')
|
||||
quantity = float(data.get('quantity', 1))
|
||||
|
||||
if not asset_id or not price:
|
||||
return jsonify({'success': False, 'error': 'Не указан актив или цена'}), 400
|
||||
if not asset_id or quantity <= 0:
|
||||
return jsonify({'success': False, 'error': 'Не указан актив или количество'}), 400
|
||||
|
||||
total_cost = price * quantity
|
||||
# Загружаем баланс комнаты
|
||||
balance = BalanceConfig.load_balance_mode(room.balance_mode)
|
||||
assets_config = balance.get_assets_config()
|
||||
|
||||
# Получаем конфигурацию актива
|
||||
asset_config = assets_config.get(asset_id)
|
||||
if not asset_config:
|
||||
return jsonify({'success': False, 'error': 'Актив не найден'}), 404
|
||||
|
||||
# Проверяем минимальное количество для покупки
|
||||
min_purchase = asset_config.get('min_purchase', 1)
|
||||
if quantity < min_purchase:
|
||||
return jsonify({'success': False, 'error': f'Минимальное количество для покупки: {min_purchase}'}), 400
|
||||
|
||||
# Получаем текущую цену из инвентаря или конфига
|
||||
inventory = None
|
||||
current_price = asset_config['base_price']
|
||||
|
||||
if asset_config.get('total_quantity') is not None:
|
||||
# Ограниченный актив - берем из инвентаря
|
||||
inventory = AssetInventory.query.filter_by(
|
||||
room_id=room.id,
|
||||
asset_id=asset_id
|
||||
).first()
|
||||
|
||||
if not inventory:
|
||||
return jsonify({'success': False, 'error': 'Инвентарь актива не найден'}), 404
|
||||
|
||||
# Проверяем остаток
|
||||
if inventory.remaining_quantity < quantity:
|
||||
return jsonify({'success': False,
|
||||
'error': f'Доступно только {inventory.remaining_quantity} {asset_config["name"]}'}), 400
|
||||
|
||||
current_price = inventory.current_price
|
||||
else:
|
||||
# Безлимитный актив - берем из game_state
|
||||
game_state = room.game_state
|
||||
market_data = json.loads(game_state.market_data) if game_state else {}
|
||||
assets = market_data.get('assets', {})
|
||||
if asset_id in assets:
|
||||
current_price = assets[asset_id].get('price', asset_config['base_price'])
|
||||
|
||||
# Рассчитываем стоимость
|
||||
total_cost = current_price * quantity
|
||||
|
||||
# Проверяем достаточно ли средств
|
||||
if player.capital < total_cost:
|
||||
return jsonify({'success': False, 'error': 'Недостаточно средств'}), 400
|
||||
return jsonify({'success': False, 'error': f'Недостаточно средств. Нужно: {format_currency(total_cost)}'}), 400
|
||||
|
||||
# Получаем текущие активы игрока
|
||||
assets = json.loads(player.assets) if player.assets else []
|
||||
# Проверяем лимиты на владение
|
||||
if asset_config.get('max_per_player'):
|
||||
# Считаем сколько уже есть у игрока
|
||||
current_assets = json.loads(player.assets) if player.assets else []
|
||||
current_quantity = 0
|
||||
for asset in current_assets:
|
||||
if asset['id'] == asset_id:
|
||||
current_quantity += asset['quantity']
|
||||
|
||||
# Добавляем новый актив
|
||||
assets.append({
|
||||
'id': asset_id,
|
||||
'quantity': quantity,
|
||||
'purchase_price': price,
|
||||
'timestamp': datetime.utcnow().isoformat()
|
||||
})
|
||||
if current_quantity + quantity > asset_config['max_per_player']:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'Максимум {asset_config["max_per_player"]} {asset_config["name"]} на игрока'
|
||||
}), 400
|
||||
|
||||
# Обновляем капитал и активы игрока
|
||||
# Проверяем месячные лимиты
|
||||
players_config = balance.get_players_config()
|
||||
purchase_limits = players_config.get('PURCHASE_LIMITS_BY_MONTH', {})
|
||||
month_limit = purchase_limits.get(room.current_month, 1.0)
|
||||
|
||||
max_allowed = asset_config.get('max_per_player', float('inf'))
|
||||
if max_allowed != float('inf'):
|
||||
max_this_month = max_allowed * month_limit
|
||||
if current_quantity + quantity > max_this_month:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'В этом месяце можно купить не более {max_this_month} {asset_config["name"]}'
|
||||
}), 400
|
||||
|
||||
# ВСЕ ПРОВЕРКИ ПРОЙДЕНЫ - ВЫПОЛНЯЕМ ПОКУПКУ
|
||||
|
||||
# 1. Обновляем инвентарь (для ограниченных активов)
|
||||
if inventory:
|
||||
inventory.remaining_quantity -= quantity
|
||||
inventory.updated_at = datetime.utcnow()
|
||||
|
||||
# 2. Обновляем активы игрока
|
||||
player_assets = json.loads(player.assets) if player.assets else []
|
||||
|
||||
# Ищем существующий актив
|
||||
found = False
|
||||
for asset in player_assets:
|
||||
if asset['id'] == asset_id:
|
||||
asset['quantity'] += quantity
|
||||
asset['purchase_price'] = (asset['purchase_price'] * asset['quantity'] + current_price * quantity) / (
|
||||
asset['quantity'] + quantity)
|
||||
found = True
|
||||
break
|
||||
|
||||
# Если не нашли, добавляем новый
|
||||
if not found:
|
||||
player_assets.append({
|
||||
'id': asset_id,
|
||||
'quantity': quantity,
|
||||
'purchase_price': current_price,
|
||||
'purchase_date': datetime.utcnow().isoformat()
|
||||
})
|
||||
|
||||
player.assets = json.dumps(player_assets)
|
||||
|
||||
# 3. Списываем деньги
|
||||
player.capital -= total_cost
|
||||
player.assets = json.dumps(assets)
|
||||
|
||||
# 4. Сохраняем транзакцию
|
||||
transaction = Transaction(
|
||||
room_id=room.id,
|
||||
player_id=player.id,
|
||||
asset_id=asset_id,
|
||||
transaction_type='buy',
|
||||
quantity=quantity,
|
||||
price=current_price,
|
||||
total_amount=total_cost
|
||||
)
|
||||
db.session.add(transaction)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# 5. Отправляем WebSocket уведомление
|
||||
socketio.emit('player_action', {
|
||||
'room': room.code,
|
||||
'player_id': player.user_id,
|
||||
'player_name': current_user.username,
|
||||
'action': 'buy',
|
||||
'asset_id': asset_id,
|
||||
'asset_name': asset_config['name'],
|
||||
'quantity': quantity,
|
||||
'price': current_price,
|
||||
'total': total_cost,
|
||||
'timestamp': datetime.utcnow().isoformat()
|
||||
}, room=room.code)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'new_capital': player.capital,
|
||||
'message': f'Куплено {quantity} {asset_id}'
|
||||
'asset_name': asset_config['name'],
|
||||
'quantity': quantity,
|
||||
'price': current_price,
|
||||
'total': total_cost,
|
||||
'remaining': inventory.remaining_quantity if inventory else None,
|
||||
'message': f'Куплено {quantity} {asset_config["name"]} за {format_currency(total_cost)}'
|
||||
})
|
||||
|
||||
|
||||
@@ -1667,6 +2010,7 @@ 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
|
||||
@@ -1674,55 +2018,132 @@ def sell_asset(room_code):
|
||||
|
||||
# Проверяем, что сейчас фаза действий
|
||||
if room.game_state and room.game_state.phase != 'action':
|
||||
return jsonify({'success': False, 'error': 'Не фаза действий'}), 400
|
||||
return jsonify({'success': False, 'error': 'Продажа доступна только в фазу действий'}), 400
|
||||
|
||||
data = request.json
|
||||
asset_id = data.get('asset_id')
|
||||
quantity = data.get('quantity', 1)
|
||||
quantity = float(data.get('quantity', 1))
|
||||
|
||||
if not asset_id:
|
||||
return jsonify({'success': False, 'error': 'Не указан актив'}), 400
|
||||
if not asset_id or quantity <= 0:
|
||||
return jsonify({'success': False, 'error': 'Не указан актив или количество'}), 400
|
||||
|
||||
# Загружаем баланс комнаты
|
||||
balance = BalanceConfig.load_balance_mode(room.balance_mode)
|
||||
assets_config = balance.get_assets_config()
|
||||
|
||||
# Получаем конфигурацию актива
|
||||
asset_config = assets_config.get(asset_id)
|
||||
if not asset_config:
|
||||
return jsonify({'success': False, 'error': 'Актив не найден'}), 404
|
||||
|
||||
# Получаем текущие активы игрока
|
||||
assets = json.loads(player.assets) if player.assets else []
|
||||
player_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:
|
||||
for asset in player_assets:
|
||||
if asset['id'] == asset_id:
|
||||
asset_to_sell = asset
|
||||
break
|
||||
|
||||
if not asset_to_sell:
|
||||
return jsonify({'success': False, 'error': 'Недостаточно активов для продажи'}), 400
|
||||
return jsonify({'success': False, 'error': f'У вас нет {asset_config["name"]}'}), 400
|
||||
|
||||
# Получаем текущую цену актива
|
||||
game_state = room.game_state
|
||||
if not game_state:
|
||||
return jsonify({'success': False, 'error': 'Состояние игры не найдено'}), 404
|
||||
if asset_to_sell['quantity'] < quantity:
|
||||
return jsonify({
|
||||
'success': False,
|
||||
'error': f'У вас только {asset_to_sell["quantity"]} {asset_config["name"]}'
|
||||
}), 400
|
||||
|
||||
market_data = json.loads(game_state.market_data)
|
||||
current_price = market_data.get('assets', {}).get(asset_id, {}).get('price', asset_to_sell['purchase_price'])
|
||||
# Получаем текущую цену
|
||||
current_price = None
|
||||
|
||||
if asset_config.get('total_quantity') is not None:
|
||||
# Ограниченный актив - берем из инвентаря
|
||||
inventory = AssetInventory.query.filter_by(
|
||||
room_id=room.id,
|
||||
asset_id=asset_id
|
||||
).first()
|
||||
|
||||
if inventory:
|
||||
current_price = inventory.current_price
|
||||
else:
|
||||
# Безлимитный актив - берем из game_state
|
||||
game_state = room.game_state
|
||||
if game_state:
|
||||
market_data = json.loads(game_state.market_data)
|
||||
assets = market_data.get('assets', {})
|
||||
if asset_id in assets:
|
||||
current_price = assets[asset_id].get('price', asset_config['base_price'])
|
||||
|
||||
# Если цену не нашли, используем цену покупки
|
||||
if current_price is None:
|
||||
current_price = asset_to_sell['purchase_price']
|
||||
|
||||
# Рассчитываем выручку
|
||||
revenue = current_price * quantity
|
||||
|
||||
# Обновляем активы игрока
|
||||
# Комиссия за продажу
|
||||
economy_config = balance.get_economy_config()
|
||||
transaction_fee = economy_config.get('TAX_SYSTEM', {}).get('transaction_fee', 0.01)
|
||||
fee = revenue * transaction_fee
|
||||
revenue_after_fee = revenue - fee
|
||||
|
||||
# ВЫПОЛНЯЕМ ПРОДАЖУ
|
||||
|
||||
# 1. Обновляем инвентарь (для ограниченных активов)
|
||||
if asset_config.get('total_quantity') is not None and inventory:
|
||||
inventory.remaining_quantity += quantity
|
||||
inventory.updated_at = datetime.utcnow()
|
||||
|
||||
# 2. Обновляем активы игрока
|
||||
asset_to_sell['quantity'] -= quantity
|
||||
if asset_to_sell['quantity'] <= 0:
|
||||
assets.remove(asset_to_sell)
|
||||
player_assets.remove(asset_to_sell)
|
||||
|
||||
# Обновляем капитал
|
||||
player.capital += revenue
|
||||
player.assets = json.dumps(assets)
|
||||
player.assets = json.dumps(player_assets)
|
||||
|
||||
# 3. Начисляем деньги
|
||||
player.capital += revenue_after_fee
|
||||
|
||||
# 4. Сохраняем транзакцию
|
||||
transaction = Transaction(
|
||||
room_id=room.id,
|
||||
player_id=player.id,
|
||||
asset_id=asset_id,
|
||||
transaction_type='sell',
|
||||
quantity=quantity,
|
||||
price=current_price,
|
||||
total_amount=revenue_after_fee
|
||||
)
|
||||
db.session.add(transaction)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
# 5. Отправляем WebSocket уведомление
|
||||
socketio.emit('player_action', {
|
||||
'room': room.code,
|
||||
'player_id': player.user_id,
|
||||
'player_name': current_user.username,
|
||||
'action': 'sell',
|
||||
'asset_id': asset_id,
|
||||
'asset_name': asset_config['name'],
|
||||
'quantity': quantity,
|
||||
'price': current_price,
|
||||
'total': revenue_after_fee,
|
||||
'fee': fee,
|
||||
'timestamp': datetime.utcnow().isoformat()
|
||||
}, room=room.code)
|
||||
|
||||
return jsonify({
|
||||
'success': True,
|
||||
'new_capital': player.capital,
|
||||
'revenue': revenue,
|
||||
'message': f'Продано {quantity} {asset_id}'
|
||||
'asset_name': asset_config['name'],
|
||||
'quantity': quantity,
|
||||
'price': current_price,
|
||||
'total': revenue_after_fee,
|
||||
'fee': fee,
|
||||
'message': f'Продано {quantity} {asset_config["name"]} за {format_currency(revenue_after_fee)}'
|
||||
})
|
||||
|
||||
|
||||
@@ -1790,80 +2211,103 @@ def end_action_phase(room_code):
|
||||
|
||||
|
||||
def calculate_and_update_market(room_id):
|
||||
"""Расчет реакции рынка (в фоновом потоке)"""
|
||||
"""Расчет реакции рынка с использованием баланса"""
|
||||
import time
|
||||
import random
|
||||
|
||||
# Ждем 5 секунд перед расчетом
|
||||
time.sleep(5)
|
||||
|
||||
room = GameRoom.query.get(room_id)
|
||||
if not room or room.status != 'playing':
|
||||
return
|
||||
|
||||
# Загружаем баланс комнаты
|
||||
balance = BalanceConfig.load_balance_mode(room.balance_mode)
|
||||
assets_config = balance.get_assets_config()
|
||||
economy_config = balance.get_economy_config()
|
||||
|
||||
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, asset_data in assets.items():
|
||||
if asset_id not in assets_config:
|
||||
continue
|
||||
|
||||
# Считаем спрос по каждому активу
|
||||
for asset_id in assets.keys():
|
||||
demand = 0
|
||||
# Считаем общее количество активов у игроков
|
||||
total_held = 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)
|
||||
for pa in player_assets:
|
||||
if pa.get('id') == asset_id:
|
||||
total_held += pa.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)
|
||||
# Коэффициент спроса
|
||||
total_supply = asset_data.get('total_quantity')
|
||||
if total_supply is not None:
|
||||
demand_factor = total_held / total_supply if total_supply > 0 else 0
|
||||
else:
|
||||
demand_factor = total_held / (len(players) * 100) if players else 0 # Для безлимитных
|
||||
|
||||
# Эффект перегрева
|
||||
if base_prices.get(asset_id) and new_price > base_prices[asset_id] * (1 + 2 * volatility):
|
||||
new_price *= random.uniform(0.7, 0.9)
|
||||
demand_factor = min(demand_factor, 1.0) # Ограничиваем
|
||||
|
||||
assets[asset_id]['price'] = new_price
|
||||
# Рассчитываем новую цену
|
||||
new_price = AssetsConfig.calculate_price(
|
||||
asset_data,
|
||||
demand_factor,
|
||||
room.current_month,
|
||||
len(players)
|
||||
)
|
||||
|
||||
asset_data['price'] = new_price
|
||||
|
||||
# Применяем корреляции - ИСПРАВЛЕНО
|
||||
correlations = economy_config.get('ASSET_CORRELATIONS', {})
|
||||
for corr_key, corr_value in correlations.items():
|
||||
# Разбираем ключ вида "asset1:asset2"
|
||||
if ':' in corr_key:
|
||||
asset1, asset2 = corr_key.split(':', 1)
|
||||
if asset1 in assets and asset2 in assets:
|
||||
price2 = assets[asset2]['price']
|
||||
base2 = assets_config.get(asset2, {}).get('base_price', price2)
|
||||
change2 = (price2 / base2) - 1 if base2 > 0 else 0
|
||||
assets[asset1]['price'] *= (1 + corr_value * change2)
|
||||
|
||||
# Обновляем данные рынка
|
||||
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)
|
||||
|
||||
# Получаем длительность фазы из конфига
|
||||
game_config = balance.get_game_config()
|
||||
phase_duration = game_config.get('PHASE_DURATIONS', {}).get('event', 30)
|
||||
|
||||
game_state.phase_end = datetime.utcnow() + timedelta(seconds=phase_duration)
|
||||
|
||||
db.session.commit()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user