拿到牌后,将其放入玩家的牌堆中。接下来,需要判断玩家可以进行哪些操作。这些操作包括多种内容,而核心判断之一便是胡牌。胡牌是玩家能否获胜的关键,因此首先要明确胡牌的条件。
胡牌的判断基于玩家的手牌。手牌是指玩家当前持有的牌,新摸到的牌已经放入手牌中,因此在判断胡牌时,不能再次将新牌加入手牌进行判断。但后续玩家出牌后,其他玩家可能会进行“吃”牌操作,所以新牌的信息仍需保留,只是在当前判断中将其标记为不参与计算。
具体操作时,先编写一个方法来判断是否能胡牌。胡牌的逻辑较为复杂,不能直接写在代码中,而是需要设计一个专门的算法。这个算法的核心是判断玩家的手牌是否符合胡牌的规则。
胡牌的规则可以用一个简单的公式表示: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 张鬼牌的所有可能胡牌编码,并将这些编码存储在内存中,形成一个“字典表”。这样,在判断胡牌时,只需将玩家的手牌编码与字典表中的编码进行匹配,如果找到匹配项,则玩家可以胡牌。
生成字典表的过程如下:
定义一个字典表,用于存储所有可能的胡牌编码。
从无鬼牌的情况开始,列举所有可能的胡牌组合。例如,对于非字牌(如 a、b、c),可以有顺子(如 123)和刻子(如 222)的组合;对于字牌(如 d),只有刻子的组合。
考虑鬼牌的情况。如果有 1 张鬼牌,可以将鬼牌替换为任意一张牌,生成新的胡牌编码;如果有 2 张鬼牌,则可以替换两张牌,以此类推,直到 8 张鬼牌。
将所有生成的胡牌编码存储在字典表中。
在判断胡牌时,先将玩家的手牌进行编码,然后与字典表中的编码进行匹配。如果找到匹配项,则玩家可以胡牌;否则,玩家不能胡牌。
这种算法的优点是通过“空间换时间”的方式,提前生成所有可能的胡牌编码,避免了在判断时进行复杂的计算,提高了判断效率。
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
}