目录
麻将的基本概念
麻将牌的构成
麻将牌是由下面的牌组成:
1.万字牌:1-9共9张(每个数字各一张)比如1万2万3万等
2.条子牌:1-9共9张(每个数字各一张) 比如1条,2条,3条等
3.筒子牌:1-9共9张(每个数字各一张) 比如1筒,2筒,3筒等
4.字牌:东南西北风各4张,红中、发财、白板各4张。
由基本牌组合而成的 万,条,筒的牌的数目为 4* 9 * 3 = 108 由字牌组成的牌的数量为 4 * 7 = 28
所以整副牌就由136张牌组成。花牌(春夏秋冬)等不计算。
麻将的碰,杠,吃,听,胡
碰、杠、吃是麻将游戏中常用的三种操作,以便于玩家将手中的牌进行组合,以尽可能地增加胡牌的机会。具体意义如下:
碰:当玩家手中有两张相同的牌,而桌上已有一张相同的牌时,玩家可以选择碰此牌,即将自己手中的两张相同的牌与桌上的一张相同的牌组成一个刻子。
杠:当玩家手中有一张牌,而桌上已有三张相同的牌时,玩家可以选择杠此牌,即将自己手中的一张牌与桌上的三张相同的牌组成一个杠。这里面杠有多种,在算法中一定要注意处理。 具体分类如下:
弯杠:当自己手中有两张牌,别人打出一张牌自己进行了碰操作,然后自己又摸到了一张牌,形成了杠
明杠:自己手中有3张牌,别人打出一张牌,形成的杠
暗杠: 自己摸到了4张一样的牌为暗杠
吃:当桌上有玩家打出一张牌后,其他玩家如果手中有两张数字连着的牌,就可以选择吃此牌,即将桌上的牌和手中的牌组合成一个顺子。
听:听牌是麻将游戏中的一个术语,指的是在某一时刻,手牌中的牌差一张即可和牌,当前的手牌一定是 3n+1张。
胡:在麻将游戏中,“胡”指的是牌手通过组成某些牌型,使结束时所剩余的牌组满足胡牌规则,从而赢得游戏的过程。
麻将胡牌条件
麻将想要胡牌,必须得满足一个条件就是 3n + 2
3 指的是由3张牌组成的面子( 顺子或者是刻子)
n 是指由n个(大于等于0)组成的面子
2 指的就是由2张牌组成的将,这个将是必不可少的,面子可以不存在,但是将必须得存在,也就是说 n可以为0,但是2必须存在。如下图所示:
举个例子,当手牌为14张时 正好满足3n +2 3x4 + 2 = 14 。表示由4组面子和一对将组成的手牌。再举个例子当手牌为 1万1万1万 5万5万 也是满足3n+2,也可以满足胡牌的条件。
胡牌算法简介
胡牌算法实现起来有多种多样,但是大致分为两种,一种是使用回溯算法,一种是查表法。
查表法是利用事先生成好的所有的胡牌牌型,然后将这些牌型加载到内存中,直接在内存中对比即可,效率非常快,缺点只不灵活,需要事先按规则生成好表。
我们现在介绍的这种算法也是用到了回溯算法,我们称之为 “选将拆分法” 。
我们先把手牌中所有可能做将的牌先找出来,然后去掉这组将牌,看剩下的牌是否能满足 3n 条件,如果可以满足则可以胡牌,如果不能满足则不能胡牌。
这种算法中间利用适当的“剪枝” ,执行起来效率非常的快,同时这种算法对于处理任意赖子的效率也是很强悍,不比查表法慢。
我后面会逐步的分析如何这种算法,让任何没有基础的人都可以彻底掌握这种算法。我们当前介绍的是普通情况下的胡牌,不计算赖子的情况下。
赖子的算法我们放在下一篇来讲,只要掌握了这种算法的原理,对于赖子的牌型算法也会很容易理解。
选将拆分法
选将拆分法就是将手牌中所有可能做将的牌,也可以说任意的牌,只要它的数量大于2,那么它都可以是将。
我们先把这些可以做将的牌单独的列举出来。然后利用回溯法开始遍历这些将,首先将此将牌从手牌中移除,然后判断剩余的牌是否能满足3n。如果可以满足那么就可以胡牌。当所有的将牌都遍历完了却没有满足3n,则这副手牌不能胡牌。
算法数据结构
构建数据结构
在前面我们己经讲述过了,牌一共为136张,万,条,筒各9种类型合计共27 种类型,字牌共7种类型,所以牌的类型总体加起来就是 34种类型。
因此我们可以使用一个数组来保存整副牌的数据结构。数组的下标用来表示当前牌的类型,数组的值用来表示当前类型的个数。因此我们就可以得出如下的数据结构:
int[] cards = {
2,0,0,0,0,0,0,0,0, //-- 1-9万
0,0,0,0,0,0,0,0,0, //-- 1-9条
0,0,0,0,0,0,0,0,0, //-- 1-9筒
0,0,0,0,0,0,0 //- 东南西北中发白
};
我来举个简单的例子来帮助大家理解下这个数据结构,当前数据结构中数组的下标0的值为2,这就是表示 1万这张牌的个数是2。
数据结构使用
我们为什么使用这种数据结构而不使用其它的数据结构来构造整副牌呢?主要原因有下面几个,我会逐一为大家阐述清楚。
牌花色的获取
使用此数据结构可以非常方便的知道任何一张牌所属的花色。我们使用 下标索引 除以9可以得出当前牌所属于的花色,比如 0/9 =0 ,9/9 =1 ,18/9=2 ,27/9 = 3 我们可以得出在数据结构中 万,条,筒它们的花色分别为 0 ,1 ,2。
获取某一花色的牌值
使用此数据结构可以非常方便的获取任何一种花色上面所有的牌,除了字牌为每种花色的牌都是9张,所以可以使用 (i / 9) * 9 获取此花色的最小值 ,然用用最小值加8就是最大值。
示例代码如下:
int min = (i / 9) * 9;
int max = min + 8;
if (max == 35) max = 33;
注:这上面的这段代码中因为字牌它的类型共有 7种,所以我们需要特殊处理下。
获取某一张牌相邻牌
便捷的查找一张牌它左右的牌,到时用来判断是否可以形成顺子。
算法代码实现
基础代码校验
有了上面的阐述,我们可以写一个简单的胡牌的方法,方法的参数接收的是我们上面说的数据结构造成的手牌数组,这个方法返回一个boolean类型用来判断是否可以胡。代码如下:
/**
* 判断手牌是否可以胡牌
* @param handCards
* <pre>
* 手牌的格式必须转换为如下数据格式,数组的下标表示牌的类型共34种类型,数组的值表示这个类型牌的数量<br>
* cards[0] = 2<br>
* 1万 共有2张 <br>
* int[] cards = {<br>
* 2,0,0,0,0,0,0,0,0, //-- 1-9万<br>
* 0,0,0,0,0,0,0,0,0, //-- 1-9条<br>
* 0,0,0,0,0,0,0,0,0, //-- 1-9筒<br>
* 0,0,0,0,0,0,0 //- 东南西北中发白<br>
* };
*</pre>
*
* @return
*/
public static boolean checkHandCardsCanWin(int[] handCards) {
int[] cards = new int[34];
int cardsCount = 0;
for (int i = 0; i < 34; ++i) {
cards[i] = handCards[i];
cardsCount+= handCards[i];
}
//当手牌数量不满足3n+2时不构成胡牌条件
if (!(cardsCount >= 2 && cardsCount <= 14 && (cardsCount - 2) % 3 == 0)) {
return false;
}
boolean hu = false;
return hu;
}
上面的这段代码就是增加了一个基础的判断,所有类型的牌的数量和是否满足3n+2
选将代码实现
下面我们修改这段代码增加选将的代码。选将的方法就是遍历所有的牌型然后找出牌的数量大于等于2的牌当作将。
我们在遍历的同时也需要特殊的处理下一些特殊的牌。我们看一组下面的牌型(W表示万 T表示筒)
这组牌中有一对将 就是 5T,如果我们使用上面分析的选将拆分法,将5T进行拆分出来&#x