二打一(斗地主)计算机博弈
二打一扑克牌项目规则
官方网站
全国计算机博弈大赛官网链接:
二打一扑克牌项目规则官网链接:
前言
二打一,民间俗称斗地主,因玩法简单、娱乐性强已成为国内最受喜爱的牌类游戏之一。2013年正式成为国家体育总局认定的正式比赛项目之一。本规则是根据中国机器博弈专业委员会主办的全国大学生计算机博弈大赛的需要,参考人民体育出版社《中国二打一扑克竞赛规则》并结合JJ斗地主、百度百科、维基百科、联众游戏、腾讯游戏等网站文献编写完成。
术语约定
局
一副牌(编码见附表1)包括发牌、叫牌、出牌、记分的博弈过程,称为一局。
轮
三个参赛选手共坐一桌完成的若干局构成一轮。
牌型
玩家一次出牌的牌张组合(见附表2)。主要包括火箭、炸弹和普通牌型。
春天与反春天
地主所有牌出完,其它两家一张都未出,称为“春天”。
其它两家中有一家先出完牌,地主只出过一手牌,“反春天”。
一局牌的比赛过程
一局牌需三个玩家(按方位西0、南1、东2区分)参与。过程包括发牌、叫牌、出牌和计分4步骤完成。通过叫牌,一个玩家成为地主(庄家),其余两个玩家作为农民(防守方)与地主对抗。以某一玩家率先出尽手中牌来结束牌局判定胜负,并计算本局小分。
发牌
一副牌54张,每个玩家发17张,剩余3张作为底牌,在地主未确定之前所有玩家不能看底牌,待地主确定后,亮出底牌并将其归于地主。
叫牌
每轮从西家(0)开始叫牌,并按出牌的顺序轮流进行,每人叫一次牌。叫牌时可以叫“1分”,“2分”,“3分”或“不叫(0)”。后叫牌者只能叫比前面叫过的分数都高或者不叫。如果有牌手叫分后,另外两人选择不叫或有牌手叫到“3分”则结束叫牌,叫牌结束后所叫分值最大的牌手确定为地主。如果三位牌手均选择不叫则视为完成一局,各家本局不得分。确定地主后,底牌亮出并发给地主。
出牌
每局由庄家先出,按逆时针顺序玩家依次出牌或过牌不出。后续跟牌者须按照同样牌型和张数进行跟牌,也可出炸弹或者火箭,后面的出牌必须大于前一手出牌。如果连续两家不出则最后出牌者可领出任意合法牌型。
出牌大小的比较:
对于单牌,自大到小的牌张分值次序为大王、小王、2、A、K、Q、J、10、9、8、7、6、5、4、3
。各花色之间无大小的区别。
对于组合牌,除火箭及炸弹外,必须牌型与张数均相同时方可进行比较。其中对牌、三条、单顺、双顺、三顺以最大牌张比较大小;三带一、三顺带牌、四带二仅按其中三条、三顺、四条的牌张比较大小,与带牌大小无关。
火箭大于炸弹,火箭及炸弹均大于其他牌型,炸弹之间按牌张大小进行比较。
记分
一局牌打完之后,双方小分计算方法如下:
地主得分=2×胜负参数×100×底分×倍数
农民得分=胜负参数×100×底分×倍数
胜负参数:胜利方为1,失败方为-1;
底分:叫牌时的1、2、3分;
倍数:初始为1。
本局打出过炸弹则倍数×(1+炸弹个数)。(为减少分数波动程度,避免运气因素影响高技术牌手发挥,因此不采用2n算法)
打出火箭,倍数×2
打出“春天”,倍数×2
打出“反春天”,倍数×2
协议使用事例
下表示例为南方位选手与平台交互信息一例
序号 | 平台发送信息 | AI应答信息 | 信息内容 |
---|---|---|---|
1 | DOUDIZHUVER 1.0 | 协议版本号 | |
2 | NAME hrbust | 选手名称 | |
3 | INFO 1,4,1,6,9,2100,15 | 轮局信息 | |
4 | OK INFO | 应答轮局信息 | |
5 | DEAL B0,4,5,7,9,10,17,21,25, 33,34,39,41,43,44,45,46 |
发牌信息 (不换行) |
|
6 | OK DEAL | 应答发牌信息 | |
7 | BID A1 | 西方位叫牌转播 | |
8 | OK BID | 应答叫牌转播 | |
9 | BID WHAT | 询问南方位叫牌 | |
10 | BID B3 | 应答叫牌询问 | |
11 | BID C0 | 东方位叫牌转播 | |
12 | OK BID | 应答叫牌转播 | |
13 | LEFTOVER B27,48,53 | 底牌信息与交付 | |
14 | OK LEFTOVER | 应答底牌信息 | |
15 | PLAY WHAT | 询问出牌 | |
16 | PLAY B0,4,5,7 | 应答出牌询问 | |
17 | PLAY C-1 | 东方位出牌转播 | |
18 | OK PLAY | 应答出牌转播 | |
19 | PLAY A12,13,14,20 | 西方位出牌转播 | |
20 | OK PLAY | 应答出牌转播 | |
21 | PLAY WHAT | 询问出牌 | |
…… | …… | 重复到一方出尽 | |
n | GAMEOVER C | 赢家信息 | |
n+1 | OK GAMEOVER | 应答赢家信息 |
协议指令解释
注意:以下一条指令中如含多张牌编码,应按升序排列
1. DOUDIZHUVER ver
裁判告知选手所采用的协议版本号,当前为1.0
例如:DOUDIZHUVER 1.0
2. NAME player
选手回复姓名。
例如:NAME hrbust
3. INFO turnid,turncount,roundid,roundcount,upcount,maxscore,time
裁判告知选手当前轮局信息
turnid为当前轮序号
turncount为总轮数,暂定4轮,视参赛选手人数可赛前调整
roundid为当前局序号
roundcount为每轮总局数,暂定6局,视参赛选手人数可赛前调整
upcount为本轮可晋级到下一轮的选手数,视比赛进程可赛前调整
maxscore为封顶分数,暂定2100,视参赛选手人数可赛前调整
time为 AI引擎应答时间限制,单位秒,暂定为15,视比赛进程可赛前调整
例如:INFO 1,4,1,6,9,2100,15
4. OK INFO
选手应答轮局信息,无可变参数
5.DEAL dC1,C2,C3,C4,C5,C6,C7,C8,C9,C10,C11,C12,C13,C14,C15,C16,C17
裁判告知选手方位和发牌编码
d为A时表示选手为西方位,d为B时表示选手为南方位,d为C时表示选手为东方位
Ci为发给选手的纸牌编码,参见附表1,各牌编码间用逗号分隔
例如:DEAL B0,4,5,7,9,10,17,21,25,33,34,39,41,43,44,45,46
6.OK DEAL
选手应答发牌信息,无可变参数
7.BID dW
裁判转发其它选手叫牌信息
d为A时表示该选手为西,d为B时表示该选手为南,d为C时表示该选手为东
W为0表示不叫,1表示1分,2表示2分,3表示3分
例如:BID A1
8.OK BID
选手应答叫牌信息,无可变参数
9.BID WHAT
裁判询问选手叫牌,无可变参数
10.BID dW
选手应答叫牌询问
d为A时表示该选手为西,d为B时表示该选手为南,d为C时表示该选手为东
W为0表示不叫,1表示1分,2表示2分,3表示3分
例如:BID B3
11.LEFTOVER dC1,C2,C3
裁判通知底牌信息,含地主方位和牌编码
d为A时表示地主为西,d为B时表示地主为南,d为C时表示地主为东
Ci为发给地主的底牌编码,参见附表1,各牌编码间用逗号分隔
12.OK LEFTOVER
选手应答底牌信息,无可变参数
13.PLAY WHAT
裁判询问选手出牌,无可变参数
14.PLAY dC1,C2,…,Cn
选手应答裁判出牌询问
d为A时表示选手为西,d为B时表示选手为南,d为C时表示选手为东
Ci为选手出牌编码,参见附表1,各牌编码间用逗号分隔
如果C0为-1,表示弃权不出牌
例如:PLAY B0,4,5,7 或 PLAY C-1
如果d与引擎方位相同且C0为-1。表示判定引擎所在方位本次因无可出牌张自动PASS。
15.PLAY dC1,C2,…,Cn
裁判转发其它选手出牌信息,参数含义见上一指令。
16.OK PLAY
选手应答裁判出牌转发信息,无可变参数
17.GAMEOVER d
裁判告知本局最先出尽手中牌的赢家方位
d为A表示赢家为西,d为B表示赢家为南,d为C表示赢家为东
18.OK GAMEOVER
选手应答裁判本局赢家信息,无可变参数
19.ERROR d
裁判转发选手异常错误信息,本局结束
d为A表示异常选手为西,d为B表示异常选手为南,d为C表示异常选手为东
20.OK ERROR
选手应答裁判异常错误信息,无可变参数
提示:
1.选手引擎程序可通过标准输入输出流(例如cin和cout)以行为单位接受和发送指令。
2.指令和参数之间以一个空格分隔。
3.竞赛平台可发出的指令类型为:DOUDIZHUVER、INFO、DEAL、BID、LEFTOVER、PLAY、GAMEOVER和ERROR,选手引擎程序可发出的指令类型为:NAME、BID、PLAY和OK。
赛制
预赛
初赛进行4轮,每轮6局,最后取前9名选手晋级决赛(轮数和局数可根据当年报名参赛队数赛前适度调整)。每局采用同场牌(每张桌打相同的牌)。赛前按种子选手优先及抽签方法确定第一轮选手桌号和方位,之后各轮按积分采用瑞士编排座位。
初赛场分方案:
每轮小分封顶值为350*局数,底线值为-350*局数。每轮结束后,选手按当前所在方向名次获得初赛场分,各方向积分最低者,得1场分,各方向积分每前进1名加1场分,如果其小分低于底线分数再减1场分,如果其小分高于封顶分数再加1场分。如果多人小分相同时,则他们均分其应得的场分。小分高于封顶值时按封顶值记录,其他情况按实际积分记录。
晋级与淘汰:
第2轮比赛结束后取前18名进入下一轮
第3轮比赛结束后取前12名进入下一轮
第4轮比赛结束后取前9名进入决赛
场积分均值:总队数除以6取整+1,例如21个队,该值为INT(21/6)+1=4
轮空处理:首轮轮空选手由抽签决定,之后各方向名次最后者抽签决定轮空,但每名选手在全部赛事中只轮空一次。轮空者获得场分均值+1,6副牌时小分得2100。
迟到处理:选手5分钟内不能到场或开局,本轮按弃权处理,扣弃权者2倍场分均值,另外两家各得场分均值。小分按本场局数*350计。
决赛
决赛采用复式循环赛赛制(决赛9人以下,含9人):
带分规则:初赛(瑞士移位赛)晋级名单第1名带5.6比赛分,之后每个名次少带0.7比赛分,第8名带0.7比赛分,第9名无带分。
进行4轮,每轮6局,按积分排定名次。(轮数和局数也可能根据进程,赛前调节)
座位编排:采用固定编排,每名选手与其他8名对手相都遇于一次,按预赛瑞士积分赛的名次入座。
积分计算:每桌打相同的牌,每副牌的三个结果中,同方向得分最高的选手获得4比赛分,居中者获得2比赛分,最低者获得0比赛分。得分相同者分享其应得的比赛分。
轮空处理:轮空者在轮空牌副得到2.5比赛分。
迟到处理:选手5分钟内不能到场或开局,本轮按弃权处理,扣弃权者6场分,另外两家各得3分。
名次排定及平分处理
预赛:排定名次时先比较总场分,再比较总小分。小分仍相同者按抽签决定名次。
决赛:排定名次时先比较总场分,如总场分相同以瑞士移位赛的名次为准。
竞赛行为准则和处罚方法
1.参赛程序必须按照协议与组委会提供的统一平台系统进行通信,不得通过平台以外途径获取信息。
2.一轮比赛进程中不能更换对手、程序、参数;各轮之间可以更换对手、程序、参数。
3.因该项目同时含有合作和对抗性质,为保障竞赛公平性,每校只能有一只队伍参赛。参赛队必须确保操作员按比赛指定位置及时到位开始比赛,任何场次迟到或超时,按相应规则处罚。
4.同一个队伍如果在一轮比赛中出现3局或以上失误或错误时,则取消比赛资格。
5.超时处理
因为比赛进程比较紧凑,需要各桌选手严格按照比赛进程完成各轮比赛。
选手及引擎与平台单次交互时间应在15秒钟内,如果超过时限,按超时错误处理,本局进程到此结束,计分规则为:
(1)如果在底牌分发前产生超时,由超时方支付相应另外两个玩家每人350分(即封顶分除以局数)。
(2)如果在底牌分发后产生超时,按底牌归属后各玩家组成的炸弹总数计算炸弹数(不含春天和反春天),如果地主超时,则支付另外两个玩家每人(炸弹总数+1)叫牌分100,如果含火箭则再乘2;如果农民超时,则支付另一农民(炸弹总数+1)叫牌分100,如果含火箭则再乘2,支付地主为另一农民分数的双倍。
附表
附表1 牌编码
编码 | 花色点数 | 编码 | 花色点数 | 编码 | 花色点数 | 编码 | 花色点数 |
---|---|---|---|---|---|---|---|
0 | ♥3 | 1 | ♦3 | 2 | ♠3 | 3 | ♣3 |
4 | ♥4 | 5 | ♦4 | 6 | ♠4 | 7 | ♣4 |
8 | ♥5 | 9 | ♦5 | 10 | ♠5 | 11 | ♣5 |
12 | ♥6 | 13 | ♦6 | 14 | ♠6 | 15 | ♣6 |
16 | ♥7 | 17 | ♦7 | 18 | ♠7 | 19 | ♣7 |
20 | ♥8 | 21 | ♦8 | 22 | ♠8 | 23 | ♣8 |
24 | ♥9 | 25 | ♦9 | 26 | ♠9 | 27 | ♣9 |
28 | ♥10 | 29 | ♦10 | 30 | ♠10 | 31 | ♣10 |
32 | ♥J | 33 | ♦J | 34 | ♠J | 35 | ♣J |
36 | ♥Q | 37 | ♦Q | 38 | ♠Q | 39 | ♣Q |
40 | ♥K | 41 | ♦K | 42 | ♠K | 43 | ♣K |
44 | ♥A | 45 | ♦A | 46 | ♠A | 47 | ♣A |
48 | ♥2 | 49 | ♦2 | 50 | ♠2 | 51 | ♣2 |
52 | 小王 | ||||||
53 | 大王 |
附表2 牌型
火箭大于炸弹和普通牌型、炸弹大于普通牌型。为避免牌型解释二义性,牌型按附表2顺序依次比较,首次匹配成功则确定为该牌型,不再向下匹配。
所带牌点数可以相同,即444+555+7+7,或555+666+99+99也为合法,牌型6、10、11带牌之间点数可以不连续。
编号 | 牌型 | 描述和备注 |
---|---|---|
0 | 弃权 | 无出牌 |
1 | 火箭 | 双王(大王和小王),最大的牌型 |
2 | 炸弹 | 四张同点数牌(型如AAAA) |
3 | 单牌 | 一张单牌(型如A) |
4 | 对牌 | 点数相同的两张牌(型如AA) |
5 | 三条 | 点数相同的三张牌(型如AAA) |
6 | 三带一 | 三条带一张单牌或一对牌。(型如:AAA+B或AAA+BB) |
7 | 单顺 | 五张或更多的连续单牌不包括2和王(型如:ABCDE或ABCDE…) |
8 | 双顺 | 三对或更多的连续对牌不包括2和王(型如:AABBCC或AABBCC..) |
9 | 三顺 | 二个或更多的连续三条不包括2和王(型如:AAABBB或AAABBBCCC…) |
10 | 三顺带牌 | 三顺带同数量的单牌或同数量的对牌(型如:AAABBB+C+D或AAABBB+CC+DD或AAABBB… +…+Y+Z或AAABBB… +…+YY+ZZ) |
11 | 四带二 | 四张同点数牌带2张单牌或2对牌(型如AAAA+B+C或AAAA+BB+CC) |
-1 | 非法牌型 | 以上牌型以外的牌张组合 |
资源
资源链接: https://github.com/Big-worth/DDZ.git
使用Git Bash的命令行
1 | git clone https://github.com/Big-worth/DDZ.git DDZ |
沈阳航空航天大学计算机博弈——二打一讲解:
代码修改指南
初始化准备工作
本文所使用的的环境是Visual Studio 2019
,使用其他版本可能会有一定差别。
首先打开Visual Studio 2019
,将DDZ中的源代码文件夹中的头文件和源文件分别放入相应位置。
修改SDL检查
点击项目->属性->C/C++->SDL检查改成否。
修改队伍名
02.cpp
文件中的第一行表示队名,将引号中的名字更改成自己队伍的名字即可。
叫分函数
位于02.cpp
的1122行~1194行
叫分的原理是根据手牌好坏来决定叫多少分,会对起始的17张牌进行估值,最后选择叫几分。
iMyBid
变量存储的是最后要叫的分数(当然也可以自定义变量,只需要保证最后是int
类型,并且函数最下方返回的是这个变量就可以了)。
sum
变量存储的是手牌的估值,最后根据手牌的估值来抉择叫几分。
count
变量存储的是临时的叫分数,最后将这个值传给iMyBid
(其实并没什么用,可以直接改变iMyBid
的值)。
最开始需要先统计出手牌分别是什么,每张牌都有一个对应的编号,如下图。
示例代码中的first_hand_cards
数组用来记录初始17张牌,接下来在1134行~1137行开始统计手牌(相当于记牌器,有一说一,这个记牌器挺烂的,建议自己写一个好一点的,统计每张牌各有多少张的那种),pDdz->iOnHand
数组记录的是初始手牌的每张牌编号。
1139行~1159行是估值部分(这段可以完全自由发挥),根据每种牌型来计算估值是多少,将估值累积进sum
变量中。举个栗子:如果同时有大小王那么可以给sum
+个5
分。估值都是自己设定的,可以很大也可以很小,比如你认为炸弹价值很高就可以设置的高一点,炸弹价值没那么高就可以低一点。一般来讲,可以计算炸弹,A
和2
的数量,三带,顺子这几个的估值就可以了(示例代码不支持出双顺,但是能够对别人打出的双顺打出正确的出牌)。
1161行~1177行可以不用怎么改,根据sum
的值去改变count
的值,来抉择叫多少分,sum
的值就是手牌好坏的评判标准,一般来讲,值越大,那么手牌越好,可以叫的分数越高。
1180行~1188行的作用是判断其余几家的叫分是否大于等于自家叫分,比如上家叫了2
分,那么自家就不能叫1
分,只能改叫0
分(也就是不叫),这段代码的作用就是防止乱叫分。原代码用的goto
,这么写的话可能会产生许多不可抗力,建议把这段改掉,不用goto
去写。
出牌函数
56行~74行是根据自家所在的不同位置来选择不同的出牌策略,比如地主的上家可以打得激进一点,来抑制地主出牌。但实际上这样工作量非常大,没这个必要,因此可以让三家都用同一种出牌策略(即调用同一个函数)。
然后再找到你选用的那个函数,底下可能会有一个(有两个有,一个没有)“在轮次少于22
手数时不出大牌”的一段代码,一定要把这段代码注释掉(当然,你给它改出来也行)。这段代码是因为在牌局前期可能不一定非要出大牌,比如说上家出了3334,而你手里只有2223
能够压住上家的牌,不过因为是在牌局早期,所以希望尽可能留住大牌,这种时候往往会选择不出。但是这段代码是有的bug
的,很有可能导致在你先手的时候不出,导致三家PASS
的bug
,也就是三家都没有出牌,会直接判负。所以这部分及以下的代码需要全部注释掉。
CalCardsValue
函数就是计算估值的函数。这个函数的原理是会枚举出每一种合法情况,然后会假定已经打出这组牌,去计算剩余牌的估值,然后选择剩余手牌估值最大的情况。
这段也需要一个记牌器去记录你的手牌情况,原代码的记牌器挺蠢的,建议重新写一个,可以和叫分函数的记牌器用一个。
sum_count
用来记录拿掉一组牌之后剩余手牌的估值(也可以换成其他变量,只需要保证最后return的变量是你用来记录估值的变量就可以了)。
剩下的部分的估值策略和叫分函数的估值策略一样但不完全一样,根据维持手牌估值最大的原则,所以需要把想要先出的牌的估值设置的低一些,比如我想优先出单牌而不是优先出炸弹,那么就需要把每个炸弹的估值设置的高一点;或者想先出顺子,那么就需要把顺子的估值设置的比单牌还低,那样就会优先出顺子了。同时也可以考虑一下手牌数量的问题,就是每有一张手牌扣多少估值,这样可以优先打出更多的牌。最后还需要考虑一下手牌打空的情况,当手牌可以直接完全打空的时候,那么此时估值应该设置为最大(自己没牌就赢了那肯定这么出)。需要计算估值的东西可以自己想,大体上和叫分函数里考虑的东西一样,可以考虑得更细致一些。
上述估值策略存在一个明显的问题,举个栗子:当你手里有34567
的牌型的时候,上家打了一张3
,通过这种估值策略会导致拆掉顺子打出一张4
从而保证手牌估值最大。
如果希望更严谨亿点的话,可以在手牌数量剩的很少的时候改变一下估值策略(即让自己的手牌保持估值更小的做法),众所周知,手牌少的时候一般是拍剧后期,因此可以优先出大牌来争夺牌权,当然这并不一定是最优方式,因此当手牌剩余很少的时候可以改变一下出牌策略。
还有一个需要注意的问题,这段的估值非常重要,如果出现越界的话,会导致出现很多bug
,比如说出的牌不符合牌型;如果估值估得不够严谨或者不够细致,很有可能出现吃不起的bug
(具体表现为把上家的牌抢过来再打一遍),一般来讲这个bug
都是因为估值估反了,或者是不够细致,这种错误是最常见的也是最难改的。
平台的使用
DDZ文件中的斗地主博弈对战平台与示例引擎.rar
和斗地主示例引擎源程序与测试平台2015-04-06E.rar
均为平台文件,这里推荐使用前者,操作相对来说较为简单。
解压之后打开新平台文件夹
打开斗地主博弈对战平台2015-09-12C
文件
在代码写完之后进行编译,找到这个代码存储的位置,里面有一个是Debug
的文件夹,再点进去有一个生成的exe
文件。找到这个文件的位置之后记一下路径,接着打开平台,点击博弈模式->训练模式。
分别选择AI_西
AI_南
AI_东
,找到刚刚记下路径的exe
文件,打开文件。为了更好地测试,可以把底下的停顿时间调的长一点。
点击牌套数据->随机产生,最后牌局设定->开始牌局,就可以看到牌局情况了。