go游戏后端开发28:胡牌算法实现

发布于:2025-04-09 ⋅ 阅读:(33) ⋅ 点赞:(0)

红中麻将胡牌逻辑开发文档

一、字牌与封牌的字典表构建

在开发红中麻将胡牌逻辑时,首先需要构建非字牌和封牌的字典表。在构建双层表时,需进行判断:若牌为风牌且索引 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
}