@filippovI

Как сделать машину состояний aiogram на множество событий?

Ккак реализовать в разрезе "большого квиза"?
Суть задачи проста - страны и их столицы.
Все названия находится в словаре:
CD: dict = {
    # Europe

    1: {
        'Нидерланды ': 'Амстердам',
        'Андорра ': 'Андорра-ла-Велья',
        'Греция ': 'Афины',
        'Сербия ': 'Белград',
        'Германия ': 'Берлин',
        'Швейцария ': 'Берн',
        'Словакия ': 'Братислава',
        'Бельгия ': 'Брюссель',
        'Венгрия ': 'Будапешт',
        'Румыния ': 'Бухарест',
        'Лихтенштейн ': 'Вадуц',
        'Мальта ': 'Валетта',
        'Польша ': 'Варшава',
        'Ватикан ': 'Ватикан',
        'Австрия ': 'Вена',
        'Литва ': 'Вильнюс',
        'Ирландия ': 'Дублин',
        'Хорватия ': 'Загреб',
        'Украина ': 'Киев',
        'Молдавия ': 'Кишинев',
        'Дания ': 'Копенгаген',
        'Португалия ': 'Лиссабон',
        'Великобритания ': 'Лондон',
        'Словения ': 'Любляна',
        'Люксембург ': 'Люксембург',
        'Испания ': 'Мадрид',
        'Белоруссия ': 'Минск',
        'Монако ': 'Монако',
        'Россия ': 'Москва',
        'Норвегия': 'Осло',
        'Франция ': 'Париж',
        'Черногория ': 'Подгорица',
        'Чехия ': 'Прага',
        'Исландия ': 'Рейкьявик',
        'Латвия ': 'Рига',
        'Италия ': 'Рим',
        'Сан-Марино ': 'Сан-Марино',
        'Босния и Герцеговина ': 'Сараево',
        'Северная Македония ': 'Скопье',
        'Болгария ': 'София',
        'Швеция ': 'Стокгольм',
        'Эстония ': 'Таллин',
        'Албания ': 'Тирана',
        'Финляндия ': 'Хельсинки'
    },

    # Asia
    2: {
        'ОАЭ': 'Абу-Даби',
        'Иордания': 'Амман',
        'Турция': 'Анкара',
        'Казахстан': 'Астана',
        'Туркменистан': 'Ашхабад',
        'Ирак': 'Багдад',
        'Азербайджан': 'Баку',
        'Таиланд': 'Бангкок',
        'Бруней': 'Бандар-Сери-Бегаван',
        'Ливан': 'Бейрут',
        'Кыргызстан': 'Бишкек',
        'Лаос': 'Вьентьян',
        'Бангладеш': 'Дакка',
        'Сирия': 'Дамаск',
        'Индия': 'Нью-Дели',
        'Индонезия': 'Джакарта',
        'Восточный Тимор': 'Дили',
        'Катар': 'Доха',
        'Таджикистан': 'Душанбе',
        'Армения': 'Ереван',
        'Израиль': 'Иерусалим',
        'Пакистан': 'Исламабад',
        'Афганистан': 'Кабул',
        'Непал': 'Катманду',
        'Малайзия': 'Куала-Лумпур',
        'Мальдивы': 'Мале',
        'Бахрейн': 'Манама',
        'Филиппины': 'Манила',
        'Оман': 'Маскат',
        'Мьянма': 'Нейпьидо',
        'Кипр': 'Никосия',
        'Китай': 'Пекин',
        'Камбоджа': 'Пномпень',
        'КНДР': 'Пхеньян',
        'Йемен': 'Сана',
        'Республика Корея': 'Сеул',
        'Сингапур': 'Сингапур',
        'Узбекистан': 'Ташкент',
        'Грузия': 'Тбилиси',
        'Иран': 'Тегеран',
        'Япония': 'Токио',
        'Бутан': 'Тхимпху',
        'Монголия': 'Улан-Батор',
        'Вьетнам': 'Ханой',
        'Шри-Ланка': 'Шри-Джаяварденепура-Котте',
        'Кувейт': 'Эль-Кувейт',
        'Саудовская Аравия': 'Эр-Рияд',
    },
} 
# ...

Далее была идея запросить у пользователя количество материков, по которым будут вопросы, перемешать словарь и задавать вопросы по словарю:

import asyncio

from aiogram import Bot, Dispatcher, types, F
from aiogram.filters.command import Command
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import StatesGroup, State
from config import API_TOKEN
from cities_dicts import CD
from utils import shuffle_dict

API_TOKEN = API_TOKEN

bot = Bot(token=API_TOKEN)
dp = Dispatcher()


