胡牌算法

关于胡牌算法

如何才算胡牌?

  • 当手牌能够完整地组合刻子(3张一样的牌)或顺子,且有一个将对(2张一样的牌)时,这幅手牌满足胡牌的基本条件。

核心算法:

  • 组合手牌算法:完整将手牌拆分成各个组合。

组合手牌算法

输入数据

 数据样本(D):

  • 普通牌值数据
  • 癞子牌(可充当其他牌)值数据

 组合器(G):

  • 将、刻、顺

输出结果

  • 手牌组合列表

算法数据

每次递归中,有用于缓存递归中查找组合结果的数据:

  • (1)用于缓存找出组合的当前胡牌组合数据;
  • (2)用于缓存已用于参与组合的牌数据;

算法实现

一重循环,一个判断:

  • (1)循环遍历传入的组合器,以未用于组合的数据为起点,查找组合;
  • (2)判断是否找到组合;若是则保存数据,并递归进入下一次组合;否则不处理。

算法分析

重复组合

在使用递归穷举胡牌组合时,难免出现组合重复的问题,这时候,需要考虑如何去除重复组合的问题。

  • 根据各个组合中牌值(牌面值),生成胡牌组合的唯一值。

缺失组合

在使用递归穷举胡牌组合,尤其是有癞子牌(可充当其他牌的牌)时,有可能会出现找出的胡牌组合不全问题。

  • 其一,需改变各个组合器的实现,力求穷举所有组合情况。然而这也意味着,会出现更多的重复组合,及大地影响性能。
  • 其二,可以从开始组合数据入手,从组合起始点来查找更多的组合情况。然而,对性能也存在影响。

组合次数

设,组合器个数为G,手牌张数为D。 为了计算的方便,这里假设,每个组合器组合牌张数俱为M。

成功组合的最少次数

当每次递归时,唯最后一次才成功找出组合,则可知最后的组合次数为:
count = G * (D/M)

成功组合的最多次数

当每次递归都能找到组合,然后进入下一个递归,则可知最后的组合次数为:
count = G ^ (D/M)

附录

接口函数

package hu

import (
    "bytes"
    "hu/combiner"
    "sort"
    "strconv"
)

type group []int

type huGroup [][]group

/* 通用胡牌算法
@params cards 传入手牌
@params lzCnt 可用癞子数量
@params combiners 组合器
@return huGroups 胡牌组合列表
*/
func GetHuGroups(cards []int, lzCnt int, combiners []combiner.Combiner) (huGroups []huGroup) {
    curHuGroup := make(huGroup, len(combiners))
    usedMap := make(map[int]bool)
    if len(cards) > 0 {
        checkGroupsByRecursion(0, cards, lzCnt, combiners, &huGroups, curHuGroup, usedMap)
    } else {
        checkGroupsByRecursion(-1, cards, lzCnt, combiners, &huGroups, curHuGroup, usedMap)
    }
    return uniqueHuGroups(huGroups)
}

// 递归检测胡牌组合
func checkGroupsByRecursion(startIdx int, cards []int, lzCnt int, combiners []combiner.Combiner, huGroups *[]huGroup, curHuGroup huGroup, usedMap map[int]bool) {
    for i := 0; i < len(combiners); i++ {
        if group := combiners[i](startIdx, cards, lzCnt, usedMap); group != nil {
            // 保存组合
            curHuGroup[i] = append(curHuGroup[i], group)
            for _, card := range group {
                if card == -1 {
                    lzCnt--
                } else {
                    usedMap[card] = true
                }
            }
            // 递归检测
            if len(usedMap) == len(cards)+lzCnt {
                *huGroups = append(*huGroups, copyHuGroup(curHuGroup))
            } else {
                nextIdx := getNextUnUsedIndex(startIdx, cards, usedMap)
                checkGroupsByRecursion(nextIdx, cards, lzCnt, combiners, huGroups, curHuGroup, usedMap)
            }
            // 还原组合
            curHuGroup[i] = curHuGroup[i][:len(curHuGroup[i])-1]
            for _, card := range group {
                if card == -1 {
                    lzCnt++
                } else {
                    delete(usedMap, card)
                }
            }
        }
    }
}

