【教程】0x04 绘制记牌器窗口及插件打包 —— 通过MYScrcpy学python系列

前言

MYScrcpy Github / Gitee
从 1.6.4 版本开始,MYScrcpy为童鞋们提供了一个开放的插件开发环境(Extensions)。童鞋们可以根据需要自行开发插件。

本章主要讲解如何通过面向对象的思想,借助Dearpygui绘制一个记牌器面板,同时如何打包插件进行共享。

注意

插件用于功能测试及教学目的,切勿违法违规使用!

我们开始吧

创建功能类

  1. ddz类下创建ddz_cls.py,新建对象类
  2. 既然是记牌器,首先要把游戏各个对象抽象出来,形成类
@dataclass
class Card:
    """
        Poker Card
    """
    name: str
    index: int
    max_n: int = 4

    def __hash__(self):
        return hash(self.name)


class Cards:
    """
        Poker Card Type
    """
    CARD_D = Card("D", index=0, max_n=1)
    CARD_X = Card("X", index=1, max_n=1)

    CARD_2 = Card("2", index=2)

    CARD_A = Card("A", index=3)
    CARD_K = Card("K", index=4)
    CARD_Q = Card("Q", index=5)
    CARD_J = Card("J", index=6)
    CARD_T = Card("T", index=7)
    CARD_9 = Card("9", index=8)
    CARD_8 = Card("8", index=9)
    CARD_7 = Card("7", index=10)
    CARD_6 = Card("6", index=11)
    CARD_5 = Card("5", index=12)
    CARD_4 = Card("4", index=13)
    CARD_3 = Card("3", index=14)

    @classmethod
    def ordered_cards(cls) -> list[Card]:
        """
            卡牌顺序列表
        :return:
        """
        return [
            cls.CARD_D, cls.CARD_X,
            cls.CARD_2, cls.CARD_A,
            cls.CARD_K, cls.CARD_Q, cls.CARD_J,
            cls.CARD_T, cls.CARD_9, cls.CARD_8, cls.CARD_7, cls.CARD_6, cls.CARD_5, cls.CARD_4, cls.CARD_3
        ]

    @classmethod
    def str2cards(cls, cmd: str) -> list[Card] | None:
        """
            cmd to card list
            B5 means Bomb 4
            SQ means qqq
            E8 means 88

        :param cmd:
        :return:
        """
        cmd = cmd.upper()
        if len(cmd) in [3, 4] and cmd[1] == '-':
            try:
                card_0 = getattr(cls, f"CARD_{cmd[0]}")
                card_1 = getattr(cls, f"CARD_{cmd[2]}")
            except Exception as e:
                return None

            return cls.ordered_cards()[min(card_0.index, card_1.index):max(card_0.index, card_1.index)+1] * (
                1 if len(cmd) == 3 else int(cmd[3])
            )

        cmd = cmd.replace('W', 'DX')

        repeat = 0
        cards = []

        for _ in cmd:
            if repeat:
                cards.extend([getattr(cls, f"CARD_{_}")] * repeat)
                repeat = 0

            else:
                if _ == 'B':
                    repeat = 4
                    continue

                elif _ == 'S':
                    repeat = 3
                    continue

                elif _ == 'E':
                    repeat = 2
                    continue

                else:
                    cards.append(getattr(cls, f"CARD_{_}"))

        return cards

定义卡槽,存储卡序列

class CardSlot:
    """
        卡槽
    """

    def __init__(self, max_slots: int):
        self.max_slots = max_slots
        self.cards = []

    def __len__(self):
        return len(self.cards)

    def add_cards(self, cards: Iterable[Card] | None):
        """
            新增卡
        :param cards:
        :return:
        """
        cards and self.cards.extend(cards)

    @property
    def card_dict(self):
        """
            卡字典
        :return:
        """
        _ = {}

        for card in self.cards:
            _.setdefault(card, {'n': 0})['n'] += 1

        return _

定义玩家、出牌环节及游戏局

class Player:
    """
        玩家
    """
    def __init__(self, name: str, is_landlord: bool = False, public_cards: list[Card] = None):

        self.name = name

        self.is_landlord = is_landlord

        self.slot_on_table = CardSlot(20)
        self.slot_hold = CardSlot(20)

        if is_landlord and public_cards:
            self.slot_hold.add_cards(public_cards)

    @property
    def hold_cards_n(self) -> int:
        """
            剩卡数
        :return:
        """
        return (20 if self.is_landlord else 17) - len(self.slot_on_table)


