AI斗地主?我lbw沒有開掛!
一、核心功能設(shè)計(jì)
其次關(guān)于出牌器,先要需,代表我們玩家自己。我們只要給這個(gè)AI輸入玩家手牌和三張底牌。確認(rèn)好地主和農(nóng)民的各個(gè)角色,告訴它三個(gè)人對應(yīng)的關(guān)系,這樣就可以確定隊(duì)友和對手。我們還要將每一輪其他兩人的出牌輸入,這樣出牌器就可以根據(jù)出牌數(shù)據(jù),及時(shí)們?nèi)〉脛倮?/span>
顯示三張底牌
顯示AI角色出牌數(shù)據(jù)區(qū)域,上家出牌數(shù)據(jù)區(qū)域,下家出牌數(shù)據(jù)區(qū)域,本局勝率區(qū)域
AI玩家手牌區(qū)域
AI出牌器開始停止
游戲剛開始根據(jù)屏幕位置,截圖識別AI玩家手牌及三張底牌
確認(rèn)三者之間的關(guān)系,識別地主和農(nóng)民角色,確認(rèn)隊(duì)友及對手關(guān)系
識別每輪三位玩家依次出了什么牌,刷新顯示對應(yīng)區(qū)域
加載訓(xùn)練好的AI模型,初始化游戲環(huán)境
每輪出牌判斷,根據(jù)上家出牌數(shù)據(jù)給出最優(yōu)出牌決策
自動(dòng)刷新玩家剩余手牌和本局勝率預(yù)測
二、實(shí)現(xiàn)步驟
1. UI設(shè)計(jì)排版布局
def setupUi(self, Form):
Form.setObjectName("Form")
Form.resize(440, 395)
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(9)
font.setBold(True)
font.setItalic(False)
font.setWeight(75)
Form.setFont(font)
self.WinRate = QtWidgets.QLabel(Form)
self.WinRate.setGeometry(QtCore.QRect(240, 180, 171, 61))
font = QtGui.QFont()
font.setPointSize(14)
self.WinRate.setFont(font)
self.WinRate.setAlignment(QtCore.Qt.AlignCenter)
self.WinRate.setObjectName("WinRate")
self.InitCard = QtWidgets.QPushButton(Form)
self.InitCard.setGeometry(QtCore.QRect(60, 330, 121, 41))
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(14)
font.setBold(True)
font.setWeight(75)
self.InitCard.setFont(font)
self.InitCard.setStyleSheet("")
self.InitCard.setObjectName("InitCard")
self.UserHandCards = QtWidgets.QLabel(Form)
self.UserHandCards.setGeometry(QtCore.QRect(10, 260, 421, 41))
font = QtGui.QFont()
font.setPointSize(14)
self.UserHandCards.setFont(font)
self.UserHandCards.setAlignment(QtCore.Qt.AlignCenter)
self.UserHandCards.setObjectName("UserHandCards")
self.LPlayer = QtWidgets.QFrame(Form)
self.LPlayer.setGeometry(QtCore.QRect(10, 80, 201, 61))
self.LPlayer.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.LPlayer.setFrameShadow(QtWidgets.QFrame.Raised)
self.LPlayer.setObjectName("LPlayer")
self.LPlayedCard = QtWidgets.QLabel(self.LPlayer)
self.LPlayedCard.setGeometry(QtCore.QRect(0, 0, 201, 61))
font = QtGui.QFont()
font.setPointSize(14)
self.LPlayedCard.setFont(font)
self.LPlayedCard.setAlignment(QtCore.Qt.AlignCenter)
self.LPlayedCard.setObjectName("LPlayedCard")
self.RPlayer = QtWidgets.QFrame(Form)
self.RPlayer.setGeometry(QtCore.QRect(230, 80, 201, 61))
font = QtGui.QFont()
font.setPointSize(16)
self.RPlayer.setFont(font)
self.RPlayer.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.RPlayer.setFrameShadow(QtWidgets.QFrame.Raised)
self.RPlayer.setObjectName("RPlayer")
self.RPlayedCard = QtWidgets.QLabel(self.RPlayer)
self.RPlayedCard.setGeometry(QtCore.QRect(0, 0, 201, 61))
font = QtGui.QFont()
font.setPointSize(14)
self.RPlayedCard.setFont(font)
self.RPlayedCard.setAlignment(QtCore.Qt.AlignCenter)
self.RPlayedCard.setObjectName("RPlayedCard")
self.Player = QtWidgets.QFrame(Form)
self.Player.setGeometry(QtCore.QRect(40, 180, 171, 61))
self.Player.setFrameShape(QtWidgets.QFrame.StyledPanel)
self.Player.setFrameShadow(QtWidgets.QFrame.Raised)
self.Player.setObjectName("Player")
self.PredictedCard = QtWidgets.QLabel(self.Player)
self.PredictedCard.setGeometry(QtCore.QRect(0, 0, 171, 61))
font = QtGui.QFont()
font.setPointSize(14)
self.PredictedCard.setFont(font)
self.PredictedCard.setAlignment(QtCore.Qt.AlignCenter)
self.PredictedCard.setObjectName("PredictedCard")
self.ThreeLandlordCards = QtWidgets.QLabel(Form)
self.ThreeLandlordCards.setGeometry(QtCore.QRect(140, 10, 161, 41))
font = QtGui.QFont()
font.setPointSize(16)
self.ThreeLandlordCards.setFont(font)
self.ThreeLandlordCards.setAlignment(QtCore.Qt.AlignCenter)
self.ThreeLandlordCards.setObjectName("ThreeLandlordCards")
self.Stop = QtWidgets.QPushButton(Form)
self.Stop.setGeometry(QtCore.QRect(260, 330, 111, 41))
font = QtGui.QFont()
font.setFamily("Arial")
font.setPointSize(14)
font.setBold(True)
font.setWeight(75)
self.Stop.setFont(font)
self.Stop.setStyleSheet("")
self.Stop.setObjectName("Stop")
self.retranslateUi(Form)
self.InitCard.clicked.connect(Form.init_cards)
self.Stop.clicked.connect(Form.stop)
QtCore.QMetaObject.connectSlotsByName(Form)
def retranslateUi(self, Form):
_translate = QtCore.QCoreApplication.translate
Form.setWindowTitle(_translate("Form", "AI歡樂斗地主--Dragon少年"))
self.WinRate.setText(_translate("Form", "勝率:--%"))
self.InitCard.setText(_translate("Form", "開始"))
self.UserHandCards.setText(_translate("Form", "手牌"))
self.LPlayedCard.setText(_translate("Form", "上家出牌區(qū)域"))
self.RPlayedCard.setText(_translate("Form", "下家出牌區(qū)域"))
self.PredictedCard.setText(_translate("Form", "AI出牌區(qū)域"))
self.ThreeLandlordCards.setText(_translate("Form", "三張底牌"))
self.Stop.setText(_translate("Form", "停止"))
2. 手牌和出牌數(shù)據(jù)識別
# 牌檢測結(jié)果濾波
def cards_filter(self, location, distance):
if len(location) == 0:
return 0
locList = [location[0][0]]
count = 1
for e in location:
flag = 1 # “是新的”標(biāo)志
for have in locList:
if abs(e[0] - have) <= distance:
flag = 0
break
if flag:
count += 1
locList.append(e[0])
return count
# 獲取玩家AI手牌
def find_my_cards(self, pos):
user_hand_cards_real = ""
img = pyautogui.screenshot(region=pos)
for card in AllCards:
result = pyautogui.locateAll(needleImage='pics/m' + card + '.png', haystackImage=img, confidence=self.MyConfidence)
user_hand_cards_real += card[1] * self.cards_filter(list(result), self.MyFilter)
return user_hand_cards_real
# 獲取地主三張底牌
def find_three_landlord_cards(self, pos):
three_landlord_cards_real = ""
img = pyautogui.screenshot(region=pos)
img = img.resize((349, 168))
for card in AllCards:
result = pyautogui.locateAll(needleImage='pics/o' + card + '.png', haystackImage=img,
confidence=self.ThreeLandlordCardsConfidence)
three_landlord_cards_real += card[1] * self.cards_filter(list(result), self.OtherFilter)
return three_landlord_cards_real
# 查找地主角色
def find_landlord(self, landlord_flag_pos):
for pos in landlord_flag_pos:
result = pyautogui.locateOnScreen('pics/landlord_words.png', region=pos, confidence=self.LandlordFlagConfidence)
if result is not None:
return landlord_flag_pos.index(pos)
return None

