import logging from datetime import date, timedelta from typing import Optional, Literal, Dict, Any from aiogram import Router, types, F, Bot from aiogram.filters import Command, or_f from bot.database.models import User as DbUser, Game as DbGame, GameChoice from bot.database.db import ( create_or_get_today_game, update_game_choice, get_user_by_id, finish_game, update_streak, get_game_by_id, get_game_on_date ) from bot.keyboards.game_keyboard import get_game_choice_keyboard, GameChoiceCallback from bot.keyboards.reply_keyboards import PLAY_BUTTON_TEXT from bot.utils.game_logic import determine_winner, CHOICE_NAMES_RU from bot.utils.helpers import get_partner from bot.display_names import DISPLAY_NAMES router = Router() async def check_and_resolve_yesterdays_game(user_id: int, partner_id: int, bot: Bot) -> bool: """ Проверяет наличие незавершенной игры за вчерашний день и завершает ее по правилам. Возвращает True, если игра была найдена и обработана, иначе False. """ yesterday = date.today() - timedelta(days=1) game = await get_game_on_date(user_id, partner_id, yesterday) if not game or game.is_finished: return False logging.info(f"Found unfinished game from yesterday ({yesterday}) for users {user_id} and {partner_id}. Resolving...") p1_id = game.player1_id p2_id = game.player2_id p1_choice = game.player1_choice p2_choice = game.player2_choice p1_user = await get_user_by_id(p1_id) p2_user = await get_user_by_id(p2_id) if not p1_user or not p2_user: logging.error(f"Could not find user data for yesterday's game {game.id}") await finish_game(game.id, None) return True p1_name = DISPLAY_NAMES.get(p1_user.telegram_id, p1_user.username or f"Пользователь {p1_user.telegram_id}") p2_name = DISPLAY_NAMES.get(p2_user.telegram_id, p2_user.username or f"Пользователь {p2_user.telegram_id}") winner_db_id: Optional[int] = None result_message: str = "" p1_win: bool = False p2_win: bool = False if p1_choice and not p2_choice: winner_db_id = p1_id p1_choice_ru = CHOICE_NAMES_RU.get(p1_choice, p1_choice) result_message = ( f"⏳ Вчерашняя игра ({yesterday}):\n" f"{p1_name} ({p1_choice_ru}) побеждает! 🎉\n" f"({p2_name} не сделал(а) свой ход)" ) p1_win = True p2_win = False logging.info(f"Yesterday's game {game.id}: Player 1 ({p1_id}) wins by default.") elif not p1_choice and p2_choice: winner_db_id = p2_id p2_choice_ru = CHOICE_NAMES_RU.get(p2_choice, p2_choice) result_message = ( f"⏳ Вчерашняя игра ({yesterday}):\n" f"{p2_name} ({p2_choice_ru}) побеждает! 🎉\n" f"({p1_name} не сделал(а) свой ход)" ) p1_win = False p2_win = True logging.info(f"Yesterday's game {game.id}: Player 2 ({p2_id}) wins by default.") else: winner_db_id = None result_message = ( f"⏳ Вчерашняя игра ({yesterday}):\n" f"Ничья! Игра аннулирована, так как не все сделали ход. 🤷‍♀️🤷‍♂️" ) p1_win = False p2_win = False logging.info(f"Yesterday's game {game.id}: Draw (at least one player did not choose).") await finish_game(game.id, winner_db_id) await update_streak(p1_id, win=p1_win) await update_streak(p2_id, win=p2_win) full_result_message = result_message + "\n\nНачинаем игру на сегодня! /play" try: await bot.send_message(p1_user.telegram_id, full_result_message, parse_mode="HTML") except Exception as e: logging.error(f"Failed to send yesterday result to user {p1_user.telegram_id}: {e}") try: await bot.send_message(p2_user.telegram_id, full_result_message, parse_mode="HTML") except Exception as e: logging.error(f"Failed to send yesterday result to user {p2_user.telegram_id}: {e}") return True @router.message(or_f(Command("play"), F.text == PLAY_BUTTON_TEXT)) async def handle_play( message: types.Message, bot: Bot, **kwargs ): """Обработчик команды /play. Проверяет вчерашнюю игру и предлагает сделать ход.""" user_db_obj: Optional[DbUser] = kwargs.get('user_db_obj') if not user_db_obj: logging.error(f"Error in handle_play: user_db_obj not found in kwargs for user {message.from_user.id}") await message.answer("Произошла ошибка при получении ваших данных.") return partner = await get_partner(user_db_obj.id) if not partner: await message.answer("Для игры нужен партнер. Дождитесь, пока второй пользователь присоединится.") return try: await check_and_resolve_yesterdays_game(user_db_obj.id, partner.id, bot) except Exception as e: logging.exception(f"Error checking/resolving yesterday's game for {user_db_obj.id} and {partner.id}") game = await create_or_get_today_game(user_db_obj.id, partner.id) if not game: await message.answer("Не удалось начать игру. Попробуйте позже.") logging.error(f"Failed to create/get game for users {user_db_obj.id} and {partner.id}") return if game.is_finished: await message.answer("Вы уже сыграли сегодня! Приходите завтра ❤️") return current_player_choice = game.player1_choice if game.player1_id == user_db_obj.id else game.player2_choice if current_player_choice: choice_name_ru = CHOICE_NAMES_RU.get(current_player_choice, current_player_choice) await message.answer(f"Вы уже сделали свой ход сегодня ({choice_name_ru}). Ожидаем хода партнера! 😉") return await message.answer("Кто больше любит сегодня? 😉 Сделайте свой выбор:", reply_markup=get_game_choice_keyboard()) @router.callback_query(GameChoiceCallback.filter()) async def handle_game_choice( callback: types.CallbackQuery, callback_data: GameChoiceCallback, bot: Bot, **kwargs ): """Обработчик нажатия на кнопку выбора хода.""" user_db_obj: Optional[DbUser] = kwargs.get('user_db_obj') if not user_db_obj: logging.error(f"Error in handle_game_choice: user_db_obj not found in kwargs for user {callback.from_user.id}") await callback.answer("Произошла ошибка при получении ваших данных.", show_alert=True) try: await callback.message.edit_text("Произошла ошибка при обработке вашего выбора.") except Exception: pass return choice: GameChoice = callback_data.choice choice_name_ru = CHOICE_NAMES_RU.get(choice, choice) partner = await get_partner(user_db_obj.id) if not partner: await callback.answer("Ошибка: не найден партнер.", show_alert=True) await callback.message.edit_text("Не удалось обработать ваш ход: партнер не найден.") return game = await create_or_get_today_game(user_db_obj.id, partner.id) if not game: await callback.answer("Ошибка: не найдена текущая игра.", show_alert=True) await callback.message.edit_text("Не удалось обработать ваш ход: игра не найдена.") logging.error(f"Game not found for users {user_db_obj.id} and {partner.id} during callback") return if game.is_finished: await callback.answer("Игра уже завершена.", show_alert=True) await callback.message.edit_text("Вы опоздали, игра на сегодня уже закончилась!" ) return player_field = "player1_choice" if game.player1_id == user_db_obj.id else "player2_choice" current_choice = getattr(game, player_field) if current_choice is not None: current_choice_ru = CHOICE_NAMES_RU.get(current_choice, current_choice) await callback.answer("Вы уже сделали свой ход.", show_alert=True) await callback.message.edit_text(f"Вы уже выбрали: {current_choice_ru}\nОжидаем ход партнера... ✨") return updated = await update_game_choice(game.id, user_db_obj.id, choice) if not updated: await callback.answer("Ошибка: не удалось сохранить ваш выбор.", show_alert=True) await callback.message.edit_text("Произошла ошибка при сохранении вашего хода. Попробуйте еще раз.") logging.error(f"Failed to update choice for game {game.id}, user {user_db_obj.id}") return await callback.answer(f"Вы выбрали: {choice_name_ru}") await callback.message.edit_text(f"Вы выбрали: {choice_name_ru}\nОжидаем ход партнера... ✨") updated_game = await get_game_by_id(game.id) if not updated_game: logging.error(f"Failed to fetch updated game {game.id} after choice update.") return if updated_game.player1_choice and updated_game.player2_choice: winner_relation = determine_winner(updated_game.player1_choice, updated_game.player2_choice) winner_db_id: Optional[int] = None result_text = "" p1_choice_ru = CHOICE_NAMES_RU[updated_game.player1_choice] p2_choice_ru = CHOICE_NAMES_RU[updated_game.player2_choice] p1_user = await get_user_by_id(updated_game.player1_id) p2_user = await get_user_by_id(updated_game.player2_id) if not p1_user or not p2_user: logging.error(f"Could not find user data for game {updated_game.id}") await bot.send_message(user_db_obj.telegram_id, "Ошибка: не удалось получить данные игроков для завершения игры.") await bot.send_message(partner.telegram_id, "Ошибка: не удалось получить данные игроков для завершения игры.") return p1_name = DISPLAY_NAMES.get(p1_user.telegram_id, p1_user.username or f"Пользователь {p1_user.telegram_id}") p2_name = DISPLAY_NAMES.get(p2_user.telegram_id, p2_user.username or f"Пользователь {p2_user.telegram_id}") if winner_relation == 1: winner_db_id = updated_game.player1_id result_text = f"{p1_name} ({p1_choice_ru}) побеждает {p2_name} ({p2_choice_ru})!\n{p1_name} сегодня любит больше! ❤️" elif winner_relation == 2: winner_db_id = updated_game.player2_id result_text = f"{p2_name} ({p2_choice_ru}) побеждает {p1_name} ({p1_choice_ru})!\n{p2_name} сегодня любит больше! ❤️" else: result_text = f"Ничья! Оба выбрали {p1_choice_ru}.\nСегодня вы любите друг друга одинаково сильно! 🥰" await finish_game(updated_game.id, winner_db_id) await update_streak(updated_game.player1_id, win=(winner_relation == 1)) await update_streak(updated_game.player2_id, win=(winner_relation == 2)) final_message = f"Игра за {updated_game.game_date.strftime('%d.%m.%Y')} завершена! 🎉\n\n{result_text}\n\nПосмотреть статистику: /stats" try: await bot.send_message(p1_user.telegram_id, final_message, parse_mode="HTML") except Exception as e: logging.error(f"Failed to send result to user {p1_user.telegram_id}: {e}") try: await bot.send_message(p2_user.telegram_id, final_message, parse_mode="HTML") except Exception as e: logging.error(f"Failed to send result to user {p2_user.telegram_id}: {e}") else: partner_user = await get_user_by_id(partner.id) if partner_user: current_user_name = DISPLAY_NAMES.get(user_db_obj.telegram_id, user_db_obj.username or f"Пользователь {user_db_obj.telegram_id}") try: await bot.send_message( partner_user.telegram_id, f"Ваш партнер, {current_user_name}, сделал свой ход! 😉\nТеперь ваша очередь решить, кто любит больше! Используйте команду /play, чтобы сделать ход." ) except Exception as e: logging.error(f"Failed to notify partner {partner_user.telegram_id}: {e}") else: logging.error(f"Could not find partner user data for notification (partner_id: {partner.id})")