211 lines
9.1 KiB
Python
211 lines
9.1 KiB
Python
from aiogram import Bot, Dispatcher, types, F
|
||
from aiogram.filters import Command
|
||
from aiogram.types import WebAppInfo, KeyboardButton, ReplyKeyboardMarkup
|
||
from bot.database import Database
|
||
import json
|
||
import asyncio
|
||
from bot.config import BOT_TOKEN, WEBAPP_URL
|
||
import logging
|
||
|
||
# Настройка логирования
|
||
logging.basicConfig(
|
||
level=logging.INFO,
|
||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
||
)
|
||
logger = logging.getLogger(__name__)
|
||
|
||
# Инициализация бота и диспетчера
|
||
bot = Bot(token=BOT_TOKEN)
|
||
dp = Dispatcher()
|
||
db = Database()
|
||
|
||
# Упрощенная клавиатура
|
||
def get_main_keyboard():
|
||
keyboard = [
|
||
[
|
||
KeyboardButton(
|
||
text="Записать броски 🎲",
|
||
web_app=WebAppInfo(url=WEBAPP_URL)
|
||
)
|
||
],
|
||
[KeyboardButton(text="Статистика 📊")]
|
||
]
|
||
return ReplyKeyboardMarkup(keyboard=keyboard, resize_keyboard=True)
|
||
|
||
@dp.message(Command("start"))
|
||
async def start_command(message: types.Message):
|
||
logger.info(f"User {message.from_user.id} ({message.from_user.username}) started the bot")
|
||
await message.answer(
|
||
"🎲 Добро пожаловать в счётчик кубиков для нард!\n\n"
|
||
"Нажмите «Записать броски» чтобы начать новую игру.",
|
||
reply_markup=get_main_keyboard()
|
||
)
|
||
|
||
# Обновляем функцию подсчета статистики в handle_webapp_data
|
||
def calculate_throw_sum(throw):
|
||
dice1, dice2 = throw['dice']
|
||
base_sum = (dice1 + dice2) * 2 if dice1 == dice2 else dice1 + dice2
|
||
return base_sum - (throw.get('unusedPoints', 0))
|
||
|
||
@dp.message(F.web_app_data)
|
||
async def handle_webapp_data(message: types.Message):
|
||
try:
|
||
user = message.from_user
|
||
logger.info(f"Received webapp data from user {user.id} ({user.username})")
|
||
|
||
data = json.loads(message.web_app_data.data)
|
||
|
||
if data['type'] == 'game_session':
|
||
# Создаем новую игру
|
||
game_id = db.create_game(user.id, user.username)
|
||
logger.info(f"Created new game {game_id} for user {user.id}")
|
||
|
||
# Записываем все броски
|
||
throws = data['throws']
|
||
for throw in throws:
|
||
db.add_throw(
|
||
game_id,
|
||
throw['dice'][0],
|
||
throw['dice'][1],
|
||
throw.get('unusedPoints', 0)
|
||
)
|
||
|
||
# Завершаем игру
|
||
db.end_game(game_id)
|
||
logger.info(f"Game {game_id} completed with {len(throws)} throws")
|
||
|
||
# Анализ комбинаций
|
||
combinations = {}
|
||
for throw in throws:
|
||
dice = sorted([throw['dice'][0], throw['dice'][1]])
|
||
key = f"{dice[0]}-{dice[1]}"
|
||
combinations[key] = combinations.get(key, 0) + 1
|
||
|
||
# Сортируем комбинации по частоте
|
||
sorted_combinations = sorted(
|
||
combinations.items(),
|
||
key=lambda x: (-x[1], x[0]) # Сортировка по убыванию частоты, потом по комбинации
|
||
)
|
||
|
||
# Подсчет статистики
|
||
total_throws = len(throws)
|
||
total_base_sum = sum((t['dice'][0] + t['dice'][1]) * 2 if t['dice'][0] == t['dice'][1] else t['dice'][0] + t['dice'][1] for t in throws)
|
||
total_final_sum = sum(calculate_throw_sum(t) for t in throws)
|
||
avg_sum = total_base_sum / total_throws if total_throws > 0 else 0
|
||
avg_sum_with_minus = total_final_sum / total_throws if total_throws > 0 else 0
|
||
doubles = sum(1 for t in throws if t['dice'][0] == t['dice'][1])
|
||
|
||
# Находим максимальный и минимальный броски
|
||
throws_with_sums = [(t, calculate_throw_sum(t)) for t in throws]
|
||
max_throw = max(throws_with_sums, key=lambda x: x[1])
|
||
min_throw = min(throws_with_sums, key=lambda x: x[1])
|
||
|
||
# Формируем сообщение
|
||
response = "🎲 Итоги игры\n\n"
|
||
|
||
# Основная статистика
|
||
response += "📊 Общая статистика:\n"
|
||
response += f"• Бросков: {total_throws}\n"
|
||
response += f"• Сумма очков: {total_final_sum}"
|
||
|
||
if total_base_sum != total_final_sum:
|
||
unused_sum = total_base_sum - total_final_sum
|
||
response += f" (потенциал: {total_base_sum})\n"
|
||
response += f"• Неиспользовано: {unused_sum} очков\n"
|
||
response += f"• Эффективность: {(total_final_sum / total_base_sum * 100):.1f}%\n"
|
||
else:
|
||
response += "\n"
|
||
|
||
response += f"• Дублей: {doubles} ({(doubles/total_throws*100):.1f}%)\n"
|
||
|
||
# Средние значения
|
||
response += "\n📈 Средние значения:\n"
|
||
response += f"• За бросок: {avg_sum_with_minus:.1f}"
|
||
if total_base_sum != total_final_sum:
|
||
response += f" (макс. {avg_sum:.1f})\n"
|
||
else:
|
||
response += "\n"
|
||
|
||
# Топ комбинаций
|
||
response += "\n🎯 Частые комбинации:\n"
|
||
for combo, count in sorted_combinations[:5]: # Показываем топ-5 комбинаций
|
||
percentage = (count / total_throws) * 100
|
||
is_double = combo[0] == combo[2] # Проверяем, дубль ли это
|
||
response += f"• {combo} {'🎯' if is_double else ''}: {count}x ({percentage:.1f}%)\n"
|
||
|
||
# Последние броски
|
||
response += "\n🎲 Последние броски:\n"
|
||
for i, throw in enumerate(throws[:5]):
|
||
sum_value = calculate_throw_sum(throw)
|
||
is_double = throw['dice'][0] == throw['dice'][1]
|
||
unused = throw.get('unusedPoints', 0)
|
||
|
||
response += f"{i+1}. {throw['dice'][0]}-{throw['dice'][1]}"
|
||
if is_double:
|
||
response += " 🎯"
|
||
if unused > 0:
|
||
response += f" = {sum_value} (-{unused})"
|
||
else:
|
||
response += f" = {sum_value}"
|
||
response += "\n"
|
||
|
||
if total_throws > 5:
|
||
response += f"... и ещё {total_throws - 5} бросков"
|
||
|
||
await message.answer(response)
|
||
logger.info(f"Sent game statistics to user {user.id}")
|
||
|
||
except Exception as e:
|
||
logger.error(f"Error handling webapp data: {e}", exc_info=True)
|
||
await message.answer("❌ Произошла ошибка при обработке данных.")
|
||
|
||
@dp.message(Command("statistics"))
|
||
@dp.message(F.text == "Статистика 📊")
|
||
async def statistics_command(message: types.Message):
|
||
user_id = message.from_user.id
|
||
logger.info(f"User {user_id} requested statistics")
|
||
|
||
stats = db.get_statistics(user_id)
|
||
|
||
response = "📊 Подробная статистика:\n\n"
|
||
|
||
if stats['total_throws']:
|
||
total_base_sum = stats['total_base_sum'] or 0
|
||
total_final_sum = stats['total_final_sum'] or 0
|
||
total_unused = stats['total_unused'] or 0
|
||
avg_sum = total_final_sum / stats['total_throws']
|
||
|
||
response += (
|
||
f"🎲 Броски:\n"
|
||
f"• Всего бросков: {stats['total_throws']}\n"
|
||
f"• Количество игр: {stats['total_games']}\n"
|
||
f"• Среднее бросков за игру: {stats['total_throws'] / stats['total_games']:.1f}\n\n"
|
||
|
||
f"🎯 Очки:\n"
|
||
f"• Общая сумма: {total_final_sum}\n"
|
||
f"• Сумма без минусов: {total_base_sum}\n"
|
||
f"• Средняя сумма за бросок: {avg_sum:.1f}\n"
|
||
f"• Эффективность: {(total_final_sum / total_base_sum * 100):.1f}%\n\n"
|
||
)
|
||
|
||
if total_unused > 0:
|
||
avg_unused = total_unused / stats['total_throws']
|
||
response += (
|
||
f"❌ Неиспользованные очки:\n"
|
||
f"• Всего неиспользовано: {total_unused}\n"
|
||
f"• В среднем за бросок: {avg_unused:.1f}\n"
|
||
f"• Процент потерь: {(total_unused / total_base_sum * 100):.1f}%\n"
|
||
)
|
||
else:
|
||
response += "У вас пока нет записанных бросков."
|
||
|
||
await message.answer(response)
|
||
logger.info(f"Sent statistics to user {user_id}")
|
||
|
||
# Запуск бота
|
||
async def main():
|
||
logger.info("Starting bot")
|
||
await dp.start_polling(bot)
|
||
|
||
if __name__ == '__main__':
|
||
asyncio.run(main()) |