// 获取牌中下个还未使用的index
func getNextUnUsedIndex(startIdx int, cards []int, usedMap map[int]bool) int {
    for i := 0; i < len(cards); i++ {
        idx := (i + startIdx) % len(cards)
        if val, ok := usedMap[cards[idx]]; !ok || !val {
            return idx
        }
    }
    return -1
}

// 拷贝胡牌组合
func copyHuGroup(oriHuGroup huGroup) huGroup {
    newHuGroup := make(huGroup, len(oriHuGroup))
    for i := 0; i < len(oriHuGroup); i++ {
        newHuGroup[i] = make([]group, len(oriHuGroup[i]))
        for j := 0; j < len(oriHuGroup[i]); j++ {
            newHuGroup[i][j] = oriHuGroup[i][j]
        }
    }
    sortHuGroup(newHuGroup)
    return newHuGroup
}

/*
组合列表[用于对手牌的组合进行排序]
实现排序所要求的的Len、Less和Swap方法
*/
type groupList []group

func (this groupList) Len() int {
    return len(this)
}

func (this groupList) Less(i, j int) bool {
    return this[i][0] < this[j][0]
}

func (this groupList) Swap(i, j int) {
    this[i], this[j] = this[j], this[i]
}

// 排序胡牌组合
func sortHuGroup(oriHuGroup huGroup) {
    for i := 0; i < len(oriHuGroup); i++ {
        sort.Sort(groupList(oriHuGroup[i]))
    }
}

// 组合去重
func uniqueHuGroups(huGroups []huGroup) (newHuGroups []huGroup) {
    huGroupValMap := make(map[string]bool)
    var buffer bytes.Buffer
    for _, theHuGroup := range huGroups {
        buffer.Reset()
        for i := 0; i < len(theHuGroup); i++ {
            buffer.WriteString(strconv.Itoa(i)) // 组合类型
            buffer.WriteString("_")
            for j := 0; j < len(theHuGroup[i]); j++ {
                for k := 0; k < len(theHuGroup[i][j]); k++ {
                    buffer.WriteString(strconv.Itoa(theHuGroup[i][j][k] >> 8)) // 取牌的byte值
                    buffer.WriteString("_")
                }
            }
        }
        val := buffer.String()
        if _, isOk := huGroupValMap[val]; !isOk {
            newHuGroups = append(newHuGroups, theHuGroup)
            huGroupValMap[val] = true
        }
    }
    //fmt.Println("uniqueHuGroups huGroupValMap:", huGroupValMap)
    return newHuGroups
}


/* 特殊胡牌算法
@params cards 传入手牌
@params lzCnt 可用癞子数量
@params combiners 普通组合器
@params sCombiners 特殊组合器(递归中只会调用一次)
@return huGroups 胡牌组合列表
*/
func GetSpecialHuGroups(cards []int, lzCnt int, combiners, sCombiners []combiner.Combiner) (huGroups []huGroup) {
    curHuGroup := make(huGroup, len(combiners)+len(sCombiners))
    startIdx, usedMap, usedCbMap := 0, make(map[int]bool), make(map[int]bool)
    if len(cards) == 0 {
        startIdx = -1
    }
    checkSpecialHuGroups(startIdx, cards, lzCnt, combiners, sCombiners, &huGroups, curHuGroup, usedMap, usedCbMap)
    return uniqueHuGroups(huGroups)
}

// 递归检测胡牌组合
func checkSpecialHuGroups(startIdx int, cards []int, lzCnt int, combiners, sCombiners []combiner.Combiner, huGroups *[]huGroup, curHuGroup huGroup, usedMap, usedCbMap map[int]bool) {
    for i := 0; i < len(combiners)+len(sCombiners); i++ {
        // 获取组合
        var g group
        if i < len(combiners) {
            g = combiners[i](startIdx, cards, lzCnt, usedMap)
        } else {
            if _, ok := usedCbMap[i]; !ok { // 若未曾使用过,才使用该组合器进行组合
                g = sCombiners[i-len(combiners)](startIdx, cards, lzCnt, usedMap)
            }
        }
        if g == nil || len(g) == 0 {
            continue
        }
        // 标记使用过的组合器
        usedCbMap[i] = true
        // 保存组合
        curHuGroup[i] = append(curHuGroup[i], g)
        for _, card := range g {
            if card == -1 {
                lzCnt--
            } else {
                usedMap[card] = true
            }
        }
        // 递归检测
        if len(usedMap) == len(cards)+lzCnt {
            *huGroups = append(*huGroups, copyHuGroup(curHuGroup))
        } else {
            nextIdx := getNextUnUsedIndex(startIdx, cards, usedMap)
            checkSpecialHuGroups(nextIdx, cards, lzCnt, combiners, sCombiners, huGroups, curHuGroup, usedMap, usedCbMap)
        }
        // 还原组合
        curHuGroup[i] = curHuGroup[i][:len(curHuGroup[i])-1]
        for _, card := range g {
            if card == -1 {
                lzCnt++
            } else {
                delete(usedMap, card)
            }
        }
        // 还原标记使用过的组合器
        delete(usedCbMap, i)
    }
}