@dataclass
class Round:
    """
        一轮游戏
    """
    index: int
    player: Player
    action: int
    cards: list[Card] = field(default_factory=list)


class Rounds:

    ACTION_PASS = 0
    ACTION_PLAY = 1

    PLAYER_N = 3

    def __init__(self, who_is_landlord: int, public_cards: list[Card], self_cards: list[Card]):

        self.players = [
            Player(['L', 'Y', 'R'][_], who_is_landlord == _, public_cards) for _ in range(3)
        ]

        self.now = who_is_landlord

        self.slot_on_table = CardSlot(54)

        self.card_rounds = []

        self.player_self.slot_hold.add_cards(self_cards)

    @property
    def player_self(self) -> Player:
        return self.players[1]

    @property
    def player_current(self) -> Player:
        return self.players[self.now]

    def play(self, action: int, cards: list[Card]) -> Round:
        """
            出牌
        :param action:
        :param cards:
        :return:
        """

        _r = Round(len(self.card_rounds), self.players[self.now], action, cards)

        self.card_rounds.append(_r)

        self.slot_on_table.add_cards(cards)

        self.now += (1 if self.now < (self.PLAYER_N - 1) else -(self.PLAYER_N - 1))

        return _r

定义一个记牌器组件及记牌器窗口

class CardsBoard:
    """
        记牌器
    """
    def __init__(self):
        self.tag_table = dpg.generate_uuid()

    def clear(self):
        dpg.delete_item(self.tag_table, children_only=True, slot=1)

    def draw(self, parent: str | int):
        with dpg.table(tag=self.tag_table, parent=parent):
            for card in Cards.ordered_cards():
                dpg.add_table_column(label=card.name)

            dpg.add_table_column(label='n')

    def update(self, rounds: Rounds):

        self.clear()

        with dpg.table_row(parent=self.tag_table):
            cards_out_all = 0
            for card in Cards.ordered_cards():
                cards_n = rounds.slot_on_table.cards.count(card)
                cards_out = card.max_n - cards_n - rounds.player_self.slot_hold.cards.count(card)
                cards_out_all += cards_out

                msg = ''
                if cards_out > 0:
                    if cards_out == 4:
                        msg += '@4'
                    else:
                        msg += f"{cards_out}"

                dpg.add_text(msg)

            dpg.add_text(str(cards_out_all))


class DDZBoard:
    """
        斗地主记牌器
    """

    players = ['Left', 'Your', 'Right']

    def __init__(self):
        self.tag_win = dpg.generate_uuid()

        self.tag_ipt_yc = dpg.generate_uuid()
        self.tag_ipt_cards = dpg.generate_uuid()
        self.tag_txt_cur_player = dpg.generate_uuid()

        self.board_cards = CardsBoard()

    def new_round(self, sender, app_data, user_data):
        """
            新游戏
        :param sender:
        :param app_data:
        :param user_data:
        :return:
        """
        self.rounds = Rounds(
            self.players.index(dpg.get_value(user_data[0])),
            Cards.str2cards(dpg.get_value(user_data[1])),
            Cards.str2cards(dpg.get_value(user_data[2]))
        )

        dpg.set_value(user_data[1], '')
        dpg.set_value(user_data[2], '')

        self.clear()
        self.update()

    def play(self, sender, app_data, user_data):
        if user_data is None:
            self.rounds.play(Rounds.ACTION_PASS, [])

        else:
            self.rounds.play(Rounds.ACTION_PLAY, Cards.str2cards(dpg.get_value(user_data)))
            dpg.set_value(user_data, '')

        self.update()

    def draw(self):
        with dpg.window(tag=self.tag_win, width=700, height=160, label='Cards Board'):

            with dpg.group(horizontal=True):
                tag_ll = dpg.add_combo(
                    items=self.players, label='LandLord', default_value=self.players[1], width=60)
                tag_pub = dpg.add_input_text(label='Public cards', width=50)
                self.tag_ipt_cards = dpg.add_input_text(tag=self.tag_ipt_yc, label='Your cards', width=150)
                dpg.add_button(label='Start', user_data=(tag_ll, tag_pub, self.tag_ipt_yc), callback=self.new_round)

            dpg.add_separator()

            with dpg.group(horizontal=True):
                dpg.add_text(tag=self.tag_txt_cur_player, default_value='Ready')
                dpg.add_input_text(label='Cards', width=150)
                dpg.add_button(label='Play', user_data=dpg.last_item(), callback=self.play)
                dpg.add_button(label='Pass', callback=self.play)

            dpg.add_separator()

            self.board_cards.draw(self.tag_win)

    def clear(self):
        self.board_cards.clear()
        dpg.set_value(self.tag_txt_cur_player, f"Ready")

    def update(self):
        self.board_cards.update(self.rounds)
        dpg.set_value(self.tag_txt_cur_player, f"Wait Player {self.rounds.player_current.name}")
  1. 在插件中引入记牌器类,并添加相应功能
...
def start(self):
	...
    # 创建记牌器面板
    self.board = DDZBoard()

def predict(self):
	...
	# 玩家手牌位置
    if y0 > 0.6:
       name = names[int(box.cls[0])][1]
       if name.upper() == 'O':
       	   # 判断大小王
           _img = Image.fromarray(
               result.orig_img
           ).crop([
               round(_) for _ in box.xyxy.tolist()[0]
           ])
           blue = self.cal_blue(_img)
           if blue > 5:
               cards += 'D'
           else:
               cards += 'X'

       else:
           cards += name.upper()

   new_cards = ''

   for card in Cards.ordered_cards():
       new_cards += card.name * cards.count(card.name)

   dpg.set_value(self.board.tag_ipt_cards,  new_cards)

 @staticmethod
 def cal_blue(img: Image.Image) -> int:
     pixels = img.size[0] * img.size[1]
     colors = img.getcolors(pixels)
     return round(sum([
         count / pixels for count, pixel_color in
         colors if pixel_color[0] < 50 and pixel_color[1] < 50 and pixel_color[2] > 150
     ]) * 100)

插件打包及共享

实际上,插件打包非常简单
新建一个ddz文件夹,将项目相关的文件拷贝其中
|- ddz
|- __init__.py
|- extension.toml
|- ddz.py
|- ddz_cls.py
|- pts/best.pt

将整个ddz文件压缩成ddz.zip文件
这样,插件就打包完成了。

注意,ZIP文件中需包含顶层文件夹,即打开ZIP文件时目录为 ddz文件夹
在这里插入图片描述

运行MYScrcpy,查看效果

将打包好的插件 ddz.zip 拷贝至 myscrcpy 插件目录下

~/.myscrcpy/extensions/

本次直接使用 mysc-cli 命令启动 MYScrcpy
可以看到,已经通过加载插件的方式加载ddz插件
在这里插入图片描述
加载日志:
在这里插入图片描述
现在,你可以将 ddz.zip 分享给你的小伙伴,通过MYScrcpy直接加载使用了!

注意,在加载插件时,需要提前安装插件运行所需的 PIL/Opencv/ultralytics 等包

总结

至此,我们借助MYScrcpy 插件架构完成了一个基础的记牌器,本系列教程也将告一段落。
因篇幅有限,插件功能简陋,不过正如笔者写这个系列的初衷一样,授之于鱼,不如授之以渔。
希望各位童鞋能通过这个系列了解MYScrcpy,了解Python,进而编写创造属于你自己的MYScrcpy插件!

文中ddz.zip已经放到群共享。
有任何问题欢迎留言或加Q群579618095交流。

python记牌器是一种用于记录卡牌游戏中玩家所出的牌并推演剩余牌的工具。在QQ游戏“升级”中,有一个名为“升级”的自动记牌器,它可以实时自动记录每局出牌,并推演剩下什么牌,还可以在每局游戏结束时自动保存记录。该记牌器的功能可以通过在Python的运行环境中运行一个名为"recorder/SHENGJI.py"的脚本来实现。 关于python实现记牌器的原理,有两种方案。方案一是减牌记牌,***开发图形化编程界面,比如使用PyQt5图形化编程界面。你可以使用PyCharm来搭建pyqt5开发环境,具体的安装步骤可以参考相关教程。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [QQCardRecorder:QQ游戏 欢乐升级 记牌器](https://download.csdn.net/download/weixin_42107165/19783580)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [python 实现记牌器原理](https://blog.csdn.net/weixin_43724249/article/details/130462030)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [如何用Python制作图形化界面?PyCharm搭建pyqt5开发环境](https://blog.csdn.net/kobepaul123/article/details/120373783)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值