main.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370
  1. from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Depends
  2. from fastapi.middleware.cors import CORSMiddleware
  3. from contextlib import asynccontextmanager
  4. import jwt
  5. import json
  6. from datetime import datetime, timedelta
  7. from typing import List, Dict, Optional
  8. import asyncio
  9. from models import *
  10. from game_logic import SetGame, Card
  11. from database import (
  12. users_db, games_db, active_connections,
  13. get_user_by_nickname, create_user, get_user_by_token,
  14. create_game, get_game, get_all_games, update_game,
  15. add_player_to_game, remove_player_from_game,
  16. broadcast_game_state
  17. )
  18. from auth import (
  19. SECRET_KEY, ALGORITHM, create_access_token,
  20. verify_password, get_password_hash, verify_token
  21. )
  22. from websocket_manager import ConnectionManager
  23. manager = ConnectionManager()
  24. @asynccontextmanager
  25. async def lifespan(app: FastAPI):
  26. print("Server starting...")
  27. yield
  28. print("Server shutting down...")
  29. await manager.disconnect_all()
  30. app = FastAPI(
  31. title="Set Game Server",
  32. description="Сервер для игры Set с WebSocket поддержкой",
  33. version="1.0.0",
  34. lifespan=lifespan
  35. )
  36. app.add_middleware(
  37. CORSMiddleware,
  38. allow_origins=["*"],
  39. allow_credentials=True,
  40. allow_methods=["*"],
  41. allow_headers=["*"],
  42. )
  43. class BaseResponse:
  44. @staticmethod
  45. def success(data: Dict = None, **kwargs):
  46. response = {
  47. "success": True,
  48. "exception": None
  49. }
  50. if data:
  51. response.update(data)
  52. response.update(kwargs)
  53. return response
  54. @staticmethod
  55. def error(message: str):
  56. return {
  57. "success": False,
  58. "exception": {
  59. "message": message
  60. }
  61. }
  62. # Эндпоинты
  63. @app.post("/user/register", response_model=RegisterResponse)
  64. async def register(user: RegisterRequest):
  65. if get_user_by_nickname(user.nickname):
  66. raise HTTPException(
  67. status_code=400,
  68. detail=BaseResponse.error("User already exists")
  69. )
  70. access_token = create_access_token(data={"sub": user.nickname})
  71. create_user(user.nickname, user.password, access_token)
  72. return BaseResponse.success(
  73. nickname=user.nickname,
  74. accessToken=access_token
  75. )
  76. @app.post("/set/room/create")
  77. async def create_game_room(request: GameRequest):
  78. user = get_user_by_token(request.accessToken)
  79. if not user:
  80. raise HTTPException(
  81. status_code=401,
  82. detail=BaseResponse.error("Invalid token")
  83. )
  84. game_id = create_game(request.accessToken)
  85. return BaseResponse.success(gameId=game_id)
  86. @app.post("/set/room/list")
  87. async def list_games(request: GameRequest):
  88. user = get_user_by_token(request.accessToken)
  89. if not user:
  90. raise HTTPException(
  91. status_code=401,
  92. detail=BaseResponse.error("Invalid token")
  93. )
  94. games = get_all_games()
  95. games_list = [{"id": game_id} for game_id in games.keys()]
  96. return BaseResponse.success(games=games_list)
  97. @app.post("/set/room/enter")
  98. async def enter_game(request: EnterGameRequest):
  99. user = get_user_by_token(request.accessToken)
  100. if not user:
  101. raise HTTPException(
  102. status_code=401,
  103. detail=BaseResponse.error("Invalid token")
  104. )
  105. game = get_game(request.gameId)
  106. if not game:
  107. raise HTTPException(
  108. status_code=404,
  109. detail=BaseResponse.error("Game not found")
  110. )
  111. success = add_player_to_game(request.gameId, request.accessToken, user["nickname"])
  112. if not success:
  113. raise HTTPException(
  114. status_code=400,
  115. detail=BaseResponse.error("Cannot join game")
  116. )
  117. await broadcast_game_state(request.gameId)
  118. return BaseResponse.success(gameId=request.gameId)
  119. @app.post("/set/field")
  120. async def get_game_field(request: GameRequest):
  121. user = get_user_by_token(request.accessToken)
  122. if not user:
  123. raise HTTPException(
  124. status_code=401,
  125. detail=BaseResponse.error("Invalid token")
  126. )
  127. game = None
  128. for game_id, game_data in games_db.items():
  129. if request.accessToken in game_data["players"]:
  130. game = game_data
  131. break
  132. if not game:
  133. raise HTTPException(
  134. status_code=400,
  135. detail=BaseResponse.error("User not in game")
  136. )
  137. field_cards = game["game"].get_field()
  138. score = game["players"].get(request.accessToken, {}).get("score", 0)
  139. status = "ongoing" if not game["game"].is_game_over() else "ended"
  140. return {
  141. "cards": field_cards,
  142. "status": status,
  143. "score": score
  144. }
  145. @app.post("/set/pick")
  146. async def pick_cards(request: PickRequest):
  147. user = get_user_by_token(request.accessToken)
  148. if not user:
  149. raise HTTPException(
  150. status_code=401,
  151. detail=BaseResponse.error("Invalid token")
  152. )
  153. game = None
  154. game_id = None
  155. for g_id, game_data in games_db.items():
  156. if request.accessToken in game_data["players"]:
  157. game = game_data
  158. game_id = g_id
  159. break
  160. if not game:
  161. raise HTTPException(
  162. status_code=400,
  163. detail=BaseResponse.error("User not in game")
  164. )
  165. is_set = game["game"].check_set(request.cards)
  166. if is_set:
  167. game["game"].remove_cards(request.cards)
  168. current_score = game["players"][request.accessToken].get("score", 0)
  169. game["players"][request.accessToken]["score"] = current_score + 1
  170. update_game(game_id, game)
  171. await broadcast_game_state(game_id)
  172. score = game["players"][request.accessToken].get("score", 0)
  173. return {
  174. "isSet": is_set,
  175. "score": score
  176. }
  177. @app.post("/set/add")
  178. async def add_cards(request: GameRequest):
  179. user = get_user_by_token(request.accessToken)
  180. if not user:
  181. raise HTTPException(
  182. status_code=401,
  183. detail=BaseResponse.error("Invalid token")
  184. )
  185. game = None
  186. game_id = None
  187. for g_id, game_data in games_db.items():
  188. if request.accessToken in game_data["players"]:
  189. game = game_data
  190. game_id = g_id
  191. break
  192. if not game:
  193. raise HTTPException(
  194. status_code=400,
  195. detail=BaseResponse.error("User not in game")
  196. )
  197. game["game"].add_cards(3)
  198. update_game(game_id, game)
  199. await broadcast_game_state(game_id)
  200. return BaseResponse.success()
  201. @app.post("/set/scores")
  202. async def get_scores(request: GameRequest):
  203. user = get_user_by_token(request.accessToken)
  204. if not user:
  205. raise HTTPException(
  206. status_code=401,
  207. detail=BaseResponse.error("Invalid token")
  208. )
  209. game = None
  210. for game_data in games_db.values():
  211. if request.accessToken in game_data["players"]:
  212. game = game_data
  213. break
  214. if not game:
  215. raise HTTPException(
  216. status_code=400,
  217. detail=BaseResponse.error("User not in game")
  218. )
  219. users = []
  220. for player_data in game["players"].values():
  221. users.append({
  222. "name": player_data["nickname"],
  223. "score": player_data.get("score", 0)
  224. })
  225. return BaseResponse.success(users=users)
  226. @app.websocket("/set")
  227. async def websocket_endpoint(websocket: WebSocket):
  228. await manager.connect(websocket)
  229. try:
  230. while True:
  231. data = await websocket.receive_text()
  232. message = json.loads(data)
  233. access_token = message.get("accessToken")
  234. if not access_token:
  235. await websocket.send_text(json.dumps(
  236. BaseResponse.error("Token required")
  237. ))
  238. continue
  239. user = get_user_by_token(access_token)
  240. if not user:
  241. await websocket.send_text(json.dumps(
  242. BaseResponse.error("Invalid token")
  243. ))
  244. continue
  245. game_id = None
  246. for g_id, game_data in games_db.items():
  247. if access_token in game_data["players"]:
  248. game_id = g_id
  249. break
  250. if not game_id:
  251. await websocket.send_text(json.dumps(
  252. BaseResponse.error("User not in game")
  253. ))
  254. continue
  255. manager.register_user(websocket, access_token, game_id)
  256. game = games_db[game_id]
  257. field_cards = game["game"].get_field()
  258. status = "ongoing" if not game["game"].is_game_over() else "ended"
  259. score = game["players"][access_token].get("score", 0)
  260. await websocket.send_text(json.dumps({
  261. "type": "game_state",
  262. "cards": field_cards,
  263. "status": status,
  264. "score": score
  265. }))
  266. except WebSocketDisconnect:
  267. await manager.disconnect(websocket)
  268. except Exception as e:
  269. print(f"WebSocket error: {e}")
  270. await manager.disconnect(websocket)
  271. @app.get("/health")
  272. async def health_check():
  273. return {
  274. "status": "healthy",
  275. "timestamp": datetime.now().isoformat(),
  276. "users_count": len(users_db),
  277. "games_count": len(games_db)
  278. }
  279. @app.get("/")
  280. async def root():
  281. return {
  282. "message": "Set Game Server",
  283. "version": "1.0.0",
  284. "docs": "/docs",
  285. "endpoints": [
  286. "/user/register",
  287. "/set/room/create",
  288. "/set/room/list",
  289. "/set/room/enter",
  290. "/set/field",
  291. "/set/pick",
  292. "/set/add",
  293. "/set/scores",
  294. "/set (WebSocket)"
  295. ]
  296. }
  297. if __name__ == "__main__":
  298. import uvicorn
  299. uvicorn.run(app, host="0.0.0.0", port=8000)