斗地主老是输?一起用Python做个自动出牌器,欢乐豆蹭蹭涨
版权声明:本文为博主原创文章,遵循CC4。0BYSA版权协议,转载请附上原文出处链接和本声明。本文链接:
https:blog。csdn。nethhladminhhlarticledetails119304504
前言
最近在网上看到一个有意思的开源项目,快手团队开发的开源AI斗地主DouZero。今天我们就一起来学习制作一个基于DouZero的欢乐斗地主出牌器,看看AI是如何来帮助我们斗地主,赢欢乐豆,实现财富自由的吧!
首先一起来看看AI斗地主出牌器的效果:
下面,我们开始介绍这个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是如何带领农民打倒地主,取得胜利的!
若本篇内容对您有所帮助,请三连点赞,在看,转发支持下。
Caffeine缓存最快缓存内存缓存一、序言Caffeine是一个进程内部缓存框架。对比GuavaCacheCaffeine是在GuavaCache的基础上做一层封装,性能有明显提高,二者同属于内存级……
梁安琪怎么哄赌王的绝非只是姿色就能取胜的澳门赌王一家一直都像个传奇一样的存在,不仅赌王本人的一生很有传奇色彩,就连他的子女们也都是如今媒体和大众们关注的焦点。最出名的就是四太梁安琪生的孩子。不仅颜值高而且个个都十分的……
哆啦a梦精神病结局是真的吗事实上并没有结尾哆啦a梦是80后儿时最美好的记忆。那个机器猫的形象至今一直出现在我们的生活当中。因为多啦A梦有一个神奇的口袋,想要什么都可以变出来。这让作为孩子的人觉得这个事业充满了无限的想象……
哪些童年女神下海了逼良为娼的只有太穷一个原因在我们的童年,影视行业没有如今的怎么的发达,影视作品很多也并不是那么的丰富的,但是有一点,我们的印象中的确是出现了不少的女神,她们的妆容看起来并没有如今的那么高级,但是却让人有……
想换一部手机2000元左右,大家能否推荐下?感谢邀请想买一部2000元左右的手机,希望大家推荐一下?实际在2000元左右的手机,我觉得直接去选择iQOO以及红米这样的性价比品牌就可以了。性价比高,质量和口碑等方面也……
Python机器学习(四十四)NumPy字符串函数NumPy中,可以使用下面的函数对dtype字符串数组进行操作。SN函数描述1hradd()连接字符串(数组)。2hrmultiply()……
梁静茹婚变原因被骂那么惨是不想要公开说梁静茹已经是离婚了,这个事情其实很多人都无法接受,还说梁静茹不应该被这样对待,梁静茹这样温柔的人丈夫都不允许,那么还有什么人是适合结婚呢?是梁静茹的问题,还是梁静茹的丈夫问题呢……
林志玲走秀裸奶大半半乳微露剩下靠想象林志玲走秀裸奶,在走秀的时候是绝对不可能露奶的,除非是那种人体艺术或者是以身体博眼球的,中国有明文规定女性是不能够露点的。女明星也是如此,一样不能够露点,尤其是在公开场合,是会……
纪美伊小山竹微博资料遇到这么漂亮的女儿邓伦父爱爆棚随着《爸爸去哪儿》第五季的播出。一个可爱的小女孩出现在大家的视线。她就是纪美伊,因为从小爱吃山竹小名被叫做ldquo;小山竹rdquo;,在节目中和邓伦组成的父女组合格外的受到……
纪英男有人娶吗自己爆料自己被包养的目的是什么纪英男是何方神圣?为什么那么多人都在关心她有没有人娶啊,说到这里就必须要说说纪英男的所作所为了,别人不管是包养还是被包养,总之这种不正当的关系多数都是偷偷摸摸私下进行的,别提光……
索尼PS5国行双手柄版11点开抢售价4428元索尼PS5国行4月底正式发布,5月正式发售。京东双手柄光驱版售价4428元,今日11点开抢。本活动可叠加京东红包,多个红包也可叠加使用。上次不少小伙伴都抢到了……
具荷拉龙俊亨分手分手后遇到了渣男具荷拉龙俊亨分手,两个人交往大概两年时间就分手了,现在具荷拉更是不在了。2019年11月24日下午6时左右,具荷拉被发现位于首尔市江南区清潭洞的住宅中身亡,年仅28岁。具荷拉龙……