局域网内双升游戏的设计(三)--算法

news/2024/10/22 7:51:18/

发牌算法

        当每个玩家都准备完毕后,那么第一步就是要发牌。需要做到一个完全随机的发牌,就要保证每张牌发到每个玩家手里的概率都是一样的,而且牌的顺序是等概率随机打乱的。程序中采用的是如下的发牌算法(感谢Dr.Light提供):

        假如有两幅牌,编号从1到108,首先随机选出一个,并且将牌发给玩家,然后将这个编号的牌与108号牌交换编号,那么剩下的牌就是从1到107号。于是再从中选出一个,重复以上的过程,这样一来,算法的复杂度就是O(n)。

牌的逻辑

在升级中,牌只有三种形式,一种是拖拉机,一种是单张(对子其实就只是长度为1的拖拉机),另一种就是甩牌时两种牌的混合。在程序中,将牌的类型抽象为三个类,如下图所示:(CCardFactory只是创建牌用的,不是具体的牌类型)

图 1.             牌的类结构图

        里面的几个虚函数主要解决了以下几个问题:

        Ø        牌对象的创建。

        Ø        两手牌比大小。

        Ø        甩牌时判断是否能够甩,即:保证甩出来的各个牌对象在其他玩家中,都是最大的。

        Ø        先出拖拉机(对子)时,对方出牌时必须从最长的拖拉机开始出,通俗点的意思就是有对子必须先出对子。在后面的讨论中,我们将其称为出的牌是否饱满。

        在研究牌的算法前,首先要将牌按照当前的主的类型和数字来排列好。为了计算的方便,将牌散列到一个大小为52(常主是一样大)的哈希表中,这样,从中找出拖拉机就相对方便些。

图 2.             散列后牌的分布

        假如方片为主,那么哈希表中每一部分对应的牌的类型就如上图所示,其中,常主和方块同属于主,(图中黄色标记)。这样一来,2244(3为主)就很容易被识别为拖拉机。方块AA黑桃33(3为主)也是连在一起的,也很容易被识别为拖拉机,同时,主的大小比其他的牌都大,这在杀的判断过程中也是有用的。另外,还需要一个判断的方法,即:一堆牌中是否都是属于同一个类型的牌(哈希表中是否为同一个颜色),如果不同类型,那么就不用判断。

        在打常主(亮大王)的时候,会有一些区别(4个常主一样大),这些小区别这里不赘述。

        接下来就是具体的算法。

牌对象的创建

        在每一轮出牌的过程中,只保存一个牌的对象,就是当前这轮中,最大的一手牌,保存在m_pCurrentCards中。下一个人出的牌,都由m_pCurrentCards来创建,通过虚函数createCards来实现,这样的好处是可以将不同类型的创建规则分散到不同的类中去。

        举例说明:如果当前第一个人出了一个3344的拖拉机,而第二个人出了4个单张,此时由m_pCurrentCards(保存着3344的CPairCards对象)来创建一个新的牌,于是就按照CPairCards的规则来创建一个牌对象,返回一个NULL,那么说明第二个人没有大过第一个人的牌,就可以忽略他。如果第三个人出了个5566拖拉机,此时由m_pCurrentCards的对象(保存着3344的CPairCards对象)来创建一个新的牌,就是保存着5566的CPardCard的对象,大过了第一个人出的牌,于是将此时m_pCurrentCards给替换为保存着5566的CPardCard对象。

        首先按照上一节所说的方法将玩家打的牌散列到哈希表中。然后调用m_pCurrentCards的createCards函数创建新牌。单张牌和拖拉机(对子)的创建比较简单,就不做说明,主要说下甩牌时CBlendCards的创建。在介绍具体创建过程之前,先介绍一下程序中定义的一个动作:strip。

        从字面上看,就是剥离。假设第一个出牌的人出了一个对子和一个单张,那么CBlendCards对象中分别有一个CSingleCard和CPairCards对象。那么,就要依次从后面人出的牌中,剥离出一个单张和一个对子,即:创建一个CSingleCard和CPairCards对象,并且从哈希表中删除对应的计数(在操作中,要考虑是否类型匹配)。下面将用图的形式说明:

图 3.             散列后的黑桃AKK

        如上图所示:假如某人先甩了黑桃的AKK,那么将牌散列到哈希表中后就如上图所示(省略其他部分)。此时由CCardFactory来创建牌,首先用一个CPairCards的对象对其做strip操作,操作后等于将其中的一个对子给剥离出来,并创建一个CPairCards对象:

