程式基於Python3.7開發的鬥地主AI出牌助手,目前支援歡樂鬥地主桌面版,微信版,也可以自己製作相應其他版本。
此出牌助手核心是識別出三位玩家出牌內容,呼叫基於DouZero封裝的API介面,輸入出牌內容,根據AI出牌方案,打出相應的牌。
![](https://img2023.cnblogs.com/blog/129408/202311/129408-20231114235431212-40412513.gif)
區域定位,獲取座標值
按鈕及牌面
根據指定視窗控制程式碼截圖
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":''#三張底牌
}
本程式僅供娛樂和學習使用,不得用於任何非法用途
作者:LionGIS
郵箱:[email protected]
QQ:1366940902
出處:http://liongis.cnblogs.com/
歡迎轉載,請在文章頁面明顯位置給出原文連結。