/* 优先组合算法
@params cards 传入手牌
@params lzCnt 可用癞子数量
@params combiners 普通组合器
@params sCombiners 特殊组合器(每次递归只会调用一次)
@return huGroups 胡牌组合列表
*/
func GetPriorityGroups(cards []int, lzCnt int, combiners []combiner.Combiner) (huGroups []huGroup) {
    if len(cards) > 0 {
        for i := 0; i < len(cards); i++ {
            if hg := checkPriorityGroups(i, cards, lzCnt, combiners); hg != nil {
                huGroups = append(huGroups, hg)
            }
        }
    } else if hg := checkPriorityGroups(-1, cards, lzCnt, combiners); hg != nil {
        huGroups = append(huGroups, hg)
    }
    return uniqueHuGroups(huGroups)
}

func checkPriorityGroups(startIdx int, cards []int, lzCnt int, combiners []combiner.Combiner) huGroup {
    usedMap, curHuGroup, isCheckout := make(map[int]bool), make(huGroup, len(combiners)), false
    for len(usedMap) < len(cards)+lzCnt {
        isCheckout = false // 重置检出标记
        for i := 0; i < len(combiners); i++ {
            if g := combiners[i](startIdx, cards, lzCnt, usedMap); g != nil {
                // 保存组合
                curHuGroup[i] = append(curHuGroup[i], g)
                for _, card := range g {
                    if card == -1 {
                        lzCnt--
                    } else {
                        usedMap[card] = true
                    }
                }
                // 重置检测七点
                startIdx = getNextUnUsedIndex(startIdx, cards, usedMap)
                isCheckout = true // 标记检出
                break
            }
        }
        if !isCheckout { // 未检测出组合,直接返回,避免死循环
            return nil
        }
    }
    return curHuGroup
}

组合器(函数)

package combiner

type Combiner func(startIdx int, cards []int, lzCards []int, usedMap map[int]bool) []int

// 组合将
func CombineJiang(startIdx int, cards []int, lzCnt int, usedMap map[int]bool) []int {
    cardCount := 2
    if len(cards)+lzCnt < cardCount {
        return nil
    }
    curGroup := make([]int, cardCount)
    if startIdx >= 0 {
        // 查找普通牌
        startCard := cards[startIdx]
        curGroup[0] = startCard
        for i := 1; i < len(cards); i++ {
            card := cards[(i+startIdx)%len(cards)]
            if val, ok := usedMap[card]; !ok || !val {
                if curGroup[1] == 0 && (card>>8 == startCard>>8) {
                    curGroup[1] = card
                    return curGroup
                }
            }
        }
    }
    // 查找癞子牌
    for i := 0; i < lzCnt; i++ {
        if curGroup[0] == 0 {
            curGroup[0] = -1
        } else if curGroup[1] == 0 {
            curGroup[1] = -1
            return curGroup
        }
    }
    return nil
}

// 组合刻子
func CombineKe(startIdx int, cards []int, lzCnt int, usedMap map[int]bool) []int {
    cardCount := 3
    if len(cards)+lzCnt < cardCount {
        return nil
    }
    curGroup := make([]int, cardCount)
    if startIdx >= 0 {
        // 查找普通牌
        startCard := cards[startIdx]
        curGroup[0] = startCard
        for i := 1; i < len(cards); i++ {
            card := cards[(i+startIdx)%len(cards)]
            if val, ok := usedMap[card]; !ok || !val {
                if card>>8 == startCard>>8 {
                    if curGroup[1] == 0 {
                        curGroup[1] = card
                    } else {
                        curGroup[2] = card
                        return curGroup
                    }
                }
            }
        }
    }
    // 查找癞子牌
    for i := 0; i < lzCnt; i++ {
        if curGroup[0] == 0 {
            curGroup[0] = -1
        } else if curGroup[1] == 0 {
            curGroup[1] = -1
        } else {
            curGroup[2] = -1
            return curGroup
        }
    }
    return nil
}