這樣我們就可以得到玩家AI手牌,其他玩家手牌(預(yù)測),地主三張底牌,三者角色關(guān)系,出牌順序。核心代碼如下:
# 坐標(biāo)
self.MyHandCardsPos = (414, 804, 1041, 59) # AI玩家截圖區(qū)域
self.LPlayedCardsPos = (530, 470, 380, 160) # 左側(cè)玩家截圖區(qū)域
self.RPlayedCardsPos = (1010, 470, 380, 160) # 右側(cè)玩家截圖區(qū)域
self.LandlordFlagPos = [(1320, 300, 110, 140), (320, 720, 110, 140), (500, 300, 110, 140)] # 地主標(biāo)志截圖區(qū)域(右-我-左)
self.ThreeLandlordCardsPos = (817, 36, 287, 136) # 地主底牌截圖區(qū)域,resize成349x168
def init_cards(self):
# 玩家手牌
self.user_hand_cards_real = ""
self.user_hand_cards_env = []
# 其他玩家出牌
self.other_played_cards_real = ""
self.other_played_cards_env = []
# 其他玩家手牌(整副牌減去玩家手牌,后續(xù)再減掉歷史出牌)
self.other_hand_cards = []
# 三張底牌
self.three_landlord_cards_real = ""
self.three_landlord_cards_env = []
# 玩家角色代碼:0-地主上家, 1-地主, 2-地主下家
self.user_position_code = None
self.user_position = ""
# 開局時(shí)三個(gè)玩家的手牌
self.card_play_data_list = {}
# 出牌順序:0-玩家出牌, 1-玩家下家出牌, 2-玩家上家出牌
self.play_order = 0
self.env = None
# 識別玩家手牌
self.user_hand_cards_real = self.find_my_cards(self.MyHandCardsPos)
self.UserHandCards.setText(self.user_hand_cards_real)
self.user_hand_cards_env = [RealCard2EnvCard[c] for c in list(self.user_hand_cards_real)]
# 識別三張底牌
self.three_landlord_cards_real = self.find_three_landlord_cards(self.ThreeLandlordCardsPos)
self.ThreeLandlordCards.setText("底牌:" + self.three_landlord_cards_real)
self.three_landlord_cards_env = [RealCard2EnvCard[c] for c in list(self.three_landlord_cards_real)]
# 識別玩家的角色
self.user_position_code = self.find_landlord(self.LandlordFlagPos)
if self.user_position_code is None:
items = ("地主上家", "地主", "地主下家")
item, okPressed = QInputDialog.getItem(self, "選擇角色", "未識別到地主,請手動(dòng)選擇角色:", items, 0, False)
if okPressed and item:
self.user_position_code = items.index(item)
else:
return
self.user_position = ['landlord_up', 'landlord', 'landlord_down'][self.user_position_code]
for player in self.Players:
player.setStyleSheet('background-color: rgba(255, 0, 0, 0);')
self.Players[self.user_position_code].setStyleSheet('background-color: rgba(255, 0, 0, 0.1);')
# 整副牌減去玩家手上的牌,就是其他人的手牌,再分配給另外兩個(gè)角色(如何分配對AI判斷沒有影響)
for i in set(AllEnvCard):
self.other_hand_cards.extend([i] * (AllEnvCard.count(i) - self.user_hand_cards_env.count(i)))
self.card_play_data_list.update({
'three_landlord_cards': self.three_landlord_cards_env,
['landlord_up', 'landlord', 'landlord_down'][(self.user_position_code + 0) % 3]:
self.user_hand_cards_env,
['landlord_up', 'landlord', 'landlord_down'][(self.user_position_code + 1) % 3]:
self.other_hand_cards[0:17] if (self.user_position_code + 1) % 3 != 1 else self.other_hand_cards[17:],
['landlord_up', 'landlord', 'landlord_down'][(self.user_position_code + 2) % 3]:
self.other_hand_cards[0:17] if (self.user_position_code + 1) % 3 == 1 else self.other_hand_cards[17:]
})
print(self.card_play_data_list)
# 生成手牌結(jié)束,校驗(yàn)手牌數(shù)量
if len(self.card_play_data_list["three_landlord_cards"]) != 3:
QMessageBox.critical(self, "底牌識別出錯(cuò)", "底牌必須是3張!", QMessageBox.Yes, QMessageBox.Yes)
self.init_display()
return
if len(self.card_play_data_list["landlord_up"]) != 17 or \
len(self.card_play_data_list["landlord_down"]) != 17 or \
len(self.card_play_data_list["landlord"]) != 20:
QMessageBox.critical(self, "手牌識別出錯(cuò)", "初始手牌數(shù)目有誤", QMessageBox.Yes, QMessageBox.Yes)
self.init_display()
return
# 得到出牌順序
self.play_order = 0 if self.user_position == "landlord" else 1 if self.user_position == "landlord_up" else 2
3. AI出牌方案輸出
下面我們就需要用到DouZero開源的AI斗地主了。DouZero項(xiàng)目地址:https://github.com/kwai/DouZero。
我們需要將該開源項(xiàng)目下載并導(dǎo)入項(xiàng)目中。
創(chuàng)建一個(gè)AI玩家角色,初始化游戲環(huán)境,加載模型,進(jìn)行每輪的出牌判斷,控制一局游戲流程的進(jìn)行和結(jié)束。核心代碼如下:
# 創(chuàng)建一個(gè)代表玩家的AI
ai_players = [0, 0]
ai_players[0] = self.user_position
ai_players[1] = DeepAgent(self.user_position, self.card_play_model_path_dict[self.user_position])
# 初始化游戲環(huán)境
self.env = GameEnv(ai_players)
# 游戲開始
self.start()
def start(self):
self.env.card_play_init(self.card_play_data_list)
print("開始出牌\n")
while not self.env.game_over:
# 玩家出牌時(shí)就通過智能體獲取action,否則通過識別獲取其他玩家出牌
if self.play_order == 0:
self.PredictedCard.setText("...")
action_message = self.env.step(self.user_position)
# 更新界面
self.UserHandCards.setText("手牌:" + str(''.join(
[EnvCard2RealCard[c] for c in self.env.info_sets[self.user_position].player_hand_cards]))[::-1])
self.PredictedCard.setText(action_message["action"] if action_message["action"] else "不出")
self.WinRate.setText("勝率:" + action_message["win_rate"])
print("\n手牌:", str(''.join(
[EnvCard2RealCard[c] for c in self.env.info_sets[self.user_position].player_hand_cards])))
print("出牌:", action_message["action"] if action_message["action"] else "不出", ", 勝率:",
action_message["win_rate"])
while self.have_white(self.RPlayedCardsPos) == 1 or \
pyautogui.locateOnScreen('pics/pass.png',
region=self.RPlayedCardsPos,
confidence=self.LandlordFlagConfidence):
print("等待玩家出牌")
self.counter.restart()
while self.counter.elapsed() < 100:
QtWidgets.QApplication.processEvents(QEventLoop.AllEvents, 50)
self.play_order = 1
elif self.play_order == 1:
self.RPlayedCard.setText("...")
pass_flag = None
while self.have_white(self.RPlayedCardsPos) == 0 and \
not pyautogui.locateOnScreen('pics/pass.png',
region=self.RPlayedCardsPos,
confidence=self.LandlordFlagConfidence):
print("等待下家出牌")
self.counter.restart()
while self.counter.elapsed() < 500:
QtWidgets.QApplication.processEvents(QEventLoop.AllEvents, 50)
self.counter.restart()
while self.counter.elapsed() < 500:
QtWidgets.QApplication.processEvents(QEventLoop.AllEvents, 50)
# 不出
pass_flag = pyautogui.locateOnScreen('pics/pass.png',
region=self.RPlayedCardsPos,
confidence=self.LandlordFlagConfidence)
# 未找到"不出"
if pass_flag is None:
# 識別下家出牌
self.other_played_cards_real = self.find_other_cards(self.RPlayedCardsPos)
# 找到"不出"
else:
self.other_played_cards_real = ""
print("\n下家出牌:", self.other_played_cards_real)
self.other_played_cards_env = [RealCard2EnvCard[c] for c in list(self.other_played_cards_real)]
self.env.step(self.user_position, self.other_played_cards_env)
# 更新界面
self.RPlayedCard.setText(self.other_played_cards_real if self.other_played_cards_real else "不出")
self.play_order = 2
elif self.play_order == 2:
self.LPlayedCard.setText("...")
while self.have_white(self.LPlayedCardsPos) == 0 and \
not pyautogui.locateOnScreen('pics/pass.png',
region=self.LPlayedCardsPos,
confidence=self.LandlordFlagConfidence):
print("等待上家出牌")
self.counter.restart()
while self.counter.elapsed() < 500:
QtWidgets.QApplication.processEvents(QEventLoop.AllEvents, 50)
self.counter.restart()
while self.counter.elapsed() < 500:
QtWidgets.QApplication.processEvents(QEventLoop.AllEvents, 50)
# 不出
pass_flag = pyautogui.locateOnScreen('pics/pass.png',
region=self.LPlayedCardsPos,
confidence=self.LandlordFlagConfidence)
# 未找到"不出"
if pass_flag is None:
# 識別上家出牌
self.other_played_cards_real = self.find_other_cards(self.LPlayedCardsPos)
# 找到"不出"
else:
self.other_played_cards_real = ""
print("\n上家出牌:", self.other_played_cards_real)
self.other_played_cards_env = [RealCard2EnvCard[c] for c in list(self.other_played_cards_real)]
self.env.step(self.user_position, self.other_played_cards_env)
self.play_order = 0
# 更新界面
self.LPlayedCard.setText(self.other_played_cards_real if self.other_played_cards_real else "不出")
else:
pass
self.counter.restart()
while self.counter.elapsed() < 100:
QtWidgets.QApplication.processEvents(QEventLoop.AllEvents, 50)
print("{}勝,本局結(jié)束!\n".format("農(nóng)民" if self.env.winner == "farmer" else "地主"))
QMessageBox.information(self, "本局結(jié)束", "{}勝!".format("農(nóng)民" if self.env.winner == "farmer" else "地主"),
QMessageBox.Yes, QMessageBox.Yes)
self.env.reset()
self.init_display()
三、出牌器用法
1. 環(huán)境配置
torch==1.9.0
GitPython==3.0.5
gitdb2==2.0.6
PyAutoGUI==0.9.50
PyQt5==5.13.0
PyQt5-sip==12.8.1
Pillow>=5.2.0
opencv-python
rlcard2. 坐標(biāo)調(diào)整確認(rèn)
其次我們要確認(rèn)屏幕截圖獲取的各個(gè)區(qū)域是否正確。如果有問題需要進(jìn)行區(qū)域位置坐標(biāo)調(diào)整。
# 坐標(biāo)
self.MyHandCardsPos = (414, 804, 1041, 59) # 我的截圖區(qū)域
self.LPlayedCardsPos = (530, 470, 380, 160) # 左邊截圖區(qū)域
self.RPlayedCardsPos = (1010, 470, 380, 160) # 右邊截圖區(qū)域
self.LandlordFlagPos = [(1320, 300, 110, 140), (320, 720, 110, 140), (500, 300, 110, 140)] # 地主標(biāo)志截圖區(qū)域(右-我-左)
self.ThreeLandlordCardsPos = (817, 36, 287, 136) # 地主底牌截圖區(qū)域,resize成349x168

3. 運(yùn)行測試
首先我們運(yùn)行AI出牌器程序,打開歡樂斗地主游戲界面,進(jìn)入游戲。當(dāng)玩家就位,手牌分發(fā)完畢,地主身份確認(rèn)之后,我們就可以點(diǎn)擊畫面中開始按鈕,讓AI來幫助我們斗地主了。
下面可以一起來看看這款A(yù)I出牌器的實(shí)驗(yàn)效果喔,看看AI是如何帶領(lǐng)農(nóng)民打倒地主,取得勝利的!
版權(quán)聲明:本文為博主原創(chuàng)文章,遵循 CC 4.0 BY-SA 版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接和本聲明。本文鏈接:
https://blog.csdn.net/hhladminhhl/article/details/119304504






