diff --git a/bot.py b/bot.py index 274f858..5f808fd 100644 --- a/bot.py +++ b/bot.py @@ -2,25 +2,30 @@ import asyncio from aiogram import Bot, Dispatcher +from aiogram.fsm.storage.redis import RedisStorage, Redis + from config.config import Config, load_config from handlers import user_handlers -from data.sql_database import LobbyDatabase +from data.sql_database import LobbyDatabase, UsersDatabase from services.set_menu import set_main_menu + logger = logging.getLogger(__name__) -lobby_database = LobbyDatabase('test3') +lobby_database = LobbyDatabase('test6') +users_database = UsersDatabase('test5') config: Config = load_config() bot = Bot(token=config.tg_bot.token, parse_mode='HTML') +redis = Redis(host='127.0.0.1:6379') +storage = RedisStorage(redis=redis) async def main(): - lobby_database.create_table() - for i in range(1, 5): - lobby_database.reset_lobby(i) - lobby_database.default_lobby(i) - lobby_database.reset_lobby(-1) - lobby_database.default_lobby(-1) + await lobby_database.create_table() + await users_database.create_table() + lobbies_id = lobby_database.get_all_lobby_stat() + for i in lobbies_id: + await lobby_database.delete_lobby(lobby_id=i[0]) logging.basicConfig(level=logging.INFO, format='%(filename)s:%(lineno)d #%(levelname)-8s ' '[%(asctime)s] - %(name)s - %(message)s') @@ -37,4 +42,4 @@ async def main(): if __name__ == '__main__': - asyncio.run(main()) \ No newline at end of file + asyncio.run(main()) diff --git a/data/sql_database.py b/data/sql_database.py index 2563d9a..5508f7d 100644 --- a/data/sql_database.py +++ b/data/sql_database.py @@ -1,63 +1,185 @@ -import sqlite3 +import psycopg2 -conn = sqlite3.connect('test.py') +conn = psycopg2.connect(host='localhost', dbname='postgres', user='postgres', password='1234', port=5432) class LobbyDatabase: def __init__(self, name): self.name = name - def create_table(self): + async def create_table(self): cur = conn.cursor() cur.execute(f"""CREATE TABLE IF NOT EXISTS {self.name} - (id INT PRIMARY KEY, - people TEXT);""") + (lobby_id BIGSERIAL PRIMARY KEY, + users TEXT); + """) conn.commit() cur.close() print('[INFO] TABLE CREATED SUCCESSFULLY') - def default_lobby(self, lobby_id: int): + async def enter_lobby(self, lobby_id: int, user_chat_id: str): cur = conn.cursor() - cur.execute(f"""INSERT OR IGNORE INTO {self.name} (id, people) - VALUES ({lobby_id}, ''); - """) + cur.execute(f"""SELECT users FROM {self.name} + WHERE lobby_id={lobby_id}; + """) + users = cur.fetchone()[0].split('~~~') + users.append(f"""{user_chat_id}""") + cur.execute(f"""UPDATE {self.name} SET users='{'~~~'.join(users)}' WHERE lobby_id={lobby_id};""") + conn.commit() + cur.close() + + async def exit_lobby(self, lobby_id: int, user_chat_id: str): + cur = conn.cursor() + cur.execute(f"""SELECT users FROM {self.name} + WHERE lobby_id={lobby_id}; + """) + users = cur.fetchone()[0].split('~~~') + users.remove(f"""{user_chat_id}""") + cur.execute(f"""UPDATE {self.name} SET users='{'~~~'.join(users)}' WHERE lobby_id={lobby_id};""") + conn.commit() + cur.close() + + def get_lobby_stat(self, lobby_id: int): + cur = conn.cursor() + cur.execute(f"""SELECT users FROM {self.name} + WHERE lobby_id={lobby_id};""") + s = cur.fetchone()[0].split('~~~') + conn.commit() + cur.close() + return s + + def create_new_lobby(self, user_chat_id: str): + cur = conn.cursor() + cur.execute(f"""INSERT INTO {self.name} (users) VALUES ('{user_chat_id}')""") + cur.execute(f"""SELECT lobby_id FROM {self.name} + WHERE users='{user_chat_id}';""") + lobby_id = cur.fetchone()[0] + conn.commit() + cur.close() + return lobby_id + + def get_all_lobby_stat(self): + cur = conn.cursor() + cur.execute(f"""SELECT lobby_id, users FROM {self.name}""") + all_lobby_stat = cur.fetchall() + return all_lobby_stat + + async def delete_lobby(self, lobby_id: int): + cur = conn.cursor() + cur.execute(f"""DELETE FROM {self.name} WHERE lobby_id={lobby_id};""") conn.commit() cur.close() - def enter_lobby(self, lobby_id: int, user_chat_id: str): + +class UsersDatabase: + def __init__(self, name): + self.name = name + + async def create_table(self): cur = conn.cursor() - cur.execute(f"""SELECT people FROM {self.name} - WHERE id={lobby_id}; + cur.execute(f"""CREATE TABLE IF NOT EXISTS {self.name} + (chat_id BIGINT PRIMARY KEY, + user_name TEXT, + lobbies_page_message_id BIGINT, + game_page_message_id BIGINT, + balance INT, + games_amount INT, + wines_amount INT, + loses_amount INT, + last_free_reward_date_timestamp INT);""") + conn.commit() + cur.close() + print('[INFO] TABLE CREATED SUCCESSFULLY') + + async def insert_new_user(self, chat_id: int, user_name: str): + cur = conn.cursor() + cur.execute(f"""INSERT INTO {self.name} + (chat_id, user_name, lobbies_page_message_id, games_amount, wines_amount, loses_amount, balance, + last_free_reward_date_timestamp) + VALUES ({chat_id}, '{user_name}', NULL, 0, 0, 0, 1000, 0) + ON CONFLICT (chat_id) DO NOTHING; """) - s = cur.fetchone()[0].split() - s.append(user_chat_id) - cur.execute(f"""UPDATE {self.name} SET people='{' '.join(s)}' WHERE id={lobby_id};""") conn.commit() cur.close() - def exit_lobby(self, lobby_id: int, user_chat_id: str): + def get_statistic_of_users_without_lobby(self): cur = conn.cursor() - cur.execute(f"""SELECT people FROM {self.name} - WHERE id={lobby_id}; - """) - s = cur.fetchone()[0].split() - s.remove(user_chat_id) - cur.execute(f"""UPDATE {self.name} SET people='{' '.join(s)}' WHERE id={lobby_id};""") + cur.execute(f"""SELECT chat_id, lobbies_page_message_id FROM {self.name} + WHERE lobbies_page_message_id IS NOT NULL;""") + stat = cur.fetchall() conn.commit() cur.close() + return stat - def reset_lobby(self, lobby_id: int): + def get_user_name(self, chat_id: int): cur = conn.cursor() - cur.execute(f"""UPDATE {self.name} SET people='' WHERE id={lobby_id};""") + cur.execute(f"""SELECT user_name FROM {self.name} WHERE chat_id={chat_id}""") + user_name = cur.fetchone() conn.commit() cur.close() + return user_name[0] - def get_lobby_stat(self, lobby_id: int): + async def update_lobbies_page_message_id(self, chat_id: int, lobbies_page_message_id: int): cur = conn.cursor() - cur.execute(f"""SELECT people FROM {self.name} - WHERE id={lobby_id}; - """) - s = cur.fetchone()[0].split() + cur.execute(f"""UPDATE {self.name} SET lobbies_page_message_id={lobbies_page_message_id} + WHERE chat_id={chat_id};""") + conn.commit() + cur.close() + + async def delete_lobbies_page_message_id(self, chat_id: int): + cur = conn.cursor() + cur.execute(f"""UPDATE {self.name} SET lobbies_page_message_id=NULL WHERE chat_id={chat_id};""") + conn.commit() + cur.close() + + def get_user_statistic(self, chat_id): + cur = conn.cursor() + cur.execute(f"""SELECT user_name, balance, games_amount, wines_amount, loses_amount + FROM {self.name} WHERE chat_id={chat_id}""") + stat = cur.fetchall() + conn.commit() + cur.close() + return stat[0] + + def get_user_balance(self, chat_id): + cur = conn.cursor() + cur.execute(f"""SELECT balance FROM {self.name} WHERE chat_id={chat_id}""") + balance = cur.fetchone() + conn.commit() + cur.close() + return balance[0] + + def update_user_balance(self, chat_id: int, balance: int): + cur = conn.cursor() + cur.execute(f"""UPDATE {self.name} SET balance={balance} WHERE chat_id={chat_id};""") + conn.commit() + cur.close() + + async def update_game_page_message_id(self, chat_id, message_id): + cur = conn.cursor() + cur.execute(f"""UPDATE {self.name} SET game_page_message_id={message_id} WHERE chat_id={chat_id};""") + conn.commit() + cur.close() + + def get_user_game_page_message_id(self, chat_id: int): + cur = conn.cursor() + cur.execute(f"""SELECT game_page_message_id FROM {self.name} WHERE chat_id={chat_id}""") + page_message_id = cur.fetchone() + conn.commit() + cur.close() + return page_message_id[0] + + def get_last_free_reward_date_timestamp(self, chat_id: int): + cur = conn.cursor() + cur.execute(f"""SELECT last_free_reward_date_timestamp FROM {self.name} WHERE chat_id={chat_id}""") + last_free_reward_date_timestamp = cur.fetchone() + conn.commit() + cur.close() + return last_free_reward_date_timestamp[0] + + def update_last_free_reward_date_timestamp(self, chat_id: int, last_free_reward_date_timestamp: int): + cur = conn.cursor() + cur.execute(f"""UPDATE {self.name} SET last_free_reward_date_timestamp={last_free_reward_date_timestamp} + WHERE chat_id={chat_id}""") conn.commit() cur.close() - return s diff --git a/handlers/user_handlers.py b/handlers/user_handlers.py index b8834f3..ec66622 100644 --- a/handlers/user_handlers.py +++ b/handlers/user_handlers.py @@ -1,30 +1,99 @@ from aiogram import Router, F +from aiogram.exceptions import TelegramBadRequest from aiogram.filters import Command, StateFilter +from aiogram.fsm.storage.base import StorageKey from aiogram.types import Message, CallbackQuery - -from bot import bot -from data.sql_database import LobbyDatabase -from keyboards.keyboards import keyboard_builder, create_inline_kb, LobbyCallbackFactory +from bot import bot, users_database, lobby_database +from keyboards.keyboards import create_inline_kb, LobbyCallbackFactory, CardsCallbackFactory from aiogram.fsm.context import FSMContext from aiogram.fsm.state import default_state -from services.services import FSMLobbyClass, create_lobbies_page +from services.services import exit_lobby, FSMLobbyClass, create_lobbies_page, create_deck, LobbyMessage, \ + update_previous_pages, GameMessage, lobbies_messages_dict, games_messages_dict, get_valid_cards, get_valid_faces +from datetime import datetime +from lexicon.lexicon import lexicon_menu_keyboard, lexicon_user_statistic router = Router() -lobby_database = LobbyDatabase('test3') -# @router.message(Command(commands='start')) -# async def start(message: Message): +@router.message(Command(commands='start')) +async def start(message: Message, + state: FSMContext): + data = await state.get_data() + await users_database.insert_new_user(chat_id=message.chat.id, user_name=message.chat.full_name) + await users_database.delete_lobbies_page_message_id(chat_id=message.chat.id) + if data.get('lobby'): + await exit_lobby(state=state, data=data, bot=bot, message=message) + await state.set_state(default_state) + keyboard = create_inline_kb(dct={'menu': 'Главное меню'}, width=1) + await message.answer(text='Это тренировочный бот для игры в Дурака с другими людьми.\n' + 'Для начала перейдите в главное меню', + reply_markup=keyboard) + await state.update_data(previous_pages=[], + user_name=users_database.get_user_name(chat_id=message.chat.id)) -@router.message(Command(commands='lobbies'), StateFilter(default_state)) -async def test2(message: Message, - state: FSMContext): - lobby_pages = create_lobbies_page() - keyboards = create_inline_kb(2, dct=lobby_pages) - await message.answer(text='Список доступных лобби', reply_markup=keyboards) +@router.callback_query(F.data == 'menu', StateFilter(default_state, FSMLobbyClass.select_lobby)) +async def menu(callback: CallbackQuery, + state: FSMContext): + data = await state.get_data() + await state.set_state(default_state) + keyboard = create_inline_kb(dct=lexicon_menu_keyboard, width=1) + await callback.message.edit_text(text='Вы находитесь в главном меню', reply_markup=keyboard) + data['previous_pages'].append(callback.data) + await state.update_data(data=data) + + +@router.callback_query(F.data == 'statistics', StateFilter(default_state)) +async def statistic(callback: CallbackQuery, + state: FSMContext): + data = await state.get_data() + data_callback = callback.data + data = update_previous_pages(data, data_callback) + user_stat = users_database.get_user_statistic(chat_id=callback.message.chat.id) + keyboard = create_inline_kb(width=1, back_button=data['previous_pages'][-1]) + await callback.message.edit_text(text=lexicon_user_statistic.format(*user_stat), reply_markup=keyboard) + data['previous_pages'].append(callback.data) + await state.update_data(previous_pages=data['previous_pages']) + + +@router.callback_query(F.data == 'free_reward', StateFilter(default_state)) +async def free_reward(callback: CallbackQuery): + balance = users_database.get_user_balance(chat_id=callback.message.chat.id) + if balance < 1450: + last_date = users_database.get_last_free_reward_date_timestamp(chat_id=callback.message.chat.id) + current_date = datetime.now().timestamp() + difference_date = current_date - last_date + if difference_date >= 3600: + await callback.answer(text='Беcплатная награда выдана (+1450)') + balance += 1450 + users_database.update_user_balance(chat_id=callback.message.chat.id, balance=balance) + users_database.update_last_free_reward_date_timestamp(chat_id=callback.message.chat.id, + last_free_reward_date_timestamp=current_date) + else: + normal_date = datetime.fromtimestamp(last_date + 3600) + await callback.answer(text=f'Следующую награду можно получить в {normal_date.strftime("%X")}', + show_alert=True) + else: + await callback.answer(text='У вас достаточно средств', show_alert=True) + + +@router.callback_query(F.data == 'lobbies', StateFilter(default_state)) +async def lobbies(callback: CallbackQuery, + state: FSMContext): + data = await state.get_data() + data_callback = callback.data + data = update_previous_pages(data, data_callback) await state.set_state(FSMLobbyClass.select_lobby) - lobby_database.enter_lobby(lobby_id=-1, user_chat_id=str(message.chat.id)) + if data.get('lobby'): + await exit_lobby(state=state, data=data, bot=bot, message=callback.message) + lobby_pages = create_lobbies_page() + keyboards = create_inline_kb(width=2, dct=lobby_pages, last_btn='create_new_lobby', + back_button=data['previous_pages'][-1]) + bot_message = await callback.message.edit_text(text='Список доступных лобби', reply_markup=keyboards) + await users_database.update_lobbies_page_message_id(chat_id=callback.message.chat.id, + lobbies_page_message_id=bot_message.message_id) + data['previous_pages'].append(callback.data) + await state.update_data(previous_pages=data['previous_pages']) @router.callback_query(LobbyCallbackFactory.filter(), StateFilter(FSMLobbyClass.select_lobby)) @@ -33,42 +102,205 @@ async def lobby_page(callback: CallbackQuery, state: FSMContext): lobby_stat = lobby_database.get_lobby_stat(callback_data.lobby_id) if len(lobby_stat) < 4: - await state.set_state(FSMLobbyClass.in_lobby) - lobby_database.enter_lobby(lobby_id=callback_data.lobby_id, user_chat_id=str(callback.message.chat.id)) - lobby_database.exit_lobby(lobby_id=-1, user_chat_id=str(callback.message.chat.id)) - people_without_lobby = lobby_database.get_lobby_stat(-1) + data = await state.get_data() + user_name = data['user_name'] + await users_database.delete_lobbies_page_message_id(chat_id=callback.message.chat.id) + people_without_lobby = users_database.get_statistic_of_users_without_lobby() lobby_pages = create_lobbies_page() - for i in people_without_lobby: - await bot.edit_message_text(chat_id=int(i), text='Список доступных лобби', reply_markup=lobby_pages) - await state.update_data(lobby=callback_data.lobby_id) - for i in lobby_stat: - await bot.send_message(chat_id=int(i), text=f'{callback.from_user.full_name} зашел в лобби') - await callback.message.delete() - await callback.message.answer(text=f'Вы вошли в лобби №{callback_data.lobby_id}! Любое ваше сообщение будет ' - f'отправлено участникам лобби!', - reply_markup=keyboard_builder(buttons=['Выйти'], - width=1)) + lobby_message = lobbies_messages_dict.get(callback_data.lobby_id) + lobby_message.update_ready_info(ready=0, user=user_name) + if lobby_message: + lobby_message.add_user(user_name) + for chat_id in lobby_stat: + try: + user_name = lobby_message.return_message(users_database.get_user_name(chat_id)) + await bot.edit_message_text(text=user_name, + chat_id=chat_id, + message_id=users_database.get_user_game_page_message_id( + chat_id=chat_id), + reply_markup=lobby_message.create_keyboard(user_name)) + except TelegramBadRequest: + pass + for chat_id, message_id in people_without_lobby: + try: + keyboard = create_inline_kb(width=2, dct=lobby_pages, last_btn='create_new_lobby', + back_button='menu') + await bot.edit_message_reply_markup(chat_id=chat_id, message_id=message_id, + reply_markup=keyboard) + except TelegramBadRequest: + pass + await lobby_database.enter_lobby(lobby_id=callback_data.lobby_id, + user_chat_id=str(callback.message.chat.id)) + await state.set_state(FSMLobbyClass.in_lobby) + await state.update_data(lobby=callback_data.lobby_id, ready=0) + bot_message = await callback.message.edit_text(text=lobby_message.return_message(user_name), + reply_markup=lobby_message.create_keyboard(user_name)) + await callback.answer(text='Вы зашли в лобби!') + await users_database.update_game_page_message_id(chat_id=callback.message.chat.id, + message_id=bot_message.message_id) + else: + await callback.answer('Лобби не существует') else: await callback.answer('Лобби заполнено') -@router.message(F.text == 'Выйти', StateFilter(FSMLobbyClass.in_lobby)) -async def exit_command(message: Message, +@router.callback_query(F.data == 'exit', StateFilter(FSMLobbyClass.in_lobby)) +async def exit_command(callback: CallbackQuery, state: FSMContext): + data = await state.get_data() + data_callback = callback.data + data = update_previous_pages(data, data_callback) + await exit_lobby(state=state, bot=bot, message=callback.message, data=data) + lobby_pages = create_lobbies_page() + keyboards = create_inline_kb(width=2, dct=lobby_pages, last_btn='create_new_lobby', + back_button=data['previous_pages'][-1]) + await callback.answer(text='Вы вышли из лобби!') + bot_message = await callback.message.edit_text(text='Список доступных лобби', reply_markup=keyboards) + await users_database.update_lobbies_page_message_id(chat_id=callback.message.chat.id, + lobbies_page_message_id=bot_message.message_id) await state.set_state(FSMLobbyClass.select_lobby) + data['previous_pages'].append(callback.data) + if data.get('current_cards'): + lobby_deck = lobby_database.get_lobby_deck(lobby_id=data['lobby']).split('~~~') + user_cards = data['current_cards'].split('~~~') + for card in user_cards: + lobby_deck.append(card) + await lobby_database.update_deck(deck='~~~'.join(lobby_deck)) + + +@router.callback_query(F.data == 'ready', StateFilter(FSMLobbyClass.in_lobby)) +async def ready_command_others(callback: CallbackQuery, + state: FSMContext): data = await state.get_data() - lobby_database.exit_lobby(lobby_id=data['lobby'], user_chat_id=str(message.chat.id)) - lobby_database.enter_lobby(lobby_id=-1, user_chat_id=str(message.chat.id)) + if data['ready'] == 0: + await state.update_data(ready=1) + user_name = data['user_name'] + lobby_message = lobbies_messages_dict[data['lobby']] + if games_messages_dict.get(data['lobby']): + game_message = games_messages_dict[data['lobby']] + game_message.add_user(callback.message.chat.id) + else: + deck = create_deck() + game_message = GameMessage(users=[callback.message.chat.id], + deck=deck, + royal_card=deck[-1]) + games_messages_dict[data['lobby']] = game_message + game_message.update_user_name(chat_id=callback.message.chat.id, user_name=user_name) + game_message.give_next_cards_to_user(user_chat_id=callback.message.chat.id, value=6) + lobby_message.update_ready_info(ready=1, user=user_name) + storage = state.storage + lobby_stat = lobby_database.get_lobby_stat(data['lobby']) + all_users_ready = lobby_message.get_all_users_ready() + if all_users_ready: + game_message.next_user() + for chat_id in lobby_stat: + await storage.set_state(StorageKey(bot_id=bot.id, + chat_id=int(chat_id), + user_id=int(chat_id)), + state=FSMLobbyClass.in_game) + await bot.edit_message_text(text='Игра началась!', + chat_id=chat_id, + message_id=users_database.get_user_game_page_message_id( + chat_id=chat_id), + reply_markup=None) + bot_message = await bot.send_message(text=str(game_message), + chat_id=chat_id, + reply_markup=game_message.create_game_keyboard(chat_id=int(chat_id))) + await users_database.update_game_page_message_id(chat_id=chat_id, message_id=bot_message.message_id) + else: + for chat_id in lobby_stat: + try: + user_name = users_database.get_user_name(chat_id=int(chat_id)) + await bot.edit_message_text(text=lobby_message.return_message(user_name), + chat_id=chat_id, + message_id=users_database.get_user_game_page_message_id( + chat_id=chat_id), + reply_markup=lobby_message.create_keyboard(user_name)) + except TelegramBadRequest as e: + print(e) + else: + await callback.answer('Вы уже готовы!') + + +@router.callback_query(F.data == 'create_new_lobby', StateFilter(FSMLobbyClass.select_lobby)) +async def create_new_lobby(callback: CallbackQuery, + state: FSMContext): + data = await state.get_data() + user_name = users_database.get_user_name(chat_id=callback.message.chat.id) + lobby_id = lobby_database.create_new_lobby(user_chat_id=str(callback.message.chat.id)) + data['lobby'] = lobby_id + data['ready'] = 0 + lobby_message = LobbyMessage([callback.message.chat.full_name]) + lobby_message.update_ready_info(ready=0, user=user_name) + await state.set_state(FSMLobbyClass.in_lobby) + await state.update_data(data=data) + await users_database.delete_lobbies_page_message_id(chat_id=callback.message.chat.id) + people_without_lobby = users_database.get_statistic_of_users_without_lobby() lobby_pages = create_lobbies_page() - keyboards = create_inline_kb(2, dct=lobby_pages) - await message.answer(text='Список доступных лобби', reply_markup=keyboards) + keyboard = create_inline_kb(width=2, dct=lobby_pages, last_btn='create_new_lobby', back_button='lobbies') + for chat_id, message_id in people_without_lobby: + try: + await bot.edit_message_reply_markup(chat_id=chat_id, message_id=message_id, + reply_markup=keyboard) + except TelegramBadRequest: + pass + bot_message = await callback.message.edit_text(text=lobby_message.return_message(user_name), + reply_markup=lobby_message.create_keyboard(user_name)) + await callback.answer(text='Вы создали лобби!') + await users_database.update_game_page_message_id(chat_id=callback.message.chat.id, + message_id=bot_message.message_id) + lobbies_messages_dict[lobby_id] = lobby_message -@router.message(StateFilter(FSMLobbyClass.in_lobby)) -async def others(message: Message, - state: FSMContext): +# GAME HANDLERS +@router.callback_query(CardsCallbackFactory.filter(), StateFilter(FSMLobbyClass.in_game)) +async def game_process(callback: CallbackQuery, + callback_data: CardsCallbackFactory, + state: FSMContext): data = await state.get_data() - people = lobby_database.get_lobby_stat(data['lobby']) - for i in people: - if int(i) != message.chat.id: - await bot.send_message(chat_id=int(i), text=message.text) \ No newline at end of file + game_message = games_messages_dict[data['lobby']] + if callback_data.to_or_from == 'to': + if len(game_message.cards_on_table.values()): + if not all([1 if i != '' else 0 for i in game_message.cards_on_table.keys()]): + valid_cards = get_valid_cards(cards=[i[0] for i in game_message.cards_on_table.items() if i[1] == ''], + card=[callback_data.face, callback_data.color], + royal_card=game_message.royal_card) + if len(valid_cards) == 1: + lobby_stat = lobby_database.get_lobby_stat(data['lobby']) + game_message.cards_on_table[valid_cards[0]] = '-'.join([callback_data.face, callback_data.color]) + game_message.delete_user_card(card='-'.join([callback_data.face, callback_data.color]), + user_chat_id=callback.message.chat.id) + for chat_id in lobby_stat: + await bot.edit_message_text(text=str(game_message), + chat_id=chat_id, + message_id=users_database.get_user_game_page_message_id( + chat_id=chat_id), + reply_markup=game_message.create_game_keyboard(chat_id=int(chat_id))) + elif len(valid_cards) >= 2: + await callback.answer(text='Падажи 2 карты если можешь покрыть пока не работает') + else: + await callback.answer(text='Вы не можете покрыться этой картой') + else: + await callback.answer(text='Все карты покрыты') + else: + await callback.answer(text='Нет карт для покрытия') + else: + cards = list(game_message.cards_on_table.values()) + cards.extend(game_message.cards_on_table.keys()) + valid_faces = get_valid_faces(cards=cards) + if len(game_message.cards_on_table.items()) < len(game_message.users_cards[game_message.user_to]): + if callback_data.face in valid_faces or not len(game_message.cards_on_table.values()): + game_message.delete_user_card(card='-'.join([callback_data.face, callback_data.color]), + user_chat_id=callback.message.chat.id) + lobby_stat = lobby_database.get_lobby_stat(data['lobby']) + game_message.cards_on_table['-'.join([callback_data.face, callback_data.color])] = '' + for chat_id in lobby_stat: + await bot.edit_message_text(text=str(game_message), + chat_id=chat_id, + message_id=users_database.get_user_game_page_message_id( + chat_id=chat_id), + reply_markup=game_message.create_game_keyboard(chat_id=int(chat_id))) + else: + await callback.answer(text='Вы не можете подкинуть эту карту') + else: + await callback.answer(text='На столе достаточно карт!') diff --git a/keyboards/keyboards.py b/keyboards/keyboards.py index d942f0f..ff84fe1 100644 --- a/keyboards/keyboards.py +++ b/keyboards/keyboards.py @@ -4,6 +4,7 @@ from aiogram.filters.callback_data import CallbackData from aiogram.types import KeyboardButton, InlineKeyboardMarkup, InlineKeyboardButton from aiogram.utils.keyboard import ReplyKeyboardBuilder, InlineKeyboardBuilder +from lexicon.lexicon import keyboard_lexicon def keyboard_builder(buttons: list, @@ -57,7 +58,7 @@ def create_inline_kb(width: int, )) if back_button: kb_builder.row(InlineKeyboardButton( - text='Назад', + text=keyboard_lexicon[back_button] if back_button in keyboard_lexicon else 'Назад', callback_data=back_button )) return kb_builder.as_markup() @@ -69,3 +70,13 @@ class LobbyCallbackFactory(CallbackData, prefix='lobby', sep='|'): def __init__(self, lobby_id): super().__init__(lobby_id=lobby_id) + + +@dataclass +class CardsCallbackFactory(CallbackData, prefix='card', sep='|'): + face: str + color: str + to_or_from: str + + def __init__(self, face, color, to_or_from): + super().__init__(face=face, color=color, to_or_from=to_or_from) diff --git a/lexicon/lexicon.py b/lexicon/lexicon.py index f2cf915..7202316 100644 --- a/lexicon/lexicon.py +++ b/lexicon/lexicon.py @@ -1,5 +1,22 @@ lexicon_commands: dict[str, str] = { '/start': 'Запуск или обновление', - '/lobbies': 'Все лобби' + '/lobbies': 'Все лобби', + '/info': 'Информация о лобби', + '/exit': 'Выйти из лобби', + '/ready': 'Приготовиться к игре' } +keyboard_lexicon = {'create_new_lobby': 'Новое лобби', 'bito': 'Бито', 'beru': 'Беру', 'exit': 'Выйти'} + +lexicon_card_colors = {'♥️': 1, '♦️': 2, '♣️': 3, '♠️': 4} + +lexicon_card_faces = {'6': 6, '7': 7, '8': 8, '9': 9, '10': 10, 'Валет': 11, 'Дама': 12, 'Король': 13, 'Туз': 14} + +lexicon_menu_keyboard = {'lobbies': 'Выбрать комнату', 'statistics': 'Статистика пользователя', + 'free_reward': 'Бесплатная награда'} + +lexicon_user_statistic = ('Ваше имя: {}\n' + 'Баланс: {}\n\n' + 'Количество сыгранных партий: {}\n' + 'Количество побед: {}\n' + 'Количество поражений: {}') diff --git a/services/services.py b/services/services.py index 68e27e1..02afa4c 100644 --- a/services/services.py +++ b/services/services.py @@ -1,16 +1,323 @@ +from typing import Optional +from aiogram import Bot +from aiogram.exceptions import TelegramBadRequest +from aiogram.fsm.context import FSMContext from aiogram.fsm.state import State, StatesGroup +from aiogram.types import InlineKeyboardMarkup, Message +from bot import users_database, lobby_database +from keyboards.keyboards import LobbyCallbackFactory, create_inline_kb, CardsCallbackFactory +from dataclasses import dataclass +from lexicon.lexicon import lexicon_card_faces, lexicon_card_colors -from data.sql_database import LobbyDatabase -from keyboards.keyboards import LobbyCallbackFactory -lobby_database = LobbyDatabase('test3') +lobbies_messages_dict = {} +games_messages_dict = {} class FSMLobbyClass(StatesGroup): - in_lobby = State() select_lobby = State() + in_lobby = State() + in_game = State() + ready = State() lobby = State() + current_cards = State() + previous_pages = State() + user_name = State() + + +def create_user_list_for_lobby(users: list, ready_dict: dict): + users_list_for_lobby = ( + '\n'.join([f'{i}. {users[i-1]} - {"Готов" if ready_dict[users[i-1]] else "Не готов"}' if i <= len(users) else + f'{i}. Ждем игрока' for i in range(1, 5)])) + return users_list_for_lobby + + +def create_lobby_short_name(users): + names = list(map(lambda x: users_database.get_user_name(x), users)) + lobby_short_name = ', '.join(names) + return lobby_short_name[:10]+'...' if len(lobby_short_name) > 12 else lobby_short_name + + +def create_game_keyboard_dict(cards: list, to_or_from: str): + cards.sort(key=lambda x: (lexicon_card_colors[x.split('-')[1]], lexicon_card_faces[x.split('-')[0]])) + return {CardsCallbackFactory(face=card.split('-')[0], color=card.split('-')[1], to_or_from=to_or_from).pack(): + f'{" ".join(card.split("-"))}' for card in cards} def create_lobbies_page(): - return {LobbyCallbackFactory(lobby_id=i).pack(): f'Лобби №{i} {len(lobby_database.get_lobby_stat(i))}/4' for i in range(1, 5)} + all_lobby_stat = lobby_database.get_all_lobby_stat() + return {LobbyCallbackFactory(lobby_id=lobby[0]).pack(): + f"""{create_lobby_short_name(lobby[1].split('~~~'))} {len(lobby[1].split('~~~'))}/4""" + for lobby in all_lobby_stat if lobby[1]} + + +async def send_messages_to_users(bot: Bot, message: str, users: list, markup: Optional[InlineKeyboardMarkup]): + for i in users: + await bot.send_message(chat_id=int(i), text=message, reply_markup=markup) + + +async def edit_users_messages(bot: Bot, message: str, users: list, markup: Optional[InlineKeyboardMarkup]): + for i in users: + await bot.edit_message_text(chat_id=int(i), text=message, reply_markup=markup) + + +def get_lobby_members(users_ids: list): + members = list(map(lambda user_id: users_database.get_user_name(user_id), users_ids)) + return '\n'.join(members) + + +def create_deck(): + faces = list(range(6, 11)) + [faces.append(i) for i in ["Король", "Дама", "Валет", "Туз"]] + colour = ["♥️", "♦️", "♣️", "♠️"] + from itertools import product + from random import shuffle + deck = ["{}-{}".format(*card) for card in product(faces, colour)] + shuffle(deck) + return deck + + +@dataclass +class LobbyMessage: + def __init__(self, users: list): + self.users = users + self.ready_text = 'Вы готовы!\n\n' + self.unready_text = 'Вы не готовы!\n\n' + self.ready_dict = {} + + def return_message(self, user: str): + users_list = create_user_list_for_lobby(users=self.users, ready_dict=self.ready_dict) + lobby_text = (self.ready_text if self.ready_dict.get(user) else self.unready_text) + (f'Участники лобби:\n' + f'{users_list}') + return lobby_text + + def delete_user(self, user: str): + self.users.remove(user) + self.ready_dict.pop(user) + + def add_user(self, user: str): + self.users.append(user) + + def update_ready_info(self, ready: int, user: str): + self.ready_dict[user] = ready + + def get_all_users_ready(self): + values = self.ready_dict.values() + return all(values) and len(values) == 2 + + def create_keyboard(self, user: str): + if self.ready_dict.get(user) == 1: + return create_inline_kb(dct={'exit': 'Выйти'}, width=1) + else: + return create_inline_kb(dct={'ready': 'Приготовиться', 'exit': 'Выйти'}, width=1) + + +@dataclass +class GameMessage: + def __init__(self, users: list, deck: list, royal_card: str): + self.users = users + self.deck = deck + self.royal_card = royal_card + self.cards_on_table = {} + self.user_from_index = 0 + self.user_to_index = 1 + self.user_from = 0 + self.user_to = 0 + self.users_cards = {} + self.users_wined = [] + self.user_from_bito = False + self.users_names = {} + + game_text = ('Ходит игрок: {}\n' + 'Кроется игрок: {}\n\n' + 'Карт в колоде: {}\n' + 'Козырная карта: {}\n\n' + 'Карты на столе: {}') + + def __str__(self): + user_from = self.users_names[self.user_from] + user_to = self.users_names[self.user_to] + cards_value = len(self.deck) + royal_card = ' '.join(self.royal_card.split('-')) + cards_on_table = '\n'.join([f'{" ".join(i[0].split("-"))} покрыта картой {" ".join(i[1].split("-"))}' + if i[1] != '' else f'{" ".join(i[0].split("-"))} не покрыта' + for i in self.cards_on_table.items()]) + return self.game_text.format(user_from, user_to, cards_value, royal_card, cards_on_table) + + def next_user(self): + self.user_from = self.users[self.user_from_index] + self.user_to = self.users[self.user_to_index] + self.user_from_index = (self.user_from_index + 1) % 4 + self.user_to_index = (self.user_to_index + 1) % 4 + print(self.user_to, self.user_from, self.users) + + def delete_user_card(self, card: str, user_chat_id: int): + cards = self.users_cards[user_chat_id] + cards.remove(card) + self.users_cards[user_chat_id] = cards + + def create_game_keyboard(self, chat_id: int): + if str(self.user_from) == str(chat_id): + if self.cards_on_table: + return create_inline_kb(width=1, + dct=create_game_keyboard_dict(cards=self.users_cards[chat_id], + to_or_from='from'), + last_btn='bito', + back_button='exit') + else: + return create_inline_kb(width=1, + dct=create_game_keyboard_dict(cards=self.users_cards[chat_id], + to_or_from='from'), + back_button='exit') + if str(self.user_to) == str(chat_id): + if self.cards_on_table and not all(self.cards_on_table.values()): + return create_inline_kb(width=1, + dct=create_game_keyboard_dict(cards=self.users_cards[chat_id], + to_or_from='to'), + last_btn='beru', + back_button='exit') + else: + return create_inline_kb(width=1, + dct=create_game_keyboard_dict(cards=self.users_cards[chat_id], + to_or_from='to'), + back_button='exit') + if self.user_from_bito: + return create_inline_kb(width=1, + dct=create_game_keyboard_dict(cards=self.users_cards[chat_id], + to_or_from='from'), + last_btn='bito', + back_button='exit') + return create_inline_kb(width=1, back_button='exit') + + def give_next_cards_to_user(self, user_chat_id: int, value: int): + if self.users_cards.get(user_chat_id): + if len(self.deck) <= value: + self.users_cards[user_chat_id].extend(self.deck) + self.deck = [] + else: + self.users_cards[user_chat_id].extend(self.deck[:value]) + self.deck = self.deck[value:] + else: + if len(self.deck) <= value: + self.users_cards[user_chat_id] = self.deck + self.deck = [] + else: + self.users_cards[user_chat_id] = self.deck[:value] + self.deck = self.deck[value:] + print(self.users_cards) + + def update_user_name(self, chat_id: int, user_name: str): + self.users_names[chat_id] = user_name + + def add_user(self, user_chat_id: int): + self.users.append(user_chat_id) + + def delete_user(self, user_chat_id: int, is_add_cards: int): + if is_add_cards: + deleted_cards = self.users_cards.pop(user_chat_id, []) + print(deleted_cards) + self.deck = deleted_cards + self.deck + self.users.remove(user_chat_id) + del self.users_names[user_chat_id] + + +async def exit_lobby(state: FSMContext, data, bot, message: Message): + await lobby_database.exit_lobby(lobby_id=data['lobby'], user_chat_id=str(message.chat.id)) + lobby_stat = lobby_database.get_lobby_stat(data['lobby']) + if lobby_stat[0] == '': + del lobbies_messages_dict[data['lobby']] + games_messages_dict[data['lobby']] = None + await lobby_database.delete_lobby(data['lobby']) + else: + lobby_message = lobbies_messages_dict.get(data['lobby']) + lobby_message.delete_user(data['user_name']) + st = await state.get_state() + if st == FSMLobbyClass.in_lobby: + print(0) + if games_messages_dict.get(data['lobby']): + game_message = games_messages_dict[data['lobby']] + if message.chat.id in game_message.users: + game_message.delete_user(message.chat.id, 1) + + for chat_id in lobby_stat: + try: + user_name = lobby_message.return_message(users_database.get_user_name(chat_id)) + await bot.edit_message_text(text=lobby_message.return_message(user_name), + chat_id=chat_id, + message_id=users_database.get_user_game_page_message_id( + chat_id=chat_id), + reply_markup=lobby_message.create_keyboard(user_name)) + except TelegramBadRequest: + pass + if st == FSMLobbyClass.in_game: + print(1) + game_message = games_messages_dict.get(data['lobby']) + if message.chat.id in game_message.users_wined: + game_message.delete_user(message.chat.id, 0) + for chat_id in lobby_stat: + try: + await bot.edit_message_text(text=str(game_message), chat_id=chat_id, + message_id=users_database.get_user_game_page_message_id( + chat_id=message.chat.id), + reply_markup=game_message.create_game_keyboard( + chat_id=int(chat_id)) + ) + except TelegramBadRequest: + pass + else: + games_messages_dict[data['lobby']] = None + for chat_id in lobby_stat: + try: + user_name = lobby_message.return_message(users_database.get_user_name(chat_id)) + lobby_message.update_ready_info(ready=0, user=user_name) + await bot.edit_message_text(text=f'Игрок {data["user_name"]} покинул игру', chat_id=chat_id, + message_id=users_database.get_user_game_page_message_id( + chat_id=message.chat.id), + reply_markup=None) + await bot.send_message(text=lobby_message.return_message(user_name), + chat_id=chat_id, + reply_markup=lobby_message.create_keyboard(data['user_name'])) + except TelegramBadRequest: + pass + lobby_pages = create_lobbies_page() + people_without_lobby = users_database.get_statistic_of_users_without_lobby() + await state.update_data(lobby=None) + for chat_id, message_id in people_without_lobby: + try: + keyboard = create_inline_kb(width=2, dct=lobby_pages, last_btn='create_new_lobby', + back_button='menu') + await bot.edit_message_reply_markup(chat_id=chat_id, message_id=message_id, + reply_markup=keyboard) + except TelegramBadRequest: + pass + + +def update_previous_pages(data, callback_data): + if callback_data in data['previous_pages']: + data['previous_pages'] = data['previous_pages'][:-2] + return data + + +def get_valid_cards(cards: list, card: list, royal_card: str): + valid_cards = [] + royal_card = royal_card.split('-') + print(royal_card, card, cards) + if card[1] == royal_card[1]: + print(1) + for i in cards: + cur_card = i.split('-') + if cur_card[1] == royal_card[1] and lexicon_card_faces[card[0]] > lexicon_card_faces[cur_card[0]]: + valid_cards.append(i) + elif cur_card[1] != royal_card[1]: + valid_cards.append(i) + else: + for i in cards: + cur_card = i.split('-') + if cur_card[1] == card[1] and lexicon_card_faces[card[0]] > lexicon_card_faces[cur_card[0]]: + valid_cards.append(i) + return valid_cards + + +def get_valid_faces(cards: list): + cards = [i.split('-') for i in cards] + return [i[0] for i in cards]