前言 最近在网上看到一个有意思的开源项目,快手团队开发的开源AI斗地主DouZero。今天我们就一起来学习制作一个基于DouZero的欢乐斗地主出牌器,看看AI是如何来帮助我们斗地主,赢欢乐豆,实现财富自由的吧! 首先一起来看看AI斗地主出牌器的效果: 私信小编01即可获取大量python学习资源 下面,我们开始介绍这个AI出牌器的制作过程。 一、核心功能设计 首先我们这款出牌器是基于DouZero开发的,核心是需要利用训练好的AI模型来帮助我们,给出最优出牌方案。 其次关于出牌器,先要需要确认一个AI出牌角色,代表我们玩家自己。我们只要给这个AI输入玩家手牌和三张底牌。确认好地主和农民的各个角色,告诉它三个人对应的关系,这样就可以确定队友和对手。我们还要将每一轮其他两人的出牌输入,这样出牌器就可以根据出牌数据,及时提供给我们最优出牌决策,带领我们取得胜利! 那么如何获取三者之间的关系呢?谁是地主?谁是农民?是自己一人作战还是农民合作?自己玩家的手牌是什么?三张底牌是什么?这些也都需要在开局后确认好。 拆解需求,大致可以整理出核心功能如下: UI设计排版布局显示三张底牌显示AI角色出牌数据区域,上家出牌数据区域,下家出牌数据区域,本局胜率区域AI玩家手牌区域AI出牌器开始停止 手牌和出牌数据识别游戏刚开始根据屏幕位置,截图识别AI玩家手牌及三张底牌确认三者之间的关系,识别地主和农民角色,确认队友及对手关系识别每轮三位玩家依次出了什么牌,刷新显示对应区域 AI出牌方案输出加载训练好的AI模型,初始化游戏环境每轮出牌判断,根据上家出牌数据给出最优出牌决策自动刷新玩家剩余手牌和本局胜率预测 二、实现步骤1。UI设计排版布局 根据上述功能,我们首先考虑进行简单的UI布局设计,这里我们使用的是pyqt5。核心设计代码如下:defsetupUi(self,Form):Form。setObjectName(Form)Form。resize(440,395)fontQtGui。QFont()font。setFamily(Arial)font。setPointSize(9)font。setBold(True)font。setItalic(False)font。setWeight(75)Form。setFont(font)self。WinRateQtWidgets。QLabel(Form)self。WinRate。setGeometry(QtCore。QRect(240,180,171,61))fontQtGui。QFont()font。setPointSize(14)self。WinRate。setFont(font)self。WinRate。setAlignment(QtCore。Qt。AlignCenter)self。WinRate。setObjectName(WinRate)self。InitCardQtWidgets。QPushButton(Form)self。InitCard。setGeometry(QtCore。QRect(60,330,121,41))fontQtGui。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。UserHandCardsQtWidgets。QLabel(Form)self。UserHandCards。setGeometry(QtCore。QRect(10,260,421,41))fontQtGui。QFont()font。setPointSize(14)self。UserHandCards。setFont(font)self。UserHandCards。setAlignment(QtCore。Qt。AlignCenter)self。UserHandCards。setObjectName(UserHandCards)self。LPlayerQtWidgets。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。LPlayedCardQtWidgets。QLabel(self。LPlayer)self。LPlayedCard。setGeometry(QtCore。QRect(0,0,201,61))fontQtGui。QFont()font。setPointSize(14)self。LPlayedCard。setFont(font)self。LPlayedCard。setAlignment(QtCore。Qt。AlignCenter)self。LPlayedCard。setObjectName(LPlayedCard)self。RPlayerQtWidgets。QFrame(Form)self。RPlayer。setGeometry(QtCore。QRect(230,80,201,61))fontQtGui。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。RPlayedCardQtWidgets。QLabel(self。RPlayer)self。RPlayedCard。setGeometry(QtCore。QRect(0,0,201,61))fontQtGui。QFont()font。setPointSize(14)self。RPlayedCard。setFont(font)self。RPlayedCard。setAlignment(QtCore。Qt。AlignCenter)self。RPlayedCard。setObjectName(RPlayedCard)self。PlayerQtWidgets。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。PredictedCardQtWidgets。QLabel(self。Player)self。PredictedCard。setGeometry(QtCore。QRect(0,0,171,61))fontQtGui。QFont()font。setPointSize(14)self。PredictedCard。setFont(font)self。PredictedCard。setAlignment(QtCore。Qt。AlignCenter)self。PredictedCard。setObjectName(PredictedCard)self。ThreeLandlordCardsQtWidgets。QLabel(Form)self。ThreeLandlordCards。setGeometry(QtCore。QRect(140,10,161,41))fontQtGui。QFont()font。setPointSize(16)self。ThreeLandlordCards。setFont(font)self。ThreeLandlordCards。setAlignment(QtCore。Qt。AlignCenter)self。ThreeLandlordCards。setObjectName(ThreeLandlordCards)self。StopQtWidgets。QPushButton(Form)self。Stop。setGeometry(QtCore。QRect(260,330,111,41))fontQtGui。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。initcards)self。Stop。clicked。connect(Form。stop)QtCore。QMetaObject。connectSlotsByName(Form)defretranslateUi(self,Form):translateQtCore。QCoreApplication。translateForm。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,上家出牌区域))self。RPlayedCard。setText(translate(Form,下家出牌区域))self。PredictedCard。setText(translate(Form,AI出牌区域))self。ThreeLandlordCards。setText(translate(Form,三张底牌))self。Stop。setText(translate(Form,停止)) 实现效果如下: 2。手牌和出牌数据识别 下面我们需要所有扑克牌的模板图片与游戏屏幕特定区域的截图进行对比,这样才能获取AI玩家手牌、底牌、每一轮出牌、三者关系(地主、地主上家、地主下家)。 识别AI玩家手牌及三张底牌: 我们可以截取游戏屏幕,根据固定位置来识别当前AI玩家的手牌和三张底牌。核心代码如下:牌检测结果滤波defcardsfilter(self,location,distance):iflen(location)0:return0locList〔location〔0〕〔0〕〕count1foreinlocation:flag1是新的标志forhaveinlocList:ifabs(e〔0〕have)distance:flag0breakifflag:count1locList。append(e〔0〕)returncount获取玩家AI手牌deffindmycards(self,pos):userhandcardsrealimgpyautogui。screenshot(regionpos)forcardinAllCards:resultpyautogui。locateAll(needleImagepicsmcard。png,haystackImageimg,confidenceself。MyConfidence)userhandcardsrealcard〔1〕self。cardsfilter(list(result),self。MyFilter)returnuserhandcardsreal获取地主三张底牌deffindthreelandlordcards(self,pos):threelandlordcardsrealimgpyautogui。screenshot(regionpos)imgimg。resize((349,168))forcardinAllCards:resultpyautogui。locateAll(needleImagepicsocard。png,haystackImageimg,confidenceself。ThreeLandlordCardsConfidence)threelandlordcardsrealcard〔1〕self。cardsfilter(list(result),self。OtherFilter)returnthreelandlordcardsreal 效果如下所示: 地主、地主上家、地主下家: 同理我们可以根据游戏屏幕截图,识别地主的图标,确认地主角色。核心代码如下:查找地主角色deffindlandlord(self,landlordflagpos):forposinlandlordflagpos:resultpyautogui。locateOnScreen(picslandlordwords。png,regionpos,confidenceself。LandlordFlagConfidence)ifresultisnotNone:returnlandlordflagpos。index(pos)returnNone 这样我们就可以得到玩家AI手牌,其他玩家手牌(预测),地主三张底牌,三者角色关系,出牌顺序。核心代码如下:坐标self。MyHandCardsPos(414,804,1041,59)AI玩家截图区域self。LPlayedCardsPos(530,470,380,160)左侧玩家截图区域self。RPlayedCardsPos(1010,470,380,160)右侧玩家截图区域self。LandlordFlagPos〔(1320,300,110,140),(320,720,110,140),(500,300,110,140)〕地主标志截图区域(右我左)self。ThreeLandlordCardsPos(817,36,287,136)地主底牌截图区域,resize成349x168definitcards(self):玩家手牌self。userhandcardsrealself。userhandcardsenv〔〕其他玩家出牌self。otherplayedcardsrealself。otherplayedcardsenv〔〕其他玩家手牌(整副牌减去玩家手牌,后续再减掉ahrefhttps:www。q578。coml140targetblankclassinfotextkey历史a出牌)self。otherhandcards〔〕三张底牌self。threelandlordcardsrealself。threelandlordcardsenv〔〕玩家角色代码:0地主上家,1地主,2地主下家self。userpositioncodeNoneself。userposition开局时三个玩家的手牌self。cardplaydatalist{}出牌顺序:0玩家出牌,1玩家下家出牌,2玩家上家出牌self。playorder0self。envNone识别玩家手牌self。userhandcardsrealself。findmycards(self。MyHandCardsPos)self。UserHandCards。setText(self。userhandcardsreal)self。userhandcardsenv〔RealCard2EnvCard〔c〕forcinlist(self。userhandcardsreal)〕识别三张底牌self。threelandlordcardsrealself。findthreelandlordcards(self。ThreeLandlordCardsPos)self。ThreeLandlordCards。setText(底牌:self。threelandlordcardsreal)self。threelandlordcardsenv〔RealCard2EnvCard〔c〕forcinlist(self。threelandlordcardsreal)〕识别玩家的角色self。userpositioncodeself。findlandlord(self。LandlordFlagPos)ifself。userpositioncodeisNone:items(地主上家,地主,地主下家)item,okPressedQInputDialog。getItem(self,选择角色,未识别到地主,请手动选择角色:,items,0,False)ifokPressedanditem:self。userpositioncodeitems。index(item)else:returnself。userposition〔landlordup,landlord,landlorddown〕〔self。userpositioncode〕forplayerinself。Players:player。setStyleSheet(backgroundcolor:rgba(255,0,0,0);)self。Players〔self。userpositioncode〕。setStyleSheet(backgroundcolor:rgba(255,0,0,0。1);)整副牌减去玩家手上的牌,就是其他人的手牌,再分配给另外两个角色(如何分配对AI判断没有影响)foriinset(AllEnvCard):self。otherhandcards。extend(〔i〕(AllEnvCard。count(i)self。userhandcardsenv。count(i)))self。cardplaydatalist。update({threelandlordcards:self。threelandlordcardsenv,〔landlordup,landlord,landlorddown〕〔(self。userpositioncode0)3〕:self。userhandcardsenv,〔landlordup,landlord,landlorddown〕〔(self。userpositioncode1)3〕:self。otherhandcards〔0:17〕if(self。userpositioncode1)3!1elseself。otherhandcards〔17:〕,〔landlordup,landlord,landlorddown〕〔(self。userpositioncode2)3〕:self。otherhandcards〔0:17〕if(self。userpositioncode1)31elseself。otherhandcards〔17:〕})print(self。cardplaydatalist)生成手牌结束,校验手牌数量iflen(self。cardplaydatalist〔threelandlordcards〕)!3:QMessageBox。critical(self,底牌识别出错,底牌必须是3张!,QMessageBox。Yes,QMessageBox。Yes)self。initdisplay()returniflen(self。cardplaydatalist〔landlordup〕)!17orlen(self。cardplaydatalist〔landlorddown〕)!17orlen(self。cardplaydatalist〔landlord〕)!20:QMessageBox。critical(self,手牌识别出错,初始手牌数目有误,QMessageBox。Yes,QMessageBox。Yes)self。initdisplay()return得到出牌顺序self。playorder0ifself。userpositionlandlordelse1ifself。userpositionlandlordupelse2 效果如下: 3。AI出牌方案输出 下面我们就需要用到DouZero开源的AI斗地主了。DouZero项目地址:https:github。comkwaiDouZero。我们需要将该开源项目下载并导入项目中。 创建一个AI玩家角色,初始化游戏环境,加载模型,进行每轮的出牌判断,控制一局游戏流程的进行和结束。核心代码如下:创建一个代表玩家的AIaiplayers〔0,0〕aiplayers〔0〕self。userpositionaiplayers〔1〕DeepAgent(self。userposition,self。cardplaymodelpathdict〔self。userposition〕)初始化ahrefhttps:www。q578。coml60targetblankclassinfotextkey游戏a环境self。envGameEnv(aiplayers)ahrefhttps:www。q578。coml60targetblankclassinfotextkey游戏a开始self。start()defstart(self):self。env。cardplayinit(self。cardplaydatalist)print(开始出牌)whilenotself。env。gameover:玩家出牌时就通过智能体获取action,否则通过识别获取其他玩家出牌ifself。playorder0:self。PredictedCard。setText(。。。)actionmessageself。env。step(self。userposition)更新界面self。UserHandCards。setText(手牌:str(。join(〔EnvCard2RealCard〔c〕forcinself。env。infosets〔self。userposition〕。playerhandcards〕))〔::1〕)self。PredictedCard。setText(actionmessage〔action〕ifactionmessage〔action〕else不出)self。WinRate。setText(胜率:actionmessage〔winrate〕)print(手牌:,str(。join(〔EnvCard2RealCard〔c〕forcinself。env。infosets〔self。userposition〕。playerhandcards〕)))print(出牌:,actionmessage〔action〕ifactionmessage〔action〕else不出,,胜率:,actionmessage〔winrate〕)whileself。havewhite(self。RPlayedCardsPos)1orpyautogui。locateOnScreen(picspass。png,regionself。RPlayedCardsPos,confidenceself。LandlordFlagConfidence):print(等待玩家出牌)self。counter。restart()whileself。counter。elapsed()100:QtWidgets。QApplication。processEvents(QEventLoop。AllEvents,50)self。playorder1elifself。playorder1:self。RPlayedCard。setText(。。。)passflagNonewhileself。havewhite(self。RPlayedCardsPos)0andnotpyautogui。locateOnScreen(picspass。png,regionself。RPlayedCardsPos,confidenceself。LandlordFlagConfidence):print(等待下家出牌)self。counter。restart()whileself。counter。elapsed()500:QtWidgets。QApplication。processEvents(QEventLoop。AllEvents,50)self。counter。restart()whileself。counter。elapsed()500:QtWidgets。QApplication。processEvents(QEventLoop。AllEvents,50)不出passflagpyautogui。locateOnScreen(picspass。png,regionself。RPlayedCardsPos,confidenceself。LandlordFlagConfidence)未找到不出ifpassflagisNone:识别下家出牌self。otherplayedcardsrealself。findothercards(self。RPlayedCardsPos)找到不出else:self。otherplayedcardsrealprint(下家出牌:,self。otherplayedcardsreal)self。otherplayedcardsenv〔RealCard2EnvCard〔c〕forcinlist(self。otherplayedcardsreal)〕self。env。step(self。userposition,self。otherplayedcardsenv)更新界面self。RPlayedCard。setText(self。otherplayedcardsrealifself。otherplayedcardsrealelse不出)self。playorder2elifself。playorder2:self。LPlayedCard。setText(。。。)whileself。havewhite(self。LPlayedCardsPos)0andnotpyautogui。locateOnScreen(picspass。png,regionself。LPlayedCardsPos,confidenceself。LandlordFlagConfidence):print(等待上家出牌)self。counter。restart()whileself。counter。elapsed()500:QtWidgets。QApplication。processEvents(QEventLoop。AllEvents,50)self。counter。restart()whileself。counter。elapsed()500:QtWidgets。QApplication。processEvents(QEventLoop。AllEvents,50)不出passflagpyautogui。locateOnScreen(picspass。png,regionself。LPlayedCardsPos,confidenceself。LandlordFlagConfidence)未找到不出ifpassflagisNone:识别上家出牌self。otherplayedcardsrealself。findothercards(self。LPlayedCardsPos)找到不出else:self。otherplayedcardsrealprint(上家出牌:,self。otherplayedcardsreal)self。otherplayedcardsenv〔RealCard2EnvCard〔c〕forcinlist(self。otherplayedcardsreal)〕self。env。step(self。userposition,self。otherplayedcardsenv)self。playorder0更新界面self。LPlayedCard。setText(self。otherplayedcardsrealifself。otherplayedcardsrealelse不出)else:passself。counter。restart()whileself。counter。elapsed()100:QtWidgets。QApplication。processEvents(QEventLoop。AllEvents,50)print({}胜,本局结束!。format(农民ifself。env。winnerfarmerelse地主))QMessageBox。information(self,本局结束,{}胜!。format(农民ifself。env。winnerfarmerelse地主),QMessageBox。Yes,QMessageBox。Yes)self。env。reset()self。initdisplay() 到这里,整个AI斗地主出牌流程基本已经完成了。 三、出牌器用法 按照上述过程,这款AI出牌器已经制作完成了。后面应该如何使用呢?如果不想研究源码,只想使用这款AI斗地主出牌器,验证下效果,该怎么配置环境运行这个AI出牌器呢?下面就开始介绍。1。环境配置 首先我们需要安装这些第三方库,配置相关环境,如下所示:torch1。9。0GitPython3。0。5gitdb22。0。6PyAutoGUI0。9。50PyQt55。13。0PyQt5sip12。8。1Pillow5。2。0opencvpythonrlcard2。坐标调整确认 我们可以打开欢乐斗地主游戏界面,将游戏窗口模式下最大化运行,把AI出牌器程序窗口需要移至右下角,不能遮挡手牌、地主标志、底牌、历史出牌这些关键位置。 其次我们要确认屏幕截图获取的各个区域是否正确。如果有问题需要进行区域位置坐标调整。坐标self。MyHandCardsPos(414,804,1041,59)我的截图区域self。LPlayedCardsPos(530,470,380,160)左边截图区域self。RPlayedCardsPos(1010,470,380,160)右边截图区域self。LandlordFlagPos〔(1320,300,110,140),(320,720,110,140),(500,300,110,140)〕地主标志截图区域(右我左)self。ThreeLandlordCardsPos(817,36,287,136)地主底牌截图区域,resize成349x168 3。运行测试 当所有环境配置完成,各区域坐标位置确认无误之后,下面我们就可以直接运行程序,测试效果啦 首先我们运行AI出牌器程序,打开欢乐斗地主游戏界面,进入游戏。当玩家就位,手牌分发完毕,地主身份确认之后,我们就可以点击画面中开始按钮,让AI来帮助我们斗地主了。 下面可以一起来看看这款AI出牌器的实验效果喔,看看AI是如何带领农民打倒地主,取得胜利的! 若本篇内容对您有所帮助,请三连点赞,再看,转发支持一下。