130 lines
5.0 KiB
Python
130 lines
5.0 KiB
Python
import json
|
|
import asyncio
|
|
import random
|
|
import math
|
|
from typing import Set, Dict, Optional
|
|
from fastapi import WebSocket
|
|
from datetime import datetime
|
|
|
|
|
|
class ConnectionManager:
|
|
def __init__(self):
|
|
self.active_connections: Dict[int, Set[WebSocket]] = {}
|
|
self._lock = asyncio.Lock()
|
|
|
|
async def connect(self, websocket: WebSocket, user_id: int):
|
|
await websocket.accept()
|
|
async with self._lock:
|
|
if user_id not in self.active_connections:
|
|
self.active_connections[user_id] = set()
|
|
self.active_connections[user_id].add(websocket)
|
|
|
|
async def disconnect(self, websocket: WebSocket, user_id: int):
|
|
async with self._lock:
|
|
if user_id in self.active_connections:
|
|
self.active_connections[user_id].discard(websocket)
|
|
if not self.active_connections[user_id]:
|
|
del self.active_connections[user_id]
|
|
|
|
async def send_personal(self, user_id: int, message: dict):
|
|
async with self._lock:
|
|
if user_id in self.active_connections:
|
|
dead = set()
|
|
for ws in self.active_connections[user_id]:
|
|
try:
|
|
await ws.send_json(message)
|
|
except Exception:
|
|
dead.add(ws)
|
|
self.active_connections[user_id] -= dead
|
|
if not self.active_connections[user_id]:
|
|
del self.active_connections[user_id]
|
|
|
|
async def broadcast(self, message: dict):
|
|
async with self._lock:
|
|
dead: Dict[int, Set[WebSocket]] = {}
|
|
for uid, sockets in self.active_connections.items():
|
|
for ws in sockets:
|
|
try:
|
|
await ws.send_json(message)
|
|
except Exception:
|
|
dead.setdefault(uid, set()).add(ws)
|
|
for uid, sockets in dead.items():
|
|
self.active_connections[uid] -= sockets
|
|
if not self.active_connections[uid]:
|
|
del self.active_connections[uid]
|
|
|
|
async def broadcast_except(self, exclude_user_id: int, message: dict):
|
|
async with self._lock:
|
|
for uid, sockets in self.active_connections.items():
|
|
if uid == exclude_user_id:
|
|
continue
|
|
for ws in sockets:
|
|
try:
|
|
await ws.send_json(message)
|
|
except Exception:
|
|
pass
|
|
|
|
@property
|
|
def online_count(self) -> int:
|
|
"""Количество уникальных пользователей онлайн"""
|
|
return len(self.active_connections)
|
|
|
|
|
|
manager = ConnectionManager()
|
|
|
|
|
|
# ─── Crash Game State ────────────────────────────────────────────────────────
|
|
|
|
class CrashGameState:
|
|
def __init__(self):
|
|
self.running = False
|
|
self.current_multiplier = 1.0
|
|
self.crash_point = 1.0
|
|
self.bets: Dict[int, float] = {}
|
|
self.cashed_out: Dict[int, float] = {}
|
|
self.phase = "waiting"
|
|
self.timer = 0
|
|
self.round = 0
|
|
self.history: list = []
|
|
self._start_time = 0.0
|
|
self._elapsed = 0.0
|
|
|
|
def generate_crash_point(self) -> float:
|
|
"""True CS2-style provably fair crash point using exponential distribution"""
|
|
# Используем распределение: чем выше множитель, тем меньше шанс
|
|
r = random.random()
|
|
# Формула: crash_point = 1 / (1 - r) с ограничением
|
|
# Это даёт экспоненциальное распределение
|
|
crash_point = 1.0 / (1.0 - r * 0.99)
|
|
# В редких случаях может быть очень высоким (>1000), ограничиваем
|
|
crash_point = min(crash_point, 1000.0)
|
|
return round(crash_point, 2)
|
|
|
|
def get_multiplier_at_time(self, elapsed_seconds: float) -> float:
|
|
"""Экспоненциальный рост множителя: e^(t * speed)"""
|
|
speed = 0.08 # скорость роста
|
|
return round(math.exp(elapsed_seconds * speed), 2)
|
|
|
|
def get_time_for_multiplier(self, mult: float) -> float:
|
|
"""Сколько времени нужно для достижения множителя"""
|
|
return math.log(mult) / 0.08
|
|
|
|
def calculate_payout(self, bet_amount: float, cashout_at: float) -> float:
|
|
return round(bet_amount * cashout_at, 2)
|
|
|
|
def to_dict(self):
|
|
return {
|
|
"phase": self.phase,
|
|
"round": self.round,
|
|
"current_multiplier": self.current_multiplier,
|
|
"crash_point": self.crash_point,
|
|
"timer": self.timer,
|
|
"bet_count": len(self.bets),
|
|
"total_bets": sum(self.bets.values()),
|
|
"cashed_out": {str(k): v for k, v in self.cashed_out.items()},
|
|
"history": self.history[-20:],
|
|
}
|
|
|
|
|
|
crash_game = CrashGameState()
|