go游戏后端开发27:胡牌逻辑

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

拿到牌后,将其放入玩家的牌堆中。接下来,需要判断玩家可以进行哪些操作。这些操作包括多种内容,而核心判断之一便是胡牌。胡牌是玩家能否获胜的关键,因此首先要明确胡牌的条件。

胡牌的判断基于玩家的手牌。手牌是指玩家当前持有的牌,新摸到的牌已经放入手牌中,因此在判断胡牌时,不能再次将新牌加入手牌进行判断。但后续玩家出牌后,其他玩家可能会进行“吃”牌操作,所以新牌的信息仍需保留,只是在当前判断中将其标记为不参与计算。

具体操作时,先编写一个方法来判断是否能胡牌。胡牌的逻辑较为复杂,不能直接写在代码中,而是需要设计一个专门的算法。这个算法的核心是判断玩家的手牌是否符合胡牌的规则。

胡牌的规则可以用一个简单的公式表示:n 组顺子(如 123)加上 m 组刻子(如 222),再加上一个对子。这里的 n 和 m 可以是任意非负整数,只要满足总数为 14 张牌即可。例如,玩家手中有 1A、2A、3A(一组顺子),4A、4A、4A(一组刻子),再加上一个对子,如 5C、5C,就满足胡牌条件。

为了实现这一逻辑,首先需要对牌进行编码。假设牌的种类为 a、b、c 等,每种牌有 1 到 9 的编号,每种编号的牌有 4 张。可以将手牌表示为一个数组,数组的每个位置代表一种牌的数量。例如,[1, 1, 1, 0, 0, 0, 0, 0, 0] 表示玩家手中有 1A、2A、3A 各一张,其余牌没有。

接下来,生成所有可能的胡牌编码。由于红中麻将中有鬼牌(红中),鬼牌可以代替任何一种牌,因此需要考虑鬼牌的情况。假设最多有 8 张鬼牌,需要生成从 0 到 8 张鬼牌的所有可能胡牌编码,并将这些编码存储在内存中,形成一个“字典表”。这样,在判断胡牌时,只需将玩家的手牌编码与字典表中的编码进行匹配,如果找到匹配项,则玩家可以胡牌。

生成字典表的过程如下:

  1. 定义一个字典表,用于存储所有可能的胡牌编码。

  2. 从无鬼牌的情况开始,列举所有可能的胡牌组合。例如,对于非字牌(如 a、b、c),可以有顺子(如 123)和刻子(如 222)的组合;对于字牌(如 d),只有刻子的组合。

  3. 考虑鬼牌的情况。如果有 1 张鬼牌,可以将鬼牌替换为任意一张牌,生成新的胡牌编码;如果有 2 张鬼牌,则可以替换两张牌,以此类推,直到 8 张鬼牌。

  4. 将所有生成的胡牌编码存储在字典表中。

在判断胡牌时,先将玩家的手牌进行编码,然后与字典表中的编码进行匹配。如果找到匹配项,则玩家可以胡牌;否则,玩家不能胡牌。

这种算法的优点是通过“空间换时间”的方式,提前生成所有可能的胡牌编码,避免了在判断时进行复杂的计算,提高了判断效率。

package alg

import (
	"fmt"
	"game/component/mj/mp"
)

var table = NewTable()

type HuLogic struct {
}

func NewHuLogic() *HuLogic {
	return &HuLogic{}
}

