love-bot/bot/handlers/game.py

251 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import logging
from datetime import date, timedelta
from typing import Optional, Literal
from aiogram import Router, types, F, Bot
from aiogram.filters import Command
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.utils.game_logic import determine_winner, CHOICE_NAMES_RU
from bot.utils.helpers import get_partner
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 = p1_user.username or f"Игрок {p1_user.telegram_id}"
p2_name = 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"<b>{p1_name}</b> ({p1_choice_ru}) побеждает! 🎉\n"
f"<i>({p2_name} не сделал(а) свой ход)</i>"
)
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"<b>{p2_name}</b> ({p2_choice_ru}) побеждает! 🎉\n"
f"<i>({p1_name} не сделал(а) свой ход)</i>"
)
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(Command("play"))
async def handle_play(message: types.Message, db_user: DbUser, bot: Bot):
"""Обработчик команды /play. Проверяет вчерашнюю игру и предлагает сделать ход."""
partner = await get_partner(db_user.id)
if not partner:
await message.answer("Для игры нужен партнер. Дождитесь, пока второй пользователь присоединится.")
return
try:
await check_and_resolve_yesterdays_game(db_user.id, partner.id, bot)
except Exception as e:
logging.exception(f"Error checking/resolving yesterday's game for {db_user.id} and {partner.id}")
game = await create_or_get_today_game(db_user.id, partner.id)
if not game:
await message.answer("Не удалось начать игру. Попробуйте позже.")
logging.error(f"Failed to create/get game for users {db_user.id} and {partner.id}")
return
if game.is_finished:
await message.answer("Вы уже сыграли сегодня! Приходите завтра ❤️")
return
current_player_choice = game.player1_choice if game.player1_id == db_user.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,
db_user: DbUser,
bot: Bot
):
"""Обработчик нажатия на кнопку выбора хода."""
choice: GameChoice = callback_data.choice
choice_name_ru = CHOICE_NAMES_RU.get(choice, choice)
partner = await get_partner(db_user.id)
if not partner:
await callback.answer("Ошибка: не найден партнер.", show_alert=True)
await callback.message.edit_text("Не удалось обработать ваш ход: партнер не найден.")
return
game = await create_or_get_today_game(db_user.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 {db_user.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 == db_user.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, db_user.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 {db_user.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(db_user.telegram_id, "Ошибка: не удалось получить данные игроков для завершения игры.")
await bot.send_message(partner.telegram_id, "Ошибка: не удалось получить данные игроков для завершения игры.")
return
p1_name = p1_user.username or f"Игрок {p1_user.telegram_id}"
p2_name = p2_user.username or f"Игрок {p2_user.telegram_id}"
if winner_relation == 1:
winner_db_id = updated_game.player1_id
result_text = f"<b>{p1_name}</b> ({p1_choice_ru}) побеждает <b>{p2_name}</b> ({p2_choice_ru})!\n{p1_name} сегодня любит больше! ❤️"
elif winner_relation == 2:
winner_db_id = updated_game.player2_id
result_text = f"<b>{p2_name}</b> ({p2_choice_ru}) побеждает <b>{p1_name}</b> ({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 = db_user.username or f"Игрок {db_user.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})")