手牌和牌时的组成
日本麻将除了特殊牌形(七对和国士)以外,都是 a个顺子+b个刻子+1个雀头
的形状,这里不管是门清还是非门清,都是这样。其中a、b均大于等于0。
我们可以将手牌分解成上面的形式,如果能够满足条件,则为和牌,然而手牌的组成有很多种,比如下面的手牌(九莲宝灯):
1 | 11123455678999s |
我们可以分解为1
111 234 55 678 999
可以和牌
也可以分解为1
11 123 345 567 8 999
无法和牌
从雀头入手
不管怎么分组,手牌都要有一个雀头
,所以我们先确定雀头
手牌中,数量大于等于2的牌都能作为雀头
当确定好雀头之后,就要将剩余的手牌分解为 顺子+刻子的情况,在上面的示例中,当选取5s作为雀头后,剩余的牌为 111234678999 ,我可以将 11123 认为是 111+23的形式,也可以是 11+123的形式,用程序也是比较方便实现的。
我的思路
将手牌排序后,找到n个雀头
对于每个雀头,都执行下面的判断逻辑
- 去除雀头,得到剩余手牌P
- 对P按照牌进行分组后排序
- 对于排序后的牌,如果是连续三张牌,则将这三张牌作为一个顺子
- 将顺子的三张牌从P中删除
- 重复2-3-4步骤,直到无顺子
- 对P进行分组
- 如果某牌数量是3,则将这三张牌作为一个刻子
- 将刻子从P中删除
- 重复6-7-8步骤,直到无刻子
- 如果P现在无牌,则表示此时能够和牌,如果有牌,表示不能和牌
是否听牌的判断
和牌和听牌不太一样,和牌是14张,听牌是13张,不管是不是门清状态,听牌必定币和牌少一张,少的是哪张牌呢?必定是下面三种情况中的其中一种
- 听雀头
- 听刻子中的一张
- 听顺子中的一张
也就是已有的手牌,加上上面三种中的其中一种,能够和牌,那我只需要将可能和牌的牌都列出来,每种都判断是否和牌就可以了。
对于自己的程序写的有没有问题,可以用九莲宝灯来验证一下
类的定义
首先是类型的枚举,定义了万、索、筒、字四种牌1
2
3
4
5
6public enum Type {
m,
s,
p,
z;
}
一张牌是由数字+类型组成的,所以牌类如下
1 | public class Pai implements Comparable<Pai> { |
对于字牌,一共有东南西北中发白,七种类型,对应的number取值为1到7,在编码时还需要注意字牌是无法组成顺子的。
对于一场比赛,牌的数量类型都是早就决定好不会变的,唯一变的就是牌的顺序,我们只需定义好一个所有牌的集合,在有需要生成牌山的时候,直接将其打乱使用即可。
宝牌是否要在class Pai
中定义?由于每场比赛,宝牌是变化的,与场况有关,所以不太适合放在其中。但是有一个例外——赤宝牌,我本想在Pai中增加一个属性aka/red,标示是否是赤宝牌,但想到现在只是在对手牌进行和牌、听牌的判断,增加这个字段与否和现在要解决的问题无关,索性就不管了,宝牌之类,留着后面再统一处理吧。
实际演示
相关代码会在整理后放到github上