图 4.             剥离出一个对子后

        同理,再剥离出后,最后生成一个CPairCards对象和一个CSingleCard对象。两者组合成一个CBlendCards对象。


图 5.             最后生成的牌对象

        此时如果第二个玩家出了红桃AKK(假设红桃为主),那就是杀了这一对。此时由于不是第一手牌,那么就会由当前最大的牌来创建新的牌。也就是刚生成的CBlendCards对象。生成的过程也是使用strip操作,先用CPairCards对象对其strip,生成一个新的对子对象:

图 6.             使用CBlendCards对象生成新对象

        同理,在使用CSingleCard对其strip后,就生成了一个新的CBlendCards对象,其中包含一个CPairCard和一个CSingleCard:


图 7.             根据第二个人出的牌生成的结果

        有了strip操作,那么创建CBlendCards的步骤就是针对它拥有的CSingleCard和CPairCards对象,依次调用strip操作,如果每个都能正常strip,那么就将生产的牌对象组成一个CBlendCards对象,就生成了新出的牌对象。strip操作在后续还会使用到。

        如果是第一手出牌的话,当前没有最大的牌,就由CCardFactory来创建。CCardFactory虽然也是CShengjiCardBase的子类,但是它并不代表具体的牌。只是根据第一个玩家打过来的牌的集合来创建出一个牌的对象。它内部分别有一个CSingleCard、CPairCards和CBlendCards对象,每次新建的时候使用默认的这几个对象来创建。如果三个对象都创建不了,说明出牌错误。一轮出牌结束后,统计完分数,就将当前最大的牌删除,并且使用CCardFactory对象替换m_pCurrentCards中原有的牌。

两手牌比大小

        每个玩家出牌后,就需要判断出来的牌的大小。现在就体现出前面保存最大牌的好处了。每次只要将新出的牌与最大的牌比大小就可以了。而具体的比大小的工作在每种牌各自的重载函数largerThan中做即可。

        拖拉机(对子)和单张的牌比较好比。主要说明一下甩牌CBlendCard的大小比较机制。在升级规则中如果甩牌的话,所以的牌都必须是其他人中最大的,否则不能出。那么,在甩牌后,如果想比出牌人更大的话,就必须要用主来杀。在我们程序中,保证最大这个是由出牌的时候的机制来保证,而比大小的时候,只要依次判断每个牌是否都大即可,如果新出的牌中,某一部分牌不大,就说明新出的牌没有大过m_pCurrentCards。

甩牌时判断是否能够甩

        在第一手出牌时,还需要判断当前的牌是否能出的出去:甩牌时每一部分的牌都需要比别人手中同花色最大的牌还要大。虚函数getIllegalCards实现了这个功能。它能获取一堆牌中,比其他人手中的牌小的那部分牌。

        判断的逻辑主要用到了前面说到的strip操作。当甩牌后,会由CCardFactory创建一个CBlendCards对象,其中有若干个CSingleCard和CPairCards对象。此时,将其他玩家的手牌散列到哈希表中,然后依次调用CBlendCards中各个牌对象的stripCards方法,剥离出对应的牌对象。如果某一次剥离出的牌对象比当前CBlendCards中的对象大,说明甩牌失败,需要强制出小。

出牌是否饱满

        根据前面的问题说明,先出拖拉机(对子)时,对方出牌时必须从最长的拖拉机开始出,通俗点的意思就是有对子必须先出对子。在后面的讨论中,我们将其称为出的牌是否饱满。

        拿一个简单的例子作为说明:

