红中麻将胡牌逻辑开发文档
一、字牌与封牌的字典表构建
在开发红中麻将胡牌逻辑时,首先需要构建非字牌和封牌的字典表。在构建双层表时,需进行判断:若牌为风牌且索引 i 大于 6,则不进行处理。这是因为风牌包含东南西北中,共 7 张,而实际上牌堆中有 9 张牌,除了这 7 张风牌,还有春夏秋冬、梅兰竹菊等特殊牌,每种牌各有 4 张,但通常这些特殊牌被认为是没用的牌,一般情况下不会使用,仅在特殊情况下才会涉及。
在构建字典表时,还需注意封牌的处理逻辑。若牌为风牌且处于特定位置,应添加相应判断条件,并根据条件进行不同操作。例如,若牌为风牌且索引 i 等于某个特定值,则需进行特定操作。在生成字典表时,要遵循分牌逻辑,确保字典表的完整性和准确性。
二、有轨与无轨字典表的生成
在生成有轨字典表时,操作相对简单,只需按照既定规则逐个替换即可。生成鬼牌时,可编写一个名为“GN腿宝鬼”的方法,将相关参数传入,根据鬼牌数量从第一个开始依次增加。
在生成过程中,需先将无轨的原有答案取出,判断题目是否一致,若一致则直接替换。替换后,要将结果取出,并根据结果进行进一步处理。例如,若取出的值为 3112,需将其转换为 2112 等形式,以满足替换条件。通过循环和相应操作,可实现对每个数字的处理和替换。
在生成有轨字典表时,还需注意对鬼牌数量的处理。鬼牌数量应在 1 到 7 之间,若小于 1 或大于 7,则无需进行判断和处理。在生成过程中,要对每个位置的牌进行判断,若某位置无牌,则不进行处理;若有牌,则进行相应操作,并将结果添加到指定位置。
三、胡牌算法实现
胡牌算法的核心在于查表。首先,需统计每种牌的数量,可使用二维数组进行统计,数组的行代表四种花色(ABCD),列代表牌的编号(1-9)。通过循环遍历牌堆,将每张牌的数量统计到二维数组中,从而为后续的编码和查表操作提供基础数据。
在查表前,需先判断牌中是否含有鬼牌,并统计鬼牌数量。可通过编写方法判断牌中是否存在特定牌,如“中”牌等。若存在,则对鬼牌数量进行相应处理。
查表时,先对牌进行编码,根据编码在字典表中查找对应的胡牌情况。若鬼牌数量大于 0,则在带鬼子的字典表中查找;若为封牌,则在封牌字典表中查找。查找时,需根据不同的牌型和鬼牌数量进行判断,若找到匹配项,则返回相应的胡牌结果;若未找到,则继续查找其他可能的组合。
在查找过程中,还需考虑将牌的使用情况。若牌中已使用了将牌,则需判断剩余鬼牌数量是否满足胡牌条件;若未使用将牌,则需将剩余鬼牌数量与牌型进行匹配,确保胡牌的合法性。
// Table 字典表
// 1A-9A B C 非字牌 一样 生成一种就行 D生成一种(没有连子的)
// 穷举所有胡牌可能 存入Table0 D TableFeng0
// 有1个鬼 将Table0中的数据 替换一个 然后存入 Table1
// 有2个鬼 将Table0中的数据 替换2个 然后存入 Table2
// 有3个鬼 将Table0中的数据 替换3个 然后存入 Table3
// 有4个鬼 将Table0中的数据 替换4个 然后存入 Table4
// 有5个鬼 将Table0中的数据 替换5个 然后存入 Table5
// 有6个鬼 将Table0中的数据 替换6个 然后存入 Table6
// 有7个鬼 将Table0中的数据 替换7个 然后存入 Table7
type Table struct {
keyDic map[string]bool //非字牌ABC 无鬼 字典
keyGuiDic map[int]map[string]bool //非字牌ABC 有鬼 字典
keyFengDic map[string]bool //字牌D 无鬼 字典
keyFengGuiDic map[int]map[string]bool //字牌D 有鬼 字典
}
func NewTable() *Table {
t := &Table{
keyDic: make(map[string]bool),
keyGuiDic: make(map[int]map[string]bool),
keyFengDic: make(map[string]bool),
keyFengGuiDic: make(map[int]map[string]bool),
}
t.gen()
return t
}
// gen 生成字典
func (t *Table) gen() {
//311100000 一种hu可能
cards := []int{0, 0, 0, 0, 0, 0, 0, 0, 0}
level := 0
jiang := false
feng := false
t.genTableNoGui(cards, level, jiang, feng)
//挨个去替换即可
t.genTableGui(feng)
feng = true
t.genTableNoGui(cards, level, jiang, feng)
t.genTableGui(feng)
t.save()
}
func (t *Table) genTableNoGui(cards []int, level int, jiang bool, feng bool) {
//生成的逻辑 比较简单
//ABC
//挨个去放 AAA组合 刻子 14张 3+3+3+2+3 5个层级 最多递归5次就可以
//ABC 连子
//DD 将 这个只能有一个 jiang=true
for i := 0; i < 9; i++ {
//feng 东南西北中发白 7张牌*4 春夏秋冬梅兰竹菊 没用的牌
if feng && i > 6 {
continue
}
//1. 需要先将cards中的牌数量计算出来,后续做判断用
totalCardCount := t.calTotalCardCount(cards)
//AAA 刻子
if totalCardCount <= 11 && cards[i] <= 1 {
cards[i] += 3
key := t.generateKey(cards)
if feng {
t.keyFengDic[key] = true
} else {
t.keyDic[key] = true
}
if level < 5 {
t.genTableNoGui(cards, level+1, jiang, feng)
}
cards[i] -= 3
}
//连子 ABC
if !feng && totalCardCount <= 11 && i < 7 && cards[i] <= 3 &&
cards[i+1] <= 3 && cards[i+2] <= 3 {
cards[i] += 1
cards[i+1] += 1
cards[i+2] += 1
key := t.generateKey(cards)
t.keyDic[key] = true
if level < 5 {
t.genTableNoGui(cards, level+1, jiang, feng)
}
cards[i] -= 1
cards[i+1] -= 1
cards[i+2] -= 1
}
//DD 将
if !jiang && totalCardCount <= 12 && cards[i] <= 2 {
cards[i] += 2
key := t.generateKey(cards)
if feng {
t.keyFengDic[key] = true
} else {
t.keyDic[key] = true
}
if level < 5 {
t.genTableNoGui(cards, level+1, true, feng)
}
cards[i] -= 2
}
}
}
func (t *Table) calTotalCardCount(cards []int) int {
count := 0
for _, v := range cards {
count += v
}
return count
}
func (t *Table) generateKey(cards []int) string {
key := ""
dic := []string{"0", "1", "2", "3", "4"}
for _, v := range cards {
key += dic[v]
}
return key
}
func (t *Table) save() {
//TODO
}
func (t *Table) genTableGui(feng bool) {
dic := t.keyDic
if feng {
dic = t.keyFengDic
}
//311200000 3 1 1 2 分表 3-1 211200000
for k := range dic {
cards := t.toNumberArray(k)
t.genGui(cards, 1, feng)
}
}
func (t *Table) toNumberArray(k string) []int {
cards := make([]int, len(k))
for i := 0; i < len(k); i++ {
card, _ := strconv.Atoi(k[i : i+1])
cards[i] = card
}
return cards
}
func (t *Table) genGui(cards []int, guiCount int, feng bool) {
for i := 0; i < 9; i++ {
if cards[i] == 0 {
//此位置没有牌
continue
}
cards[i]--
//需要判断其有没有 有就不处理 没有就添加到字典中
if !t.tryAdd(cards, guiCount, feng) {
cards[i]++
continue
}
if guiCount < 8 {
t.genGui(cards, guiCount+1, feng)
}
cards[i]++
}
}
func (t *Table) tryAdd(cards []int, guiCount int, feng bool) bool {
for i := 0; i < 9; i++ {
if cards[i] < 0 || cards[i] > 4 {
return false
}
}
key := t.generateKey(cards)
if feng {
if t.keyFengGuiDic[guiCount] == nil {
t.keyFengGuiDic[guiCount] = make(map[string]bool)
}
_, ok := t.keyFengGuiDic[guiCount][key]
if ok {
return false
}
t.keyFengGuiDic[guiCount][key] = true
return true
}
if t.keyGuiDic[guiCount] == nil {
t.keyGuiDic[guiCount] = make(map[string]bool)
}
_, ok := t.keyGuiDic[guiCount][key]
if ok {
return false
}
t.keyGuiDic[guiCount][key] = true
return true
}
func (t *Table) findCards(cards []int, guiCount int, feng bool) bool {
//先编码
key := t.generateKey(cards)
if guiCount > 0 {
if feng {
_, ok := t.keyFengGuiDic[guiCount][key]
if ok {
return true
}
} else {
_, ok := t.keyGuiDic[guiCount][key]
if ok {
return true
}
}
} else {
if feng {
_, ok := t.keyFengDic[key]
if ok {
return true
}
} else {
_, ok := t.keyDic[key]
if ok {
return true
}
}
}
return false
}