麻将普通胡牌算法JS版(含癞子,非轮训)

2 篇文章 0 订阅

记录一下麻将的通用胡牌算法实现,只要满足X*ABC + Y*DDD + EE 即可胡牌。

在这里先分析一下最简单的胡牌思路:先找出所有可能的将牌,若除去两张将牌之外的所有牌都能成,则可胡牌。

将牌就是公式里唯一的对子即是EE顺子ABC或者刻子DDD

将牌的查找:遍历每张手牌,若有两张以上相同牌就能作将,或者用一张癞子凑也可。

接下来就只要判断一副牌是否成扑,伪码如下:

function isShunKe=(牌){ // 这里约定传入的牌是有序的、张数是3的倍数  
    if(没牌){  
        return true;  
    }  
    if(若第一张是顺子中的一张){  
        if(isShunKe(去掉该顺子剩下的牌)){  
            return true;  
        }  
    }  
    if(若第一张是刻子中的一张){  
        if(isShunKe(去掉该刻子剩下的牌)){  
            return true;  
        }  
    }  
    return false;  
}

 只要思路清晰了算法就很简单,为什么只考虑第一张牌?其实函数内部递归调用了每张牌都会计算到的,除非没牌了。下面给出详细代码:

function isShunKe(cards, laiziCount){  
    if (cards.length === 0){  
        return true;  
    }  
    // 若第一张是顺子中的一张  
    for (var first = cards[0] - 2; first <= cards[0]; first++) {  
        if(first % 10 > 7 || (laiziCount === 0 && first < cards[0])) {   
            // 顺子第一张牌不会大于7点、无赖子情况下顺子第一张只能用手上的牌  
            continue;  
        }  
        var shunCount = 0;  
        for (var i=0;i<3;i++) {  
            if (cards.indexOf(first + i) >= 0) {  
                shunCount++;  
            }  
        }  
        if (shunCount === 3 || shunCount + laiziCount >= 3) {  
            // 找到包含第一张牌的顺子  
            var puCards = cards.slice();  
            var puLaizi = laiziCount;  
            for (var i=0; i<3; i++) {  
                var deletePos = puCards.indexOf(first + i);  
                if (deletePos >= 0) {  
                    puCards.splice(deletePos, 1);  
                }  
                else {  
                    puLaizi--;  
                }  
            }  
            if (isShunKe(puCards, puLaizi)) {  
                // 剩下的牌成扑  
                return true;  
            }  
        }  
    }  
    // 若第一张是刻子中的一张  
    var keziCount = 1;  
    var keziCard = cards[0];  
    if (cards[1] === keziCard) {  
        keziCount++;  
    }  
    if (cards[2] === keziCard) {  
        keziCount++;  
    }  
    if (keziCount === 3 || keziCount + laiziCount >= 3) {  
        var puCards = cards.slice();  
        var puLaizi = laiziCount;  
        for (var i = 0; i < 3; i++) {  
            var deletePos = puCards.indexOf(keziCard);  
            if (deletePos >= 0) {  
                puCards.splice(deletePos, 1);  
            }  
            else {  
                puLaizi--;  
            }  
        }  
        if (isShunKe(puCards, puLaizi)) {  
            return true;  
        }  
    }  
    return false;  
}   

 下面是判断胡牌的函数:

function canHuLaizi(cards, laiziCount) {  
    if ((cards.length + laiziCount + 1) % 3 != 0) {  
        // 若牌张数不是2、5、8、11、14则不能胡  
        return false;  
    }  
    // 排序方便胡牌判断  
    cards.sort(function(a, b) {  
        return a - b;  
    })  
    // 依次删除一对牌做将,其余牌全部成扑则可胡  
    for (var i = 0; i < cards.length; i++) {  
        if (i > 0 && cards[i] == cards[i - 1]){  
            // 和上一次是同样的牌,避免重复计算  
            continue;   
        }  
        if ((i + 1 < cards.length && cards[i] == cards[i + 1]) || laiziCount > 0) {  
            // 找到对子、或是用一张癞子拼出的对子  
            var puCards = cards.slice();  
            var puLaizi = laiziCount;  
            puCards.splice(i, 1);  
            if (puCards[i] == cards[i]) {  
                puCards.splice(i, 1);  
            }  
            else {  
                puLaizi--;  
            }  
            // 删去对子判断剩下的牌是否成扑  
            if (isShunKe(puCards, puLaizi)) {  
                return true;  
            }  
        }  
    }  
    if (laiziCount >= 2 && isShunKe(cards, laiziCount - 2)) {  
        // 两个癞子做将牌特殊判定处理  
        return true;  
    }  
    return false;  
}  

这里做一下两个输入参数的说明:

  1. cards:手牌数组,不超过14张牌,每张牌由整数表示如下  
  2. 数字 {01 ~ 09} 表示  {1 ~ 9} 筒

    数字 {11 ~ 19} 表示  {1 ~ 9} 条

    数字 {21 ~ 29} 表示  {1 ~ 9} 万

    数字 {31 33 35 37 } 表示风 { 东 南 西 北 }    // 为什么间隔两个数字记录一个,就是为了防止将自拍在自己算过程中组成顺子

    数字 {41 43 45} 表示箭 {中 發 白}

     
  3. laiziCount:癞子数量,用整数表示  

 

有关算法代码重构,这里有个小技巧:

使用随机生成的牌型数据,将新版本代码与老版本代码对比,当运算结果不一样时候可以快速的找出bug,也可以循环大量输入统计耗时比较效率。

通过这个手段解决了一些小问题,最终跑1000万次牌型输出结果一致,就可以安心的替换了。

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值