图 8.             出牌饱满性示意图

        假设玩家1先出了一个拖拉机,玩家2手中有776543的牌,按照规则,应该将一对7给出出去,但是玩家2却没有将对7打出,所以应该要能够识别出这种错误状态。

        首先,玩家1打出拖拉机后,会生成一个CPairCards的对象,长度为2,接着用这个CPairCards对象对玩家2的手牌和玩家2打出的牌分别提取其中一个最饱满的牌。何为最饱满的牌?如上图所示,如果出了一个拖拉机后,饱满长度按照以下排列:长度为2的拖拉机>对子>单张。提取方法就是前面所说的strip,但是要strip直到生成牌的个数相等。

        举例说明,按照玩家1打出的牌,生成一个CPairCards牌对象后,这个对象的长度为2,那么从2开始,先用一个长度为2的CPairCards对象strip手牌,结果得到是空,因为玩家我2手中没有拖拉机,然后依次递减,用长度为1的CPairCards对象来strip手牌,得到一个长度为1的CPairCards对象(对7)。但是到现在还不能结束,要等到生成的对象的牌个数等于4张(AAKK)的时候才能结束。于是继续strip长度为1的CPairCards,返回为空,因为剥离出一个对7后只有单张牌。那么就换CSingleCard对象继续做以上的操作,直到strip出两个CSingleCard对象后才算结束。此时,strip出的对象牌数加起来是4(CPairCards和两个CSingleCard对象)。操作结束。

        同理,用手牌生成的长度为2的CPairCards对象对玩家2打出的牌做同样操作,得到4个CSingleCard对象。

        最后,通过饱满度从大到小比较得到的两组对象,比较后发现,从手牌中得到的一组对象中,对7对应的CPairCards对象的饱满度大于从打出的牌中得到的CSingleCard对象。所以出牌错误。

总结(出牌说明)

        下面两个流程图分别给出了第一手牌和非第一手牌时,服务器收到出牌消息后的处理:

图 9.             第一手牌的处理

图 10.         非第一手牌的处理

          参考代码+编译后的程序:http://download.csdn.net/detail/hustxyj/7024091


http://www.ppmy.cn/news/856543.html

相关文章

局域网内双升游戏的设计

本科毕业前,就有意向开始写一个双升的游戏,那时候用QT写了个界面,不过由于架构太混乱,导致后面不想去维护。研究生第二年下学期,实验室活不多,就乘机写了一个 新的牌的框架,正好实践了很多设计模…

手机自动化脚本-- 模拟器模拟真机环境过检测

文章目录 安装包目录结构一、安装雷电模拟器9二、新建模拟器并且初始化1. 性能设置2. 其他设置3. 当前配置三、面具1. 安装2. 面具初始化四、修改机型1. 将机型模块安装到模拟器2. 安装机型模块五、修改su问题1. 安装RE管理器2. 删除自带su文件,避免文件冲突六、下载地址额外安…

论文(1)——大家说SCI的一区二区和CCF中A类B类是什么意思?

文章目录 引言问题描述问题解决CCF 和A、B、C类CCF注意事项 SCI和一区、二区如何判定你找的论文所属的会议或期刊是几区或者几类?使用特定的网站查询使用浏览器插件 一年之内的应该投什么刊物? 总结参考 引言 已经研一暑假了,周围很多人已经…

三星依靠折叠手机技术优势在中国市场强力复兴,中国手机该担心了

据某电商公布的热销安卓旗舰手机排行榜,三星8月底上市的两款高端手机Galaxy Z flip3和Galaxy Z fold3均进入了热销榜top10,分别位居第六名、第七名,显示出这家手机企业在被中国消费者冷落数年后似乎开始重获中国消费者的认可。 某电商公布的这…

华为、小米已成老年手机市场主力—1.5万条老年手机电商数据解读

过去几年,大部分中老年人接触互联网都是从使用智能手机开始的,正是由于智能手机在中老年人群中的快速普及推动了互联网在中老年人群中渗透率的大幅增长,随着智能手机越用越熟练,他们开始广泛体验语音、视频、K歌、支付、追剧、看小…

安卓开发者:如何成为Kotlin大师?附送18款kotlin开源项目(纯资源分享)

在开发界,新的语言不断出现,包括go、Rust、Nim、Julia、Scala、Swift…这个名单可以拉很长,但从来没有一款语言像Kotlin一样引起这样广泛的讨论。 在StackOverflow网站统计的,最受开发者欢迎的编程语言排行榜,Kotlin得到了 72.6% 的高比例支持,位列第四名。据 JetBrains…

线上手机市场彻底变天,小米气势如虹,有望跻身全球前三强

某电商公布的8月份热销手机排行榜显示,小米最多手机款式上榜,显示出小米手机在国内手机市场似乎见到了反弹的希望,苹果则以iPhone11高居第一名。 国内线上手机市场则有数年以小米为首,它在2011年推出手机后就一直以互联网营销之王…

安卓是免费的?谷歌开始挥舞镰刀收割了,比高通税还离谱

谷歌的安卓系统一直以来都宣传免费,然而早前欧洲反垄断调查披露的数据却显示谷歌已对安卓手机开始收费,每部手机收费最高达到40美元,这一收费标准甚至超过了广受诟病的高通税,看起来谷歌真的是生财有道啊。 一直以来谷歌的安卓系统…