棋牌麻将 - 无癞子胡牌算法(优化)

本文详细介绍了麻将胡牌算法的核心思想和技术实现细节。通过“去伪存真”、“端本正源”、“去粗取精”的原则,文章阐述了如何通过生成中间表并对之进行处理来判断手牌是否能胡牌。适用于对算法感兴趣的读者。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

基础知识

本文涉及的所有名词均在博文中有说明: http://blog.youkuaiyun.com/kunyus/article/details/78644517

核心思想

  1. 去伪存真: 满足胡牌的部分, 要么是 {3*n}(不带将), 要么是 {3*n+2}(带将).

  2. 端本正源: 麻将牌胡牌(除特殊规则)和牌面没有直接关系, 只和数量以及所处位置(顺子)相关.

  3. 去粗取精: 麻将牌所有组合中, 只有顺子和其他牌有关联关系,且关系只存在于相连的两张牌中.

完整处理流程

生成中间表

  1. 将序数牌按花色以及牌个数进行归表并记录表中牌的总个数.

    此处用万举例, 其他花色结果一样:

    • 手牌 万: 1-9 各一张, 此时表内容应为: 111111111(9)
    • 手牌 万: 11 22 333 4 5 6 77 88, 此时表内容应为: 2231112200(14)
  2. 将字牌按牌个数进行归表并记录表中牌的总个数, 相邻牌用 0 隔开.

    如: 东风 南风 西风 北风 红中 发财 白板, 表内容: 1010101010101(7)

  3. 任意表中牌个数出现不满足 {3*n} 且不满足 {3*n+2} 的, 直接判定无法胡牌.


type MTable struct {
    Count int
    Tiles []int
}

// 0x01 - 0x09 - 筒
// 0x11 - 0x19 - 万
// 0x21 - 0x29 - 条
// 0x31 0x33 0x35 - 中发白
// 0x41 0x43 0x45 0x47 - 东南西北

func compileTiles(unlockTiles []byte) (int, error) {
    mtables := make([]MTable, 7)

    for i, _ := range mtables {
        mtables[i].Tiles = make([]int, 9)
    }

    for _, tile := range unlockTiles {
        group := tile / 0x10

        if int(group+1) > len(mtables) {
            return 0, errors.New("unrecognized type " + strconv.Itoa(int(group)) + " of tile")
        }

        tile := tile % 0x10 - 1

        if int(tile) > (len(mtables[group].Tiles) - 1) {
            return 0, errors.New("unrecognized tile " + strconv.Itoa(int(tile)))
        }

        mtables[group].Count++
        mtables[group].Tiles[tile]++

        if mtables[group].Tiles[tile] > 4 {
            return 0, errors.New("abnormal number " + strconv.Itoa(mtables[group].Tiles[tile]) + " of tile " + strconv.Itoa(int(tile)))
        }
    }

    super := 0
    for _, slice := range mtables {
        if 0 == slice.Count{
            continue
        }

        rest := slice.Count % 3 

        if 0 != rest && 2 != rest {
            return 0, errors.New("slice length " + strconv.Itoa(int(slice.Count)) + " check does not pass")
        }

        if err := decomposeMTable(slice.Tiles); nil != err{
            return 0, err
        }
    }

    return super, nil
}

处理中间表

  1. 分别对生成的中间表进行正向遍历, 并分别进行 2 - 6 步处理.

  2. 当前位是否为 0, 为 0 直接跳到下一个继续处理.

  3. 当前值是否为 1 且表长度大于 3, 满足分别对当前值和后两个值减 1 , 出现负数无法胡牌.

  4. 当前位是否为 2, 满足将其视为刻子, 值改 0, 跳到下一个继续处理

  5. 当前位是否为 3 , 满足将值改为 1 跳到步骤 2 处理, 处理失败无法胡牌.

  6. 当前位是否为 4, 满足将值改为 1 跳到步骤 2 处理, 处理失败将值改为 1 跳到步骤 2 处理, 处理失败无法胡牌.


func decomposeMTable(slice []int) (error){
    sliceSize := len(slice)

    for index := 0; index < sliceSize; index++ {
        tileCount := slice[index]

        switch(tileCount){
        case 0:
            continue
        case 1:
            if (sliceSize - index) < 3 {
                return errors.New("failed to remove sequence, slice length is not enough")
            }

            for k := 0; k < 3; k++{
                if slice[index + k] < 1 {
                    return errors.New("failed to remove sequence, continuous tile does not exist")
                }

                slice[index + k]--
            }
        case 2:
            slice[index] = 0
        default: // 3 && 4
            slice[index] -= 2
            for ; slice[index] > 0; slice[index]--{
                tslice := make([]int,len(slice) - index)
                copy(tslice, slice[index:])

                if err := decomposeMTable(tslice); nil == err{
                    return nil
                }
            }

            if 4 == tileCount {
                return errors.New("there can not be four identical tiles")
            }

            slice[index] = 0
        }

        if 0 != slice[index]{
            return errors.New("unexpected error")
        }
    }

    return nil
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值