""" Promo Phase — глобальное скрытое состояние "промо-фазы". Влияет на то, какой эффект дают промокоды (везёт / не везёт / нейтрально). Переключается случайно каждые 1-6 часов. """ import json import random from datetime import datetime, timedelta from pathlib import Path PHASE_FILE = Path("promo_phase.json") PHASE_CONFIG = { "LUCKY": { "weight": 0.4, "min_hours": 1, "max_hours": 4, "effect": "boost", "boost_range": (0.15, 0.35), # +15-35% к luck_multiplier "rare_chance_boost": 0.05, # +5% к шансу редкого }, "UNLUCKY": { "weight": 0.4, "min_hours": 1, "max_hours": 4, "effect": "penalty", "penalty_range": (0.10, 0.25), # -10-25% к luck_multiplier "ceiling_penalty": 0.10, # -10% к потолку }, "NEUTRAL": { "weight": 0.2, "min_hours": 1, "max_hours": 3, "effect": "none", } } def load_phase(): """Загружает текущую промо-фазу из файла""" if PHASE_FILE.exists(): try: with open(PHASE_FILE, 'r') as f: return json.load(f) except: pass return { "phase": "NEUTRAL", "until": datetime.utcnow().isoformat(), "started_at": datetime.utcnow().isoformat() } def save_phase(state: dict): """Сохраняет промо-фазу""" with open(PHASE_FILE, 'w') as f: json.dump(state, f, indent=2) def pick_next_phase() -> str: """Выбирает следующую фазу случайно, с учётом весов""" phases = list(PHASE_CONFIG.keys()) weights = [PHASE_CONFIG[p]["weight"] for p in phases] return random.choices(phases, weights=weights, k=1)[0] def get_random_duration(phase: str) -> timedelta: """Случайная длительность фазы""" config = PHASE_CONFIG.get(phase, PHASE_CONFIG["NEUTRAL"]) min_h = config["min_hours"] max_h = config["max_hours"] hours = random.uniform(min_h, max_h) return timedelta(hours=hours) def ensure_phase(): """Проверяет и при необходимости переключает фазу""" state = load_phase() until = datetime.fromisoformat(state["until"]) if datetime.utcnow() >= until: new_phase = pick_next_phase() duration = get_random_duration(new_phase) state["phase"] = new_phase state["until"] = (datetime.utcnow() + duration).isoformat() state["started_at"] = datetime.utcnow().isoformat() save_phase(state) return state["phase"] def get_current_phase() -> str: """Возвращает текущую фазу""" return ensure_phase() def get_promo_effect_for_user(user_id: int, hot_score: float = 0.0, withdrawal_ratio: float = 0.0, is_cold: bool = False) -> dict: """ Возвращает индивидуальный эффект промо-фазы для пользователя. Одинаковая фаза → разный эффект в зависимости от hot/cold/withdrawal. """ phase = get_current_phase() config = PHASE_CONFIG.get(phase, {}) effect_type = config.get("effect", "none") result = { "phase": phase, "effect": effect_type, "luck_modifier": 0.0, # additive к luck_multiplier "ceiling_modifier": 0.0, # additive к ceiling "rare_chance_modifier": 0.0, # additive к шансу редкого "description": "" } if effect_type == "boost": boost = random.uniform(*config["boost_range"]) # Холодные игроки получают БОЛЬШЕ буста (реактивация) if is_cold: boost *= 1.5 # Кто много вывел — получает МЕНЬШЕ boost *= max(0.5, 1.0 - withdrawal_ratio * 0.5) result["luck_modifier"] = boost result["rare_chance_modifier"] = config.get("rare_chance_boost", 0) result["description"] = "Промо-удача 🍀" elif effect_type == "penalty": penalty = random.uniform(*config["penalty_range"]) # Горячие (много потратили, 0 выводов) почти не чувствуют пенальти if hot_score > 10000 and withdrawal_ratio < 0.1: penalty *= 0.3 # Кто вывел много — получает ПОЛНУЮ открутку if withdrawal_ratio > 0.5: penalty *= 1.5 result["luck_modifier"] = -penalty result["ceiling_modifier"] = -config.get("ceiling_penalty", 0) result["description"] = "Промо-открутка 💀" return result def get_promo_phase_info() -> dict: """Возвращает инфо о фазе: phase, effect, next_switch, remaining_minutes.""" state = load_phase() phase = state["phase"] until = datetime.fromisoformat(state["until"]) config = PHASE_CONFIG.get(phase, {}) remaining = (until - datetime.utcnow()).total_seconds() return { "phase": phase, "effect": config.get("effect", "none"), "next_switch": until.isoformat(), "remaining_minutes": max(0, int(remaining // 60)), } def get_phase_debug_info() -> dict: """Для админки — отладка промо-фаз""" state = load_phase() phase = state["phase"] until = datetime.fromisoformat(state["until"]) started = datetime.fromisoformat(state["started_at"]) remaining = (until - datetime.utcnow()).total_seconds() return { "current_phase": phase, "started_at": started.isoformat(), "until": until.isoformat(), "remaining_seconds": max(0, int(remaining)), "config": PHASE_CONFIG.get(phase, {}) }