// 组合顺子
func CombineShun(startIdx int, cards []int, lzCnt int, usedMap map[int]bool) []int {
    cardCount := 3
    if len(cards)+lzCnt < cardCount {
        return nil
    }
    curGroup, setTime := make([]int, cardCount), 0
    setGroup := func(idx, card int) {
        curGroup[idx] = card
        setTime++
    }
    if startIdx >= 0 {
        // 查找普通牌
        startCard := cards[startIdx]
        setGroup(0, startCard)
        for i := 1; i < len(cards); i++ {
            card := cards[(i+startIdx)%len(cards)]
            if val, ok := usedMap[card]; !ok || !val {
                if curGroup[1] == 0 && (card>>8-startCard>>8 == 1) {
                    setGroup(1, card)
                } else if curGroup[2] == 0 && (card>>8-startCard>>8 == 2) {
                    setGroup(2, card)
                }
                if setTime == 3 {
                    return curGroup
                }
            }
        }
    }
    // 查找癞子牌
    for i := 0; i < lzCnt; i++ {
        if curGroup[0] == 0 {
            setGroup(0, -1)
        } else if curGroup[1] == 0 {
            setGroup(1, -1)
        } else if curGroup[2] == 0 {
            setGroup(2, -1)
        }
        if setTime == 3 {
            return curGroup
        }
    }
    return nil
}

// 组合边顺
func CombineBianShun(startIdx int, cards []int, lzCnt int, usedMap map[int]bool) []int {
    cardCount := 2
    if len(cards)+lzCnt < cardCount {
        return nil
    }
    curGroup := make([]int, cardCount)
    if startIdx >= 0 {
        // 查找普通牌
        startCard := cards[startIdx]
        curGroup[0] = startCard
        for i := 1; i < len(cards); i++ {
            card := cards[(i+startIdx)%len(cards)]
            if val, ok := usedMap[card]; !ok || !val {
                if curGroup[1] == 0 && (card>>8-startCard>>8 == 1) {
                    curGroup[1] = card
                    return curGroup
                }
            }
        }
    }
    // 查找癞子牌
    for i := 0; i < lzCnt; i++ {
        if curGroup[0] == 0 {
            curGroup[0] = -1
        } else if curGroup[1] == 0 {
            curGroup[1] = -1
            return curGroup
        }
    }
    return nil
}

// 组合夹顺
func CombineJiaShun(startIdx int, cards []int, lzCnt int, usedMap map[int]bool) []int {
    cardCount := 2
    if len(cards)+lzCnt < cardCount {
        return nil
    }
    curGroup := make([]int, cardCount)
    if startIdx >= 0 {
        // 查找普通牌
        startCard := cards[startIdx]
        curGroup[0] = startCard
        for i := 1; i < len(cards); i++ {
            card := cards[(i+startIdx)%len(cards)]
            if val, ok := usedMap[card]; !ok || !val {
                if curGroup[1] == 0 && (card>>8-startCard>>8 == 2) {
                    curGroup[1] = card
                    return curGroup
                }
            }
        }
    }
    // 查找癞子牌
    for i := 0; i < lzCnt; i++ {
        if curGroup[0] == 0 {
            curGroup[0] = -1
        } else if curGroup[1] == 0 {
            curGroup[1] = -1
            return curGroup
        }
    }
    return nil
}

// 组合单牌
func CombineDan(startIdx int, cards []int, lzCnt int, usedMap map[int]bool) []int {
    cardCount := 1
    if len(cards)+lzCnt < cardCount {
        return nil
    }
    curGroup := make([]int, cardCount)
    if startIdx >= 0 {
        // 查找普通牌
        startCard := cards[startIdx]
        curGroup[0] = startCard
        return curGroup
    }
    // 查找癞子牌
    for i := 0; i < lzCnt; i++ {
        if curGroup[0] == 0 {
            curGroup[0] = -1
            return curGroup
        }
    }
    return nil
}

results matching ""

    No results matching ""