// CheckHu cards当前需要判断的玩家的牌 guiList 鬼牌 代替任何一种牌(红中) card 拿牌/吃牌
// A 万(1-9 *4) B 筒 (1-9 *4) C 条(1-9 *4) D 风 (东南西北 白板 发财等等 1-9 * 4)
// A B C D(红中) 36+36+36+红中数量
// 胡牌的逻辑=N*连子+M*刻子+1*将  连子 = 1,2,3 刻子=2,2,2 将=3,3(对子)
// 13 + 1= 14 在这个牌的基础上去判断  1A 2A 3A 4A 4A 4A 6A 6A 6A 2B 3B 4B 5C 5C
// 算法:1. 编码的操作 1-9A  000000000 每一个位置代表牌有几个 1A 2A 3A 4A 4A 4A 6A 6A 6A (111303000)
// 2. 生成胡牌的信息:111303000编码 代表此牌胡了
// 这样类似的胡的编码非常多,我们把这种很多种可能 叫做穷举法,需要将所有的可能的胡牌的排列 计算出来,转换成编码
// 1A2A5A5A 110020000 如果要胡 0鬼 3A 鬼1 无将 胡3A5A都行 有将 直接胡 我们需要计算这种能胡的所有可能性
// 无鬼 n可能 鬼1 n种可能 鬼2 n种可能 .... 8个 如果有8个鬼 直接胡的 0-7
// 3. 已经把胡牌所有的可能计算出来了,然后将其加载进内存,空间换时间,进行胡牌检测的时候,直接进行匹配即可,查表法
// 1A 2A 3A 4A 4A 4A 6A 6A 6A 2B 3B 4B 5C 5C  111303000  011100000 000020000  = hu
// 先去生成表(所有胡牌的可能) 8张表   feng 8张
func (h *HuLogic) CheckHu(cardInHandList []mp.CardID, guiList []mp.CardID, cardOngoing mp.CardID) bool {
	// 复制 cardInHandList
	cardList := make([]mp.CardID, len(cardInHandList))
	copy(cardList, cardInHandList)
	if cardOngoing > 0 && cardOngoing < 36 {
		cardList = append(cardList, cardOngoing)
	}
	//guiList []{Zhong}
	return h.isHu(cardList, guiList)
}

func (h *HuLogic) isHu(cardList []mp.CardID, guiList []mp.CardID) bool {
	// A B C D
	cards := [][]int{
		{0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0},
		{0, 0, 0, 0, 0, 0, 0, 0, 0},
	}
	guiCount := 0
	for _, card := range cardList {
		if IndexOf(guiList, card) != -1 {
			guiCount++
		} else {
			i := int(card) / 10
			j := int(card)%10 - 1
			cards[i][j]++
		}
	}
	fmt.Printf("hu cards:%v \n", cards)
	cardData := &CardData{
		guiCount: guiCount,
		jiang:    false,
	}
	for i := 0; i < 4; i++ {
		feng := i == 3
		cardData.cards = cards[i]
		if !h.checkCards(cardData, 0, feng) {
			return false
		}
	}
	if !cardData.jiang && cardData.guiCount%3 == 2 {
		return true
	}
	if cardData.jiang && cardData.guiCount%3 == 0 {
		return true
	}
	return false
}

func (h *HuLogic) checkCards(data *CardData, guiCount int, feng bool) bool {
	totalCardCount := table.calTotalCardCount(data.cards)
	if totalCardCount == 0 {
		return true
	}
	//查表 如果表没有 那么就加一个鬼
	if !table.findCards(data.cards, guiCount, feng) {
		if guiCount < data.guiCount {
			//递归 每次鬼+1
			return h.checkCards(data, guiCount+1, feng)
		} else {
			return false
		}
	} else {
		//将只能有一个
		if (totalCardCount+guiCount)%3 == 2 {
			if !data.jiang {
				data.jiang = true
			} else if guiCount < data.guiCount {
				//需要再次尝试+1鬼 看是否胡 只能有一个将
				return h.checkCards(data, guiCount+1, feng)
			} else {
				return false
			}
		}
		data.guiCount = data.guiCount - guiCount
	}
	return true
}

type CardData struct {
	cards    []int
	guiCount int
	jiang    bool
}

func IndexOf[T mp.CardID](list []T, v T) int {
	for index, value := range list {
		if value == v {
			return index
		}
	}
	return -1
}


网站公告

今日签到

点亮在社区的每一天
去签到