class Start(StatesGroup):
    start_name = State()
    name = State()


@dp.message(Command("start"))
async def start(message: types.Message, state: FSMContext):
    await state.set_state(Start.start_name)
    await bot.send_message(message.from_user.id,
                           text='Можно выбрать один материк или перечислить несколько через пробел:\n'
                                '1 - Европа\n'
                                '2 - Азия\n'
                                '3 - Африка\n'
                                '4 - Северная Америка\n'
                                '5 - Южная Америка\n'
                                '6 - Австралия\n')


@dp.message(Start.start_name)
async def test(message: types.Message, state: FSMContext):
    cities: dict = {}
    await state.update_data(name=message.text)
    data = await state.get_data()
    data_list: list = data["name"].strip(' ').split(' ')
    data_list_: list = [i for i in data_list if i]

    for i in data_list_:
        cities.update(CD[int(i)])

    data_ = shuffle_dict(cities).items()


async def main():
    await dp.start_polling(bot)


if __name__ == "__main__":
    asyncio.run(main())

К сожалению, не могу приложить хоть какое-то решение, так как не понимаю, как уйти от огромного количества стейтов. Стран много и вопросов-ответов будет тоже много, а на каждый вопрос создать свою функцию, мне кажется, не очень правильно.
  • Вопрос задан
  • 333 просмотра
Решения вопроса 1
@filippovI Автор вопроса
В общем, обошелся с FSM только для запроса начальных данных.
Дальше исходя из этих данных формируем нужные нам списки в функции стэйта, в ней же посылаем первый вопрос и дальше обрабатываем ответы в пустом хендлере, который читает все сообщения, отправленные боту.

Вот примерная реализация
@dp.message(Command("start"))
async def start(message: types.Message, state: FSMContext):
    await state.set_state(Start.start_name)
    await bot.send_message(message.from_user.id,
                           text='Можно выбрать один материк или перечислить несколько через пробел:\n'
                                '1 - Европа\n'
                                '2 - Азия\n'
                                '3 - Африка\n'
                                '4 - Северная Америка\n'
                                '5 - Южная Америка\n'
                                '6 - Австралия\n')


@dp.message(Start.start_name)
async def test(message: types.Message, state: FSMContext):
    await state.update_data(name=message.text)
    data = await state.get_data()
    
    # Создаем словарь перемешанных вопросов и ответов
    cities: dict = {}

    # Список вариантов материков
    data_list: list = [i for i in data["name"].strip(' ').split(' ') if i]
    
    # Исходя из списка вариантов и основного словаря со столицами CD обновляем cities
    for i in data_list:
        cities.update(CD[int(i)])
        
    # Разбиваем на словари вопросов и ответов для записи в БД
    questions_list, answers_list = list(shuffle_dict(cities))
    
    # Записываем в БД (создаем нового юзера, если нет. Создаем под него вопросы и ответы, если нет. Иначе обновляем)
    await db.insert('users', [message.from_user.id, message.from_user.username, 1])
    await db.insert('questions', [message.from_user.id, ', '.join(questions_list), ', '.join(answers_list), 0])
    
    # Отправляем первый вопрос. Завершаем стейт
    await bot.send_message(message.from_user.id, await db.select_question(message.from_user.id))
    await state.clear()


@dp.message()
async def test1(message: types.Message):

    # Проверяем, что пользователь играет (флажок in_game из БД)
    if await db.select_in_game(message.from_user.id) == 1:

        # Если его ответ совпал с ответом из БД, то:
        # 1. Увеличиваем счетчик вопросов в БД на 1
        # 2. Отправляем следующий вопрос, исходя из счетчика
        if message.text == await db.select_answer(message.from_user.id):
            await db.update_question_number(message.from_user.id)
            await bot.send_message(message.from_user.id, await db.select_question(message.from_user.id))

        # Если его ответ = 1, то:
        # 1. Отправляем правильный ответ
        # 2. Увеличиваем счетчик вопросов в БД на 1
        # 3. Отправляем следующий вопрос
        if message.text == '1':
            await bot.send_message(message.from_user.id, await db.select_answer(message.from_user.id))
            await db.update_question_number(message.from_user.id)
            await bot.send_message(message.from_user.id, await db.select_question(message.from_user.id))

        # Если его ответ = 0, то:
        # 1. Завершаем игру. Чистим все нужные строки в БД.
        # 2. Отправляем сообщение о завершении
        if message.text == '0':
            await db.the_end(message.from_user.id)
            await bot.send_message(message.from_user.id, 'Завершено')
Ответ написан
Комментировать
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы