上篇博客传送门http://blog.csdn.net/sm9sun/article/details/65448140
上文中已经实现了基本胡法的算法,本章加入“癞子玩法”的判胡逻辑
对于癞子的处理方式无非就两种:
一,以枚举的方式使癞子转换为其他牌型进行进一步判断
二,在计算判胡时出现位置空缺时使用癞子进行补位
前者的优势是准确度相对容易把控,并且逻辑清晰。坏处是时间消耗较高,因为是指数级别,当出现3个癞子时(4个癞子直接判胡)需要大量的运算。
后者的优势是时间消耗可能会好一些,注意是可能!因为如果在补位是加入癞子,会极大的影响剪枝。因为你不确定当前非胡状态是否可能通过多个癞子进行补救
但是如果傻傻的将34个牌型逐次遍历一边无疑显得很low逼。。于是我们在枚举前构造一个满足癞子任命的序列(比如说 如果你没有一二三万,那么你肯定是不会把癞子当成一万的)
function GetAppointList(arr,Hun,AppoinList){
/*
#define 0~8 万
#define 9~17 饼
#define 18~26 条
#define 27 东
#define 28 南
#define 29 西
#define 30 北
#define 31 中
#define 32 发
#define 33 白
*/
for(i=0;i<34;++i)
{
if (i > 26) //风牌
{
if (arr[i] > 0) {
AppoinList.push(i);
}
}
else if (i == 0 || i == 9 || i == 18) //一,当有本身或者同时有二三时
{
if (arr[i] > 0 || (arr[i + 1] > 0 && arr[i + 2] > 0)) {
AppoinList.push(i);
}
}
else if (i == 8 || i == 17 || i == 26) {
if (arr[i] > 0 || (arr[i - 1] > 0 && arr[i - 2] > 0)) //九,当有本身或者同时有二三时
{
AppoinList.push(i);
}
}
else if (i == 1 || i == 10 || i == 19) {
if (arr[i] > 0 || (arr[i - 1] > 0 && arr[i + 1] > 0) || (arr[i + 1] > 0 && arr[i + 2] > 0)) //二,当有本身或者同时有一三或者有三四时
{
AppoinList.push(i);
}
}
else if (i == 7 || i == 16 || i == 25) {
if (arr[i] > 0 || (arr[i - 1] > 0 && arr[i + 1] > 0) || (arr[i - 1] > 0 && arr[i - 2] > 0)) //八,当有本身或者同时有七九或者有六七时
{
AppoinList.push(i);
}
}
else {
if (arr[i] > 0 || (arr[i - 1] > 0 && arr[i + 1] > 0) || (arr[i - 1] > 0 && arr[i - 2] > 0) || (arr[i + 1] > 0 && arr[i + 2] > 0)) //其他中间牌
{
AppoinList.push(i);
}
}
}
}
这样相对来说会减少大量的循环次数。
然后再说外面,之前的判胡函数,由于加入4癞子直接胡,所以在顶层进行判断,以免后续递归重复计算
相应的基本判胡与七小对判胡放进下一步函数处理
function CanHuPai(arr,Hun){
if(arr[Hun]==4)
{
return true;
}
else
{
var AppoinList=new Array();
GetAppointList(arr, Hun,AppoinList); //混牌可以任命的序列
return HunAppoint(arr,Hun,arr[Hun],AppoinList);
}
}
而且在判胡递归算法中,要加入牌大于4情况的处理,比如说4个一样的牌 胡赖子这种
在function CanHuPai_3N_recursive中加入:
if (arr[P] > 4) //混牌特殊处理
{
arr[P]-=3;
ret = CanHuPai_3N_recursive(arr, count - 3, P);
arr[P]+=3;
return ret;
}
在function CanHuPai_norm(arr)中加入:
else if (arr[i] > 4)
{
arr[i] -= 2;
ret = CanHuPai_3N_recursive(arr, count - 2, 0);
arr[i] += 2;
if (ret)
{
return ret;
}
}
最后就是癞子任命方法的实现了,首先当癞子数为0时,调用原有的判胡算法进行判胡,如果有癞子,则使一个癞子成为某个牌,然后再递归调用。
function HunAppoint(arr,Hun,HunCount,AppoinList)
arr为牌型状态数组,Hun为癞子ID,HunCount为未处理癞子个数 AppoinList为可以任命序列
这里可能有人会问,你的AppoinList难道不用刷新吗? 比如说 你有两个癞子,有一个孤独的一万, 你可以用两个癞子作为一二三万呀?
而你的序列不刷新,之前构造序列时判断也是必须当二三同时存在时才可以将一打入序列,这会不会有问题?
其实如果出现两个癞子与一个常牌组成一个组合的时候,我们可以只认为其算为3*该常牌,比如说我把他按照三个一万算,照样可以满足条件。
所以构造序列不存在两个癞子与一个常牌组成顺子的情况。
代码如下
/*
混牌任命牌型函数
若无混牌,则通过麻将判胡算法判胡
若有,则枚举可任命的牌序列,进行下一层递归
*/
function HunAppoint(arr,Hun,HunCount,AppoinList) {
// process.stdout.write(AppoinList+'\n');
var ret=false;
if (HunCount == 0) {
if (CanHuPai__7pair(arr)) {
return true;
}
else if (CanHuPai_norm(arr)) {
return true;
}
else {
return false;
}
}
else {
for(var i=0;i< AppoinList.length;++i) {
arr[AppoinList[i]]++;
arr[Hun]--;
ret = HunAppoint(arr, Hun, HunCount - 1, AppoinList, special);
arr[AppoinList[i]]--;
arr[Hun]++;
if (ret) {
return true;
}
}
}
return ret;
}
这里我要吐槽下,之前我用foreach来迭代AppoinList数组的,毕竟个人感觉arr[AppoinList[i]]可读性不是很好。。。
但是在foreach里用return竟然还会继续循环!!!!!!
例如:
[1, 2, 3, 4].forEach(function(e){
console.log(e);
if(e === 3) return;
})
照样输出1234
在这里浪费了大量的时间,我还以为我算法有问题= =
其应该算是一个独立的函数栈吧, return 只是从最里面的那个回调函数 return,不能从 forEach return
我也是日了狗了。
最后测试一下
input:
arr[0]+=3;
arr[1+9]+=1;
arr[3+9]+=1;
arr[4+9]+=1;
arr[5+9]+=0;
arr[6+9]+=1;
arr[8+9]+=3;
arr[29]+=2
arr[28]+=1
0,11,14,28,29
true
2017-03-24T08:47:42.399Z 2017-03-24T08:47:42.515Z 116
0(一万)是癞子,输出表示,可以胡一万,二饼,五饼,西风,南风。
应该是正确的。
后续的优化处理帖
http://blog.csdn.net/sm9sun/article/details/77774722