from fastapi import FastAPI, Depends, HTTPException from sqlalchemy.orm import Session from pydantic import BaseModel from typing import Optional, List import sys import os sys.path.append(os.path.join(os.path.dirname(__file__), "../../../shared")) from database.connection import get_db from database.models import GameSession, Player, GameAnswer, Question from utils.scoring import ScoringEngine from websocket.socket_manager import socket_manager app = FastAPI(title="Game Service", version="1.0.0") class StartGameRequest(BaseModel): player_name: str class AnswerSubmissionRequest(BaseModel): session_id: int question_id: int answer: str hint_used: bool = False class GameSessionResponse(BaseModel): id: int player_id: int total_score: int questions_answered: int time_remaining: Optional[int] = None current_question: Optional[dict] = None @app.get("/health") def health_check(): return {"status": "healthy", "service": "game-service"} @app.post("/start", response_model=GameSessionResponse) def start_game(request: StartGameRequest, db: Session = Depends(get_db)): player = db.query(Player).filter(Player.name == request.player_name).first() if not player: player = Player(name=request.player_name) db.add(player) db.commit() db.refresh(player) session = GameSession(player_id=player.id) db.add(session) db.commit() db.refresh(session) return GameSessionResponse( id=session.id, player_id=session.player_id, total_score=session.total_score, questions_answered=session.questions_answered, time_remaining=ScoringEngine.get_max_session_duration() ) @app.post("/answer") async def submit_answer(request: AnswerSubmissionRequest, db: Session = Depends(get_db)): session = db.query(GameSession).filter(GameSession.id == request.session_id).first() if not session: raise HTTPException(status_code=404, detail="Game session not found") question = db.query(Question).filter(Question.id == request.question_id).first() if not question: raise HTTPException(status_code=404, detail="Question not found") existing_attempts = db.query(GameAnswer).filter( GameAnswer.session_id == request.session_id, GameAnswer.question_id == request.question_id ).count() if existing_attempts >= ScoringEngine.MAX_ATTEMPTS: raise HTTPException(status_code=400, detail="Maximum attempts reached") attempt_number = existing_attempts + 1 is_correct = request.answer.lower().strip() == question.correct_answer.lower().strip() points = ScoringEngine.calculate_points(is_correct, request.hint_used, attempt_number) answer = GameAnswer( session_id=request.session_id, question_id=request.question_id, attempt_number=attempt_number, user_answer=request.answer, is_correct=is_correct, hint_used=request.hint_used, points_earned=points ) db.add(answer) if is_correct: session.total_score += points session.questions_answered += 1 db.commit() # Notify real-time subscribers try: player_name = session.player.name if session.player else "Unknown" await socket_manager.notify_answer_submitted( session_id=request.session_id, player_name=player_name, is_correct=is_correct, points_earned=points, total_score=session.total_score ) except Exception as e: print(f"WebSocket notification failed: {e}") return { "correct": is_correct, "points_earned": points, "attempts_remaining": ScoringEngine.MAX_ATTEMPTS - attempt_number, "total_score": session.total_score } @app.get("/session/{session_id}") def get_game_session(session_id: int, db: Session = Depends(get_db)): session = db.query(GameSession).filter(GameSession.id == session_id).first() if not session: raise HTTPException(status_code=404, detail="Game session not found") return GameSessionResponse( id=session.id, player_id=session.player_id, total_score=session.total_score, questions_answered=session.questions_answered ) @app.get("/leaderboard") def get_leaderboard(db: Session = Depends(get_db)): result = db.execute(""" SELECT p.name as player_name, gs.total_score, gs.questions_answered, gs.ended_at FROM game_sessions gs JOIN players p ON gs.player_id = p.id WHERE gs.ended_at IS NOT NULL ORDER BY gs.total_score DESC, gs.ended_at ASC LIMIT 10 """).fetchall() return [ { "player_name": row.player_name, "score": row.total_score, "questions_answered": row.questions_answered, "completed_at": row.ended_at } for row in result ] if __name__ == "__main__": import uvicorn uvicorn.run(app, host="0.0.0.0", port=8001)