import json from fastapi import APIRouter, Request, HTTPException from auth import require_auth from database import get_pool router = APIRouter(prefix="/game", tags=["game"]) @router.get("/progress") async def get_progress(request: Request): telegram_id, _ = await require_auth(request) pool = await get_pool() async with pool.acquire() as conn: user = await conn.fetchrow( "SELECT last_completed_level, current_level FROM users WHERE telegram_id = $1", telegram_id ) if not user: return { "last_completed_level": 0, "total_score": 0, "current_level": "A1" } current_level = user["current_level"] or "A1" # Очки из филвордов wordsearch_score = await conn.fetchval( "SELECT COALESCE(SUM(score), 0) FROM user_progress WHERE user_id = $1", telegram_id ) # Очки из предложений sentence_score = await conn.fetchval( "SELECT COALESCE(SUM(score), 0) FROM sentence_progress WHERE user_id = $1", telegram_id ) # Очки из квиза wordquiz_score = await conn.fetchval( "SELECT COALESCE(total_score, 0) FROM word_quiz_progress WHERE user_id = $1", telegram_id ) # Очки из Bubble Words bubblewords_score = await conn.fetchval( "SELECT COALESCE(total_score, 0) FROM bubble_words_progress WHERE user_id = $1", telegram_id ) total_score = (wordsearch_score or 0) + (sentence_score or 0) + (wordquiz_score or 0) + (bubblewords_score or 0) return { "last_completed_level": user["last_completed_level"] or 0, "total_score": total_score, "current_level": current_level } @router.get("/level/{level_number}") async def get_level(request: Request, level_number: int): telegram_id, _ = await require_auth(request) pool = await get_pool() async with pool.acquire() as conn: user = await conn.fetchrow( "SELECT app_language, current_level, last_completed_level FROM users WHERE telegram_id = $1", telegram_id ) if not user: raise HTTPException(401, "User not registered") lang = user["app_language"] or "pl" current_level = user["current_level"] or "A1" last_completed = user["last_completed_level"] or 0 # Проверяем, что уровень доступен (не дальше следующего за пройденным) if level_number > 1 and level_number > last_completed + 1: raise HTTPException(403, "Level not available yet. Complete previous level first.") # Получаем уровень по его номеру ВНУТРИ выбранного уровня level = await conn.fetchrow( "SELECT * FROM levels WHERE level = $1 AND level_number = $2", current_level, level_number ) if not level: raise HTTPException(404, "Level not found") words = await conn.fetch( "SELECT * FROM level_words WHERE level_id = $1", level["id"] ) progress = await conn.fetchrow( "SELECT * FROM user_progress WHERE user_id = $1 AND level_id = $2", telegram_id, level["id"], ) word_list = [] for w in words: translation = w.get(f"translation_{lang}", "") or w.get("translation_pl", "") or w.get("translation_en", "") word_list.append({ "word_pl": w["word_pl"], "translation": translation, }) grid_letters = level["grid_letters"] if isinstance(grid_letters, str): grid_letters = json.loads(grid_letters) word_paths = level["word_paths"] if isinstance(word_paths, str): word_paths = json.loads(word_paths) return { "level": { "id": level["id"], "level_number": level["level_number"], "grid_size": level["grid_size"], "grid_letters": grid_letters, "word_paths": word_paths, "level": level["level"], }, "words": word_list, "progress": { "completed": progress is not None, "hints_used": progress["hints_used"] if progress else 0, "score": progress["score"] if progress else 0, }, } @router.post("/check-word") async def check_word(request: Request): telegram_id, _ = await require_auth(request) body = await request.json() level_id = body.get("level_id") selected_cells = body.get("cells", []) if not level_id or not selected_cells: raise HTTPException(400, "level_id and cells required") pool = await get_pool() found_word = None perfect_count = body.get("perfect_streak", 0) async with pool.acquire() as conn: level = await conn.fetchrow("SELECT * FROM levels WHERE id = $1", level_id) if not level: raise HTTPException(404, "Level not found") word_paths = level["word_paths"] if isinstance(word_paths, str): word_paths = json.loads(word_paths) grid_letters = level["grid_letters"] if isinstance(grid_letters, str): grid_letters = json.loads(grid_letters) selected_word = '' for cell in selected_cells: r, c = cell[0], cell[1] try: selected_word += grid_letters[r][c] except (IndexError, TypeError): continue selected_word = selected_word.lower() for word in word_paths.keys(): if word.lower() == selected_word: found_word = word break # 🔧 ИСПРАВЛЕНО: вызов достижений ВНЕ блока async with pool.acquire() if found_word: from routers.achievements import check_and_award_achievements new_achievements = [] if perfect_count > 0: ach = await check_and_award_achievements(telegram_id, "perfect_words", perfect_count) new_achievements.extend(ach) return { "status": "ok", "found": True, "word": found_word, "new_achievements": new_achievements } return {"status": "ok", "found": False} @router.post("/complete-level") async def complete_level(request: Request): """Завершение уровня с проверкой на читерство и выдачей достижений""" telegram_id, _ = await require_auth(request) body = await request.json() level_id = body.get("level_id") hints_used = body.get("hints_used", 0) completion_time = body.get("completion_time") perfect_streak = body.get("perfect_streak", 0) if not level_id: raise HTTPException(400, "level_id required") hints_used = min(max(0, hints_used), 3) # Защита от читерства: минимальное время 5 секунд if completion_time is not None and completion_time < 5: raise HTTPException(400, "Completion time too fast. Minimum 5 seconds required.") pool = await get_pool() # 🔧 ИСПРАВЛЕНО: все проверки и запись прогресса — внутри одного блока # все данные для достижений собираем здесь, а сами вызовы делаем снаружи already_completed = False existing_score = 0 score = 0 levels_completed = 0 grand_total = 0 async with pool.acquire() as conn: user = await conn.fetchrow( "SELECT id, telegram_id, last_completed_level, current_level FROM users WHERE telegram_id = $1", telegram_id ) if not user: raise HTTPException(401, "User not registered") level = await conn.fetchrow( "SELECT id, level_number, grid_size, level FROM levels WHERE id = $1", level_id ) if not level: raise HTTPException(404, "Level not found") current_level_number = level["level_number"] level_name = level["level"] or "A1" # Проверяем, что уровень принадлежит текущему уровню пользователя if level_name != (user["current_level"] or "A1"): raise HTTPException(403, "This level does not belong to your current level") existing_progress = await conn.fetchrow( "SELECT score FROM user_progress WHERE user_id = $1 AND level_id = $2", telegram_id, level_id ) if existing_progress: already_completed = True existing_score = existing_progress["score"] else: score = max(10, 100 - hints_used * 10) await conn.execute( """INSERT INTO user_progress (user_id, level_id, completed_at, hints_used, score, level) VALUES ($1, $2, NOW(), $3, $4, $5)""", telegram_id, level_id, hints_used, score, level_name, ) # Обновляем last_completed_level (максимальный номер пройденного уровня) await conn.execute( """UPDATE users SET last_completed_level = GREATEST(last_completed_level, $2) WHERE telegram_id = $1""", telegram_id, current_level_number, ) # Собираем данные для достижений внутри блока levels_completed = await conn.fetchval( "SELECT COUNT(*) FROM user_progress WHERE user_id = $1", telegram_id ) or 0 total_score = await conn.fetchval( "SELECT COALESCE(SUM(score), 0) FROM user_progress WHERE user_id = $1", telegram_id ) or 0 sentence_score = await conn.fetchval( "SELECT COALESCE(SUM(score), 0) FROM sentence_progress WHERE user_id = $1", telegram_id ) or 0 wordquiz_score = await conn.fetchval( "SELECT COALESCE(total_score, 0) FROM word_quiz_progress WHERE user_id = $1", telegram_id ) or 0 bubblewords_score = await conn.fetchval( "SELECT COALESCE(total_score, 0) FROM bubble_words_progress WHERE user_id = $1", telegram_id ) or 0 grand_total = total_score + sentence_score + wordquiz_score + bubblewords_score # Если уровень уже был пройден — просто возвращаем результат if already_completed: return { "status": "ok", "score": existing_score, "already_completed": True, "new_achievements": [] } # 🔧 ИСПРАВЛЕНО: вызовы достижений ВНЕ блока async with pool.acquire() from routers.achievements import check_and_award_achievements new_achievements = [] ach = await check_and_award_achievements(telegram_id, "levels_completed", levels_completed) new_achievements.extend(ach) if hints_used == 0: ach = await check_and_award_achievements(telegram_id, "no_hints_level", 1) new_achievements.extend(ach) if completion_time is not None and completion_time > 0: if int(completion_time) <= 60: ach = await check_and_award_achievements(telegram_id, "speed_level", 60) new_achievements.extend(ach) ach = await check_and_award_achievements(telegram_id, "total_score", grand_total) new_achievements.extend(ach) if perfect_streak >= 5: ach = await check_and_award_achievements(telegram_id, "perfect_words", perfect_streak) new_achievements.extend(ach) return { "status": "ok", "score": score, "already_completed": False, "new_achievements": new_achievements }