胡牌算法(基于go,支持多赖子)

1,897 阅读3分钟

简介

麻将,四人骨牌博戏,流行于华人文化圈中。起源于中国,粤港澳及闽南地区俗称麻雀,由中国古人发明的博弈游戏,娱乐用具,一般用竹子、骨头或塑料制成的小长方块,上面刻有花纹或字样,每副136张。四人骨牌博戏,流行于华人文化圈中。不同地区的游戏规则稍有不同。麻将的牌式主要有“筒(文钱)”、“条(索子)”、“万(万贯)”等。

实现思路

1.胡牌条件:

牌型符合3n+2的形式。例如:

示例图

2.胡牌公式

N×顺子 + M×刻子 + 1×将
N>=0, M>=0

3.牌数据表示

//牌数据
var BaseCards = [136]byte{
	//36
	1, 2, 3, 4, 5, 6, 7, 8, 9, //1~9万
	1, 2, 3, 4, 5, 6, 7, 8, 9, //1~9万
	1, 2, 3, 4, 5, 6, 7, 8, 9, //1~9万
	1, 2, 3, 4, 5, 6, 7, 8, 9, //1~9万
	//36
	11, 12, 13, 14, 15, 16, 17, 18, 19, //1~9筒
	11, 12, 13, 14, 15, 16, 17, 18, 19, //1~9筒
	11, 12, 13, 14, 15, 16, 17, 18, 19, //1~9筒
	11, 12, 13, 14, 15, 16, 17, 18, 19, //1~9筒
	//36
	21, 22, 23, 24, 25, 26, 27, 28, 29, //1~9条
	21, 22, 23, 24, 25, 26, 27, 28, 29, //1~9条
	21, 22, 23, 24, 25, 26, 27, 28, 29, //1~9条
	21, 22, 23, 24, 25, 26, 27, 28, 29, //1~9条
	//28
	31, 32, 33, 34, 35, 36, 37, //东南西北中发白(字牌)
	31, 32, 33, 34, 35, 36, 37, //东南西北中发白(字牌)
	31, 32, 33, 34, 35, 36, 37, //东南西北中发白(字牌)
	31, 32, 33, 34, 35, 36, 37, //东南西北中发白(字牌)
}

//春夏秋冬梅兰竹菊(花牌) 8
var SpecialCards = [8]byte{41, 42, 43, 44, 45, 46, 47, 48}


//组合类型
const (
	GroupTypeSingle byte = iota //单张
	GroupTypeDouble             //对子
	GroupTypePenZi              //碰子
	GroupTypeShunZi             //顺子
)

4.计算各牌数量

  	//转换:将牌当作数组索引,计算每种牌的数量
	list := [40]byte{}
	for i := 0; i < count; i++ {
		list[cards[i]]++
	} 

5.多种组牌假设

牌无非可组成刻子、顺子和将三种组合,同样我们可将牌分成三种去假设组合,只要该假设组合成功之后下面的牌也是同样的方式依次去判断直到遍历最后一张,只要至少一种成功我们就可以胡牌,多种组合的成功也对于各种地方不同规则算分非常具有优势,列如:333,可组成3个刻子,也可以组成3个顺子,有些地方玩法刻子是算分的,那我们就优先选择组成3个刻子的组合。

废话不多说,直接上代码:

   
//是否胡牌
//@param cards []byte 普通牌
//@param special []byte 百搭牌
func CheckHu(cards []byte, specials []byte) (isHu bool, groups map[int][]Group) {
	count := len(cards)
	godCount := len(specials)
	if (count+godCount)%3 != 2 {
		return
	}
	//牌数量计算
	list := [MaxCard]byte{}
	for i := 0; i < count; i++ {
		list[cards[i]]++
	}
	//万
	startCard := byte(1)
	fmt.Println(list)
	isHu, groups = multiCheck(Group{}, startCard, list, godCount)
	if isHu {
		for kind, groupList := range groups {
			count = 0
			for i := 0; i < len(groupList); i++ {
				if groupList[i].Type == GroupTypeDouble {
					count++
				}
			}
			//必须只能一对
			if count != 1 {
				//去掉不符合的
				delete(groups, kind)
			}
		}
		if len(groups) < 1 {
			isHu = false
		}
	}
	return
}

分三种假设:


