Exemple #1
0
class Game:
    MIN_PLAYERS = 4
    STATUS_PENDING = "pending"
    STATUS_LAUNCHED = "launched"
    STATUS_CANCELED = "canceled"

    def __init__(self, group_chat_id, app):
        self.group_chat_id = group_chat_id
        self.channels = []
        self.status = Game.STATUS_PENDING
        self.board = None
        self.app = app
        self.app["events"].subscribe_callback(self.group_chat_id, self)
        self.callback_input = dict()
        self.table = "```Здесь отобразится стол```"
        self.log = ["```Здесь лог событий```"]

    async def update_callback(self, callback: Callback):
        assert type(callback.sender) == User
        if callback.chat_id not in self.callback_input:
            self.callback_input[callback.chat_id] = []
        self.callback_input[callback.chat_id].append(callback)
        await asyncio.sleep(0)
        return

    def add_player(self, player_id):
        if player_id not in self.channels:
            self.channels.append(player_id)

    def can_start(self) -> bool:
        return True  # TODO: delete
        return len(self.channels) >= Game.MIN_PLAYERS

    async def start(self):
        if not self.can_start():
            await self.print_group(
                f"Игра отменена. Требуется минимум {Game.MIN_PLAYERS} игроков")
            self.app["events"].unsubscribe_callback(self.group_chat_id)
            for p in self.channels:
                self.app["events"].unsubscribe_callback(p.user_id, self)
            return
        await self.print_group(game_info["description"] +
                               game_info["on_start_tip"])
        if self.app["cfg"].DEBUG:
            self.channels = [{
                "group_chat_id": self.group_chat_id,
                "user_id": 435627225,
                "user_fullname": "Дмитрий Калекин",
                "user_alert": "@herr_horror Дмитрий"
            }, {
                "group_chat_id": self.group_chat_id,
                "user_id": 435627225,
                "user_fullname": "Dmitriy Zaytsev",
                "user_alert": "@1"
            }, {
                "group_chat_id": self.group_chat_id,
                "user_id": 435627225,
                "user_fullname": "Zag",
                "user_alert": "@2"
            }, {
                "group_chat_id": self.group_chat_id,
                "user_id": 435627225,
                "user_fullname": "Александр Грицай",
                "user_alert": "@3"
            }]
        self.board = Board(self.channels)
        for p in self.board.players:
            assert type(p) == Player
            self.app["events"].subscribe_callback(p.user_id, self)
        await self.run()

    # TODO: ------------------------------- refactor -------------------

    async def show_cards_to_all(self):
        if self.app["cfg"].DEBUG:
            for p in self.board.players:
                await self.show_cards(p)
        else:
            await asyncio.gather(
                *[self.show_cards(p) for p in self.board.players])

    async def show_cards(self, p: Player):
        # if not p.title_message_id:
        #     r1 = await self.app["telebot"].sendMessage(p.user_id, f"Ваше имя: *{p.user_fullname}* -------------------------------- ")
        #     p.title_message_id = r1["result"]["message_id"]

        if not p.table_message_id:
            r2 = await self.app["telebot"].sendMessage(p.user_id, self.table)
            p.table_message_id = r2["result"]["message_id"]

        if not p.hand_slots or len(p.hand_slots) == 0:
            await self.create_image_slots(p)
        else:
            await self.update_image_slots(p)

        if not p.panel_message_id:
            r3 = await self.app["telebot"].sendMessage(
                p.user_id,
                f"`[Это сообщение обновится, и вы выберете ваше действие с картами]`"
            )
            p.panel_message_id = r3["result"]["message_id"]
        else:
            await self.app["telebot"].editMessageText(p.user_id,
                                                      p.panel_message_id,
                                                      "\r\n".join(p.local_log))

        if not p.log_message_id:
            r4 = await self.app["telebot"].sendMessage(p.user_id,
                                                       "\n".join(self.log))
            p.log_message_id = r4["result"]["message_id"]
        return

    async def show_play_drop_options(self, p):
        await self.app["telebot"].editMessageText(
            p.user_id,
            p.panel_message_id,
            "\r\n".join(p.local_log),
            reply_markup={
                "inline_keyboard": [
                    *[[{
                        "text": f"▶️ {play_card.name}",
                        "callback_data": f"phase2:play_card {play_card.uuid}"
                    }] for play_card in p.get_possible_play()],
                    *[[{
                        "text": f"🗑 {drop_card.name}",
                        "callback_data": f"phase2:drop_card {drop_card.uuid}"
                    }] for drop_card in p.get_possible_drop()]
                ]
                # 🖐  🕹 Joystick 🗑 Wastebasket ☣ Biohazard 🎮 🎯 Direct Hit
            },
            parse_mode="markdown")

    async def show_give_options(self, p, receiver, can_def=False):
        def_buttons = []
        if can_def:
            def_buttons = [[{
                "text":
                f"🛡 {block_card.name}",
                "callback_data":
                f"phase3:block_exchange_card {block_card.uuid}"
            }] for block_card in p.get_possible_block_exchange()]

        await self.app["telebot"].editMessageText(
            p.user_id,
            p.panel_message_id,
            "\r\n".join(p.local_log),
            reply_markup={
                "inline_keyboard": [
                    *[[{
                        "text": f"🎁 {give_card.name}",
                        "callback_data": f"phase3:give_card {give_card.uuid}"
                    }] for give_card in p.get_possible_give(receiver)],
                    *def_buttons
                ]
                #   🕹 Joystick 🗑 Wastebasket ☣ Biohazard 🎮 🎯 Direct Hit
            },
            parse_mode="markdown")

    async def print_group(self, msg: str, **kwargs):
        return await self.app["telebot"].sendMessage(self.group_chat_id, msg,
                                                     **kwargs)

    async def listen_input(self, p: Player):
        while True:
            await asyncio.sleep(1)
            if p.user_id not in self.callback_input:
                continue
            events = self.callback_input[p.user_id]
            index = None
            for i, clb in enumerate(events):
                if clb.message_id == p.panel_message_id:
                    index = i
            if index is not None:
                c = self.callback_input[p.user_id].pop(index)
                return c.data, p

    async def clear_input(self, p: Player):
        assert p.panel_message_id is not None
        return await self.app["telebot"].editMessageText(
            p.user_id, p.panel_message_id, "\r\n".join(p.local_log))

    async def show_table_to_all(self, table):
        assert type(self.board.players) == list
        await asyncio.gather(
            *[self.show_table(p, table) for p in self.board.players])
        return

    async def show_table(self, p: Player, table: str):
        try:
            await self.app["telebot"].editMessageText(p.user_id,
                                                      p.table_message_id,
                                                      table)
        except Warning:
            print("Стол остался прежним")

    async def show_log_to_all(self, msg: str):
        self.log.append(msg)
        while len(self.log) > 6:
            _ = self.log.pop(0)
        assert type(self.board.players) == list
        for p in self.board.players:
            await self.show_log(p)
            # self.app.loop.create_task(self.show_log(p))
        return

    async def show_log(self, p: Player):
        await self.app["telebot"].editMessageText(p.user_id, p.log_message_id,
                                                  "\n".join(self.log))

    async def create_image_slots(self, p):
        """
        Создаём слоты для изображений 
        """

        top_card_image = "https://eva-bot.ru/res/normal/min/top-card-950x1343-min.png"
        media = list([
            f"https://eva-bot.ru/res/normal/min/{choice(h.images)}-950x1343-min.png"
            for h in p.hand
        ])
        if len(media) < 5:
            media.append(top_card_image)

        r2 = await self.app["telebot"].sendMediaGroup(
            p.user_id,
            list([{
                "type": "photo",
                "media": image
            } for image in media]))
        for i, msg in enumerate(r2["result"]):
            p.hand_slots.append(msg["message_id"])

    async def update_image_slots(self, p):
        """
        Обновляем слоты для изображений -  только изменившиеся
        """
        counter = 0
        for card in p.hand:
            assert type(card) == Card
            image = f"https://eva-bot.ru/res/normal/min/{choice(card.images)}-950x1343-min.png"
            try:
                self.app.loop.create_task(self.app["telebot"].editMessageMedia(
                    p.user_id, p.hand_slots[counter], {
                        "type": "photo",
                        "media": image
                    }))
            except Warning:
                print(f"Изображение карты осталось старым: {card.name}")
            counter += 1

        for i in range(counter, len(p.hand_slots)):
            image = "https://eva-bot.ru/res/normal/min/top-card-950x1343-min.png"
            try:
                self.app.loop.create_task(self.app["telebot"].editMessageMedia(
                    p.user_id, p.hand_slots[counter], {
                        "type": "photo",
                        "media": image
                    }))
            except Warning:
                print(f"Изображение осталось старым: top-card")

    def print_hands(self):
        output = f"Ход {self.board.move}, ходит *{self.board.current_player().user_fullname}* \r\n"
        for i, p in enumerate(self.board.players):
            turn = "✅" if i == self.board.turn else "⏳"  # ☣️ # 🤢
            # output += "```"
            name = f"*{p.name}*" if p == self.board.current_player(
            ) else p.name
            output += f"{turn} {p.avatar} {name}\r\n"
            # output += "```"
            # for o in p.get_cards_names():
            #     if o == "Заражение":
            #         output += "`[`🤢`" + o + "]`; "
            #     if o == "Нечто":
            #         output += "`[`🍄`" + o + "]`; "
            #     else:
            #         output += "`[" + o + "]`; "
            if len(p.global_log) > 0:
                output += '\r\n'.join(
                    [f"             `{s}`" for s in p.global_log]) + "\r\n"
            else:
                output += "`       ...`\r\n"
        return output

    async def phase1(self, p: Player) -> bool:
        """
        Фаза взятия карты и игры паники
        Возвращает необходимость продолжать код
        """
        # p.global_log = "тянет карту с колоды..."
        # await self.show_table_to_all()
        # await self.show_log_to_all(f"Фаза 1. {p.user_fullname} тянет карту с колоды")
        card = p.pull_deck()
        assert type(card) == Card
        if not card.is_panic():
            p.take_on_hand(card)
            p.local_log.append(f"🎲 событие `{card.name}` c колоды")
            p.global_log.append(f"🎲 Вытянул событие из колоды")
            await self.show_cards(p)
            return True
        else:
            p.local_log.append(
                f"🔥 паника `{card.name}` с колоды, ход завершён.")
            p.global_log.append(
                f"🔥 Вытянул панику `{card.name}` с колоды. Ход завершён.")
            p.play_panic(card)
            self.board.deck.append(card)  # карта паники ушла в колоду
            return False
        return True

    async def phase2(self, p: Player):
        """
        Фаза сброса или игры карты с руки
        """
        # Обновили карты на руке игрока и ждём от него хода
        # await self.show_cards(p)
        p.local_log.append(f"❗️ Сыграйте ▶️ или сбросьте 🗑 карту...")
        p.global_log.append(f"🃏 Играет или сбрасывает...")
        await asyncio.gather(*[
            self.show_play_drop_options(p),
            self.show_table_to_all(self.print_hands())
        ])

        cmd = None
        # while cmd not in ["phase2:play_card", "phase2:drop_card"]:
        # await asyncio.sleep(0)
        full_input, triggered_player = await self.listen_input(p)
        cmd, card_uuid = full_input.split(" ")
        assert cmd == "phase2:play_card" or cmd == "phase2:drop_card"
        assert triggered_player == p
        card = p.pop_card_by_uuid(int(card_uuid))  # выбранная карта
        assert type(card) == Card
        if cmd == "phase2:play_card":
            p.play_card(card, target=None)
            p.local_log[-1] = f"▶️ сыграна `{card.name}`"
            p.global_log[-1] = f"▶️ Сыграл карту `{card.name}`"
        else:
            p.local_log[-1] = f"🗑 сброшена `{card.name}`"
            p.global_log[-1] = f"🗑 Сбросил карту"

        await self.clear_input(p)

        p.drop_card(card)  # в любом случае в колоду
        await asyncio.gather(
            *[self.show_cards(p),
              self.show_table_to_all(self.print_hands())])
        return

    async def proccess_exchange(self, p: Player, next_player: Player):
        full_input, player = await self.listen_input(p)
        assert player == p
        cmd, card_uuid = full_input.split(" ")
        # BUG: assertion error here
        assert cmd in ["phase3:give_card", "phase3:block_exchange_card"]
        my_card = player.pop_card_by_uuid(int(card_uuid))
        assert type(my_card) == Card

        if cmd == "phase3:give_card":
            player.local_log[
                -1] = f"🎁 отдана `{my_card.name}` для *{next_player.user_fullname}*"
            player.global_log[
                -1] = f"♣️ Отдал карту для {next_player.user_fullname}"

            await asyncio.gather(*[
                self.clear_input(player),
                self.show_cards(player),
                self.show_table_to_all(self.print_hands())
            ])
        else:
            assert cmd == "phase3:block_exchange_card"
            player.local_log[
                -1] = f"🛡 сыграна защита `{my_card.name}` от *{next_player.user_fullname}*"
            player.global_log[
                -1] = f"🛡 Защитился `{my_card.name}` от обмена с {next_player.user_fullname}"

            await asyncio.gather(*[
                self.clear_input(player),
                self.show_cards(player),
                self.show_table_to_all(self.print_hands())
            ])
        return player, my_card

    async def phase3(self, p: Player):
        next_player = self.board.player_next()
        p.local_log.append(f"❗️ Передайте карту *{next_player.user_fullname}*")
        p.global_log.append(f"💤 Передаёт карту {next_player.user_fullname}")

        next_player.local_log.append(
            f"❗️ Передайте карту *{p.user_fullname}*, либо защититесь 🛡 от обмена."
        )
        next_player.global_log.append(f"💤 Передаёт карту {p.user_fullname}")

        exchangers = await asyncio.gather(*[
            self.show_give_options(p, next_player),
            self.show_give_options(next_player, p, can_def=True),
            self.show_table_to_all(self.print_hands()),
            self.proccess_exchange(p, next_player),
            self.proccess_exchange(next_player, p)
        ])
        p1, card1 = exchangers[3]
        p2, card2 = exchangers[4]
        assert type(p1) == Player
        assert type(p2) == Player
        assert type(card1) == Card
        assert type(card2) == Card
        p1.take_on_hand(card2, sender=p2)
        p2.take_on_hand(card1, sender=p1)

        p1.local_log.append(
            f"🤲 получена `{card2.name}` от *{p2.user_fullname}*")
        p2.local_log.append(
            f"🤲 получена `{card1.name}` от *{p1.user_fullname}*")
        p1.global_log[-1] = f"👌🏻 Передал карту {p2.user_fullname}"
        p2.global_log[-1] = f"👌🏻 Передал карту {p1.user_fullname}"

        await asyncio.gather(*[
            self.app.loop.create_task(self.show_cards(p1)),
            self.app.loop.create_task(self.show_cards(p2)),
            self.app.loop.create_task(
                self.show_table_to_all(self.print_hands()))
        ])
        return

    async def run(self):
        # До старта игры показываем карты игрокам, пишем историю ситуации и ждём, чтобы они прочитали
        p = None
        await self.show_cards_to_all()
        while not self.board.is_end:
            await asyncio.sleep(0)

            if p:
                p.global_log = []
                p.local_log.append("`----------`")
                if len(p.local_log) > 10:
                    for _ in range(0, 5):
                        p.local_log.pop(0)

            self.board.next_turn()
            p = self.board.current_player()

            # Рисуем стол и очередность в общем чате
            await self.show_table_to_all(self.print_hands())
            # await self.print_group(self.table)
            # Тянем карту: либо паника и переход хода, либо фаза сыграть карту с руки
            p.global_log = []
            if len(p.local_log) > 0:
                p.local_log.append("`----------`")

            if not await self.phase1(p):
                print("=============================")
                # self.app.loop.create_task(self.delay_flush(p))
                continue
            await self.phase2(p)
            await self.phase3(p)
            # await self.app.loop.create_task(self.delay_flush(p))
            print("=============================")
        await self.print_group("game ended")
        return