简介

程序基于Python3.7开发的斗地主AI出牌助手,目前支持欢乐斗地主桌面版,微信版,也可以自己制作相应其他版本。
此出牌助手核心是识别出三位玩家出牌内容,调用基于DouZero封装的API接口,输入出牌内容,根据AI出牌方案,打出相应的牌。

运行效果

![](https://img2023.cnblogs.com/blog/129408/202311/129408-20231114235431212-40412513.gif)

核心功能

  • 手牌和位置识别
    1. 游戏刚开始根据屏幕位置,截图识别AI玩家手牌及三张底牌
    2. 根据玩家手牌判断是否抢地主和加倍
    3. 确认三者之间的关系,识别地主和农民角色,确认队友及对手关系
  • 识别每轮三位玩家出牌
    1. 根据提示按钮,判断当前的操作
    2. 识别三位玩家此轮出的牌,并记录下来
  • AI出牌方案输出
    1. 将出牌记录按格式要求发送给斗地主AI
    2. 获取斗地主AI出牌方案,选取最高胜率方案
    3. 根据AI出牌方案,选择对相应的牌,并出牌

素材准备

  • 区域定位,获取坐标值
    斗地主AI出牌助手--在线调用斗地主AI,实现自动斗地主-LMLPHP

  • 按钮及牌面
    斗地主AI出牌助手--在线调用斗地主AI,实现自动斗地主-LMLPHP

核心代码

  • 根据指定窗口句柄截图

    def WindowShot(self):
        """
        根据窗口句柄截图
        返回: 图片对象
        """
        windll.user32.SetProcessDPIAware()
        hwnd = self.Handle
        left, top, right, bottom = win32gui.GetClientRect(hwnd)
        w = right - left
        h = bottom - top
        hwnd_dc = win32gui.GetWindowDC(hwnd)
        mfc_dc = win32ui.CreateDCFromHandle(hwnd_dc)
        save_dc = mfc_dc.CreateCompatibleDC()
        bitmap = win32ui.CreateBitmap()
        bitmap.CreateCompatibleBitmap(mfc_dc, w, h)
        save_dc.SelectObject(bitmap)
        # If Special K is running, this number is 3. If not, 1
        result = windll.user32.PrintWindow(hwnd, save_dc.GetSafeHdc(), 3)
        bmpinfo = bitmap.GetInfo()
        bmpstr = bitmap.GetBitmapBits(True)
        img = np.frombuffer(bmpstr, dtype=np.uint8).reshape((bmpinfo["bmHeight"], bmpinfo["bmWidth"], 4))
        img = np.ascontiguousarray(img)[..., :-1]  # make image C_CONTIGUOUS and drop alpha channel
        #img = Image.frombuffer("RGB",(bmpinfo['bmWidth'], bmpinfo['bmHeight']),bmpstr, 'raw', 'BGRX', 0, 1)
        if not result:  # result should be 1
            win32gui.DeleteObject(bitmap.GetHandle())
            save_dc.DeleteDC()
            mfc_dc.DeleteDC()
            win32gui.ReleaseDC(hwnd, hwnd_dc)
            raise RuntimeError(f"Unable to acquire screenshot! Result: {result}")
        #cv2.imwrite('./imgs/print.png', img)
        #return img
        return cv2.cvtColor(img, cv2.COLOR_BGRA2BGR)
    
  • 在图片中查找第一个相似的图片

    def LocateOnImage(self, template, image=None, region=None, confidence=0.9, grayscale=True):
        """
        在image中寻找template,返回第一个查找到的范围
    
        参数:
        template: 需要查找的图片,文件名或图片对象
        image: 被查找的图片,文件名或图片对象
        region: 查找范围
        confidence: 置信度
        grayscale: 是否为灰度图
    
        返回值:
        查找到的图片范围
        """
        if image is None:
            image = self.WindowShot()
        return pyautogui.locate(template, image, region=region, confidence=confidence, grayscale=grayscale)
    
  • 在图片中查找所有相似的图片

    def LocateAllOnImage(self, template, image=None, region=None, confidence=0.9, grayscale=True):
        """
        在image中寻找template,返回第一个查找到的范围
    
        参数:
        template: 需要查找的图片,文件名或图片对象
        image: 被查找的图片,文件名或图片对象
        region: 查找范围
        confidence: 置信度
        grayscale: 是否为灰度图
    
        返回值:
        查找到的图片范围
        """
        if image is None:
            image = self.WindowShot()
        return pyautogui.locateAll(template, image, region=region, confidence=confidence, grayscale=grayscale)
    
  • 识别手牌

    def GetCards(self, image, player):
        hand_cards = []
        cards = ""
        start_x = 0
        width = player["width"]
        for card in self.AllCardsNC:
            confidence = player["confidence"] or 0.85
            grayscale = True
            if(card in ('D', 'X')):
                confidence = 0.85
                grayscale = False
    
            card_key = player["prefix"] + card
            matches = self.LocateAllOnImageName(card_key, image, player["region"], confidence, grayscale)
    
            if len(matches) > 0:
                sorted_matches = sorted(matches, key=lambda match: match[0])
                #print(target_position)
                #print(mark)
    
                if(card != 'X'):
                    start_x = 0
    
                for match in sorted_matches:
                    if(start_x == 0 or start_x + player["width"] < match[0]):
                        #大小王容易判断错误,需要再判断一下颜色值
                        if(card == 'D'):
                            #match = sorted_matches[0]
                            y,x,h,w = match
                            cropped_image = image[x:x+w, y:y+h]
                            #colors = cv2.mean(cropped_image)
                            mean, stddev = cv2.meanStdDev(cropped_image)
                            #print("均值:", mean)
                            #print("差值:", stddev)
                            if(stddev[2][0] > 30):
                                continue
                        start_x = match[0]
                        width = match[2]
                        hand_cards.append({card:match})
                        cards += card
        
        return cards, hand_cards
    
  • 将出牌记录发送给AI,进行预测

    def GetCardsForPredict(self):
        #将出牌记录发送给AI,进行预测
        resultStr = self.PostPredict()
        #获取最高胜率的出牌
        last_move_cards = self.GetPredictWinRates(resultStr)
        return last_move_cards
    
  • 出牌

    def PlayedCards(self, cards, image=None):
        if image is not None:
            window_shot_image = image
        else:
            window_shot_image = self.WindowShot()
        player_self_cards, range = self.GetCards(window_shot_image, self.Players["PlayerSelf"])
        select_index = []
        select_cards = []
        #将cards中的字符顺序反转
        play_cards = cards[::-1]
        cards = play_cards
    
        for c in cards:
            index = -1
            for card in player_self_cards:
                index += 1
                
                if c == card and index not in select_index:
                    select_index.append(index)
                    select_cards.append(c)
                    #print(range[index])
                    cards = cards.replace(c, '', 1)
                    card_range = range[index][c]
                    break
    
        cards = play_cards
        index = -1
        #window_shot_image = self.WindowShot()
        for n in select_index:
            window_shot_image = self.WindowShot()
            player_self_cards, range = self.GetCards(window_shot_image, self.Players["PlayerSelf"])
            if(len(player_self_cards) > n):
                #player_self_cards = player_self_cards
                index += 1
                card_range = range[n][cards[index]]
                #如果要出的牌还未选择完毕,则点击
                if(card_range[1] > self.CardTop):
                    self.LeftClick(card_range)
            
            time.sleep(0.2)
    
        return self.GetSelectCards(play_cards)
    
  • DouZero 的请求结构体

    Predict = {
        "bomb_num":0,#炸弹数量
        "card_play_action_seq":'',#历史出牌动作序列,用逗号分隔
        "last_move_landlord":'',#地主最后出的牌
        "last_move_landlord_down":'',#地主下家最后出的牌
        "last_move_landlord_up":'',#地主上家最后出的牌
        "num_cards_left_landlord":20,#地主手牌剩余数量
        "num_cards_left_landlord_down":17,#地主下家手牌剩余数量
        "num_cards_left_landlord_up":17,#地主上家手牌剩余数量
        "other_hand_cards":'',#还剩余的牌
        "played_cards_landlord":'',#地主所有出的牌
        "played_cards_landlord_down":'',#地主下家所有出的牌
        "played_cards_landlord_up":'',#地主上家所有出的牌
        "player_hand_cards":'',#玩家手中的牌
        "player_position":0,	#-当前玩家的位置序号 0 地主,1 地主下家,2 地主上家
        "three_landlord_cards":''#三张底牌
    }
    

可用的 DouZero 后端地址

免责声明

本程序仅供娱乐和学习使用,不得用于任何非法用途

参考项目

11-15 07:19