//多路判断
func multiCheck(newGroup Group, curCard byte, list [MaxCard]byte, godCount int) (ok bool, groups map[int][]Group) {
	groups = map[int][]Group{}
	curGroups := []Group{newGroup}
	curKind := 0
	success, otherGroups := check333(curCard, list, godCount)
	if success {
		ok = true
		for _, otherGroupList := range otherGroups {
			curKind++
			if _, exit := groups[curKind]; !exit {
				groups[curKind] = curGroups
			}
			groups[curKind] = append(groups[curKind], otherGroupList...)
		}
	}
	success, otherGroups = check123(curCard, list, godCount)
	if success {
		ok = true
		for _, otherGroupList := range otherGroups {
			curKind++
			if _, exit := groups[curKind]; !exit {
				groups[curKind] = curGroups
			}
			groups[curKind] = append(groups[curKind], otherGroupList...)
		}
	}
	success, otherGroups = check222(curCard, list, godCount)
	if success {
		ok = true
		for _, otherGroupList := range otherGroups {
			curKind++
			if _, exit := groups[curKind]; !exit {
				groups[curKind] = curGroups
			}
			groups[curKind] = append(groups[curKind], otherGroupList...)
		}
	}
	if len(groups) < 1 {
		groups[curKind] = curGroups
	}
	return
}

组刻子:

func check333(startCard byte, list [MaxCard]byte, godCount int) (ok bool, groups map[int][]Group) {
	for curCard := startCard; curCard < MaxCard; curCard++ {
		if list[curCard] > 0 {
			switch {
			case list[curCard] >= 3:
				list[curCard] -= 3
			case list[curCard] == 2 && godCount > 0:
				list[curCard] -= 2
				godCount--
			case list[curCard] == 1 && godCount > 1:
				list[curCard] -= 1
				godCount -= 2
			default:
				return
			}
			newGroup := Group{
				Type:  GroupTypePenZi,
				Cards: []byte{curCard, curCard, curCard},
			}
			ok, groups = multiCheck(newGroup, curCard, list, godCount)
			return
		}
	}
	ok = true
	return
}

组顺子:

func check123(startCard byte, list [MaxCard]byte, godCount int) (ok bool, groups map[int][]Group) {
	for curCard := startCard; curCard < MaxCard; curCard++ {
		if list[curCard] > 0 {
			color := curCard / 10
			//字牌不能组成顺子
			if color > 2 {
				return
			}
			card1 := curCard
			card2 := curCard + 1
			card3 := curCard + 2

			value := curCard % 10
			switch {
			case value < 8 && list[curCard+1] > 0 && list[curCard+2] > 0: //111
				list[card1]--
				list[card2]--
				list[card3]--
			case value < 8 && list[curCard+1] < 1 && list[curCard+2] < 1: //100
				if godCount < 2 {
					return
				}
				godCount -= 2
				list[card1]--
			case value < 8 && list[curCard+1] < 1 && list[curCard+2] > 0: //101
				if godCount < 1 {
					return
				}
				list[card1]--
				//list[card2]--
				list[card3]--
				godCount -= 1
			case value < 8 && list[curCard+1] > 0 && list[curCard+2] < 1: //110
				if godCount < 1 {
					return
				}
				list[card1]--
				list[card2]--
				//list[card3]--
				godCount -= 1
			case value < 9 && list[curCard+1] > 0: //11
				if godCount < 1 {
					return
				}
				card3 = card1 - 1
				list[card1]--
				list[card2]--
				//list[card3]--
				godCount -= 1
			default:
				return
			}
			newGroup := Group{
				Type:  GroupTypeShunZi, //碰子
				Cards: []byte{card1, card2, card3},
			}
			ok, groups = multiCheck(newGroup, curCard, list, godCount)
			return
		}
	}
	ok = true
	return
}

组将:

func check222(startCard byte, list [MaxCard]byte, godCount int) (ok bool, groups map[int][]Group) {
	for curCard := startCard; curCard < MaxCard; curCard++ {
		if list[curCard] > 0 {
			switch {
			case list[curCard] >= 2:
				list[curCard] -= 2
			case list[curCard] == 1 && godCount > 0:
				list[curCard] -= 1
				godCount--
			default:
				return
			}
			newGroup := Group{
				Type:  GroupTypeDouble, //碰子
				Cards: []byte{curCard, curCard},
			}
			ok, groups = multiCheck(newGroup, curCard, list, godCount)
			return
		}
	}
	ok = true
	return
}

测试

func TestCheckHu(t *testing.T) {
	//3332123
	cards := []byte{1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 11, 12, 13}
	hu, groups := CheckHu(cards, []byte{})
	if hu {
		for _, groupList := range groups {
			fmt.Println(groupList)
		}
	}
}

结果:

[{0 []} {2 [1 1 1]} {2 [2 2 2]} {2 [3 3 3]} {1 [4 4]} {3 [11 12 13]}]
[{0 []} {3 [1 2 3]} {3 [1 2 3]} {3 [1 2 3]} {1 [4 4]} {3 [11 12 13]}]
[{0 []} {3 [1 2 3]} {1 [1 1]} {3 [2 3 4]} {3 [2 3 4]} {3 [11 12 13]}]
[{0 []} {1 [1 1]} {3 [1 2 3]} {3 [2 3 4]} {3 [2 3 4]} {3 [11 12 13]}]

总结

目前尚未用于项目,如果你发现有bug和觉得需要改进的地方欢迎留言~