棋牌开发里面如何写好搜索顺子的算法

本文深入探讨并对比了几种不同的扑克游戏顺子搜索算法,分析了现有算法的不足,并提出了一种通用、易用且功能强大的搜索顺子算法。该算法不仅能够搜索单连顺子,还能搜索对连、三连等,且可通过配置适应不同游戏需求。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       很多有3年开发经验以上的朋友,可能不屑一顾,这有何难,不就是几行代码的事,从我所见到的所有版本中,目前还没有一个令我满意的版本,满分100分最多占到70,不合格的占90%。下面我就让大家看看我公司同事写的各个搜索顺子算法的版本吧。先看斗地主的一个搜索顺子的版本

-- 获取顺子推荐类型
local function GetSingleLineCardType(nCards,MaxCardInfo)
    local result = {}
    local bomb = {}
    local temp = GetCardNum(nCards)

    for i=1,#temp do
        if temp[i] == 4 then
            table.insert(bomb,i)
            temp[i] = 0
        end
    end
    
    local function GetResult(bHaveBomb)
        local num = MaxCardInfo.CardNum
        if #nCards >= num then
            if num ~= 0 then
                for i=MaxCardInfo.CardValue + 1,12 do
                    local have = true
                    if not bHaveBomb then
                        for j=i,i-num+1,-1 do
                            if temp[j] == 0 then
                                have = false
                                break
                            end
                        end
                    else
                        have = false
                        for j=i,i-num+1,-1 do
                            if temp[j] == 4 then
                                have = true
                            elseif temp[j] == 0 then
                                have = false
                                break
                            end
                        end
                    end

                    if have then
                        local ret = {}
                        for j=i,i-num+1,-1 do
                            for k,v in ipairs(nCards) do
                                if GetCard(v) == j then
                                    table.insert(ret,v)
                                    break
                                end
                            end
                        end
                        table.insert(result,ret)
                    end
                end
            else
                if bHaveBomb then
                    return
                end
                local i = 1
                while i < 9 do--如果i大于8就不可能匹到顺子了
                    if temp[i]==1 and temp[i+1]==1 and temp[i+2]==1 and temp[i+3]==1 and temp[i+4]==1 then
                        local n = i+4
                        for k=n+1,12 do
                            if temp[k]~=1 then
                                n = k - 1
                                break
                            end 
                        end
                        local ret = {}
                        for m=i,n do
                            for _,card in ipairs(nCards) do
                                if GetCard(card) == m then
                                    table.insert(ret,card)
                                    break
                                end
                            end
                        end
                        table.insert(result,ret)
                        i = n
                    end
                    i = i+1
                end
            end
        end
    end

    GetResult(false)

    for i=1,#bomb do
        local ret = {}
        for _,card in ipairs(nCards) do
            if GetCard(card) == bomb[i] then
                table.insert(ret,card)
                if #ret == 4 then
                    break
                end
            end
        end
        table.insert(result,ret)
    end

    if temp[14]==1 and temp[15] == 1 then
        table.insert(result,{0x5E,0x5F})
    end

    temp = GetCardNum(nCards)
    GetResult(true)

    return result
end
            

再看这个接口使用样例

    local MaxCardInfo = {CardNum = 0,CardType = CARDTYPE.CT_SINGLE_LINE,CardValue = 0} 
    local res = GetSingleLineCardType(tempcard,MaxCardInfo)

         罪名:

        第1点:一个算法接口要了解外部的数据结构就证明这个接口不具备通用性,同时接口一旦要做出修改相关数据结构的代码,那么所有使用的这个接口的定义的数据结构部分代码都要修改,可见你的算法内部要了解外部的数据结构,那它就很难维护了;

        第2点:接口内部居然返回了炸弹结果,一个函数只做一件事情,这个也没做到,这样外部对于函数返回的结果处理起来也是困难的,容易犯错的,要理解接口内部构造。

        第3点:函数还要区分炸弹的处理,就算你需要在结果中排除炸弹,那么也应该是另外一个功能了,排除炸弹的结果可以在函数返回来后再剔除处理。否则其他模块需要包含炸弹的结果呢?GetSingleLineCardType这个命名就应该是做搜索顺子的事,其他逻辑不应该穿插进来,否则任何一个模块要使用这种接口,还要阅读内部函数才能正确使用,那样太累了,

       第4点:搜索手牌中的顺子这个是一个通用算法,不应该定义成local,否则别的模块无法使用,这个游戏要改成别的游戏时,这部分代码还得做出修改,这就很不稳定了。

       第5点:该算法不能搜索从A到5的顺子,唉,如果十三张游戏要用这个算法还得做出修改,太难了...。

      第6点:

 for i=1,#temp do
        if temp[i] == 4 then
            table.insert(bomb,i)
            temp[i] = 0
        end
    end

     两副牌的扑克怎么办?

      眼尖的朋友应该还能看出很多毛病,逻辑冗余,硬编码等等

      你很难相信这是工作了10几年的人写出来的代码质量,唉,典型的一年经验用了10年的人,不是程序员大龄了可怕,可怕的是年纪大了,还停留在幼儿水平。这行业混饭出的人太多了,这样的接口只能在这个他自己的项目中使用,别人改还容易出bug,从复用性,易用,容错性,可扩展方面来说,这个函数根本不合格,可公司就是大量充斥着这种代码。下面再来看一个十三张的搜索顺子算法样例

function BaseMyPoker:GetAllStraights(len, num)
	local straits = {}
	for i=15-len, 1, -1 do
		local strait = self:GetStraight(i,i+len-1, num)
		if strait then
			table.insert(straits, strait)
		end
	end
	return straits
end
function BaseMyPoker:GetStraight(startPoint,endPoint,num)
    if startPoint < 1 or startPoint > 14 then
		Log('Warnning: In GetStraight(), startPoint not in 1 - 14.')
		return nil
	end

	if endPoint < 1 or endPoint > 14 then
		Log('Warnning: In GetStraight(), endPoint not in 1 - 14.')
		return nil
	end

    local retTab = {}

	for i = startPoint, endPoint do
        local tmp = self:GetSamePointPokers(i, num)
        
        if #tmp > 0 then
        	for j = 1, #tmp do
        	    table.insert(retTab, tmp[j])
        	end
        else
        	return nil
        end
    end

    return retTab 
end

FuncName:     GetSZ
description:  在手牌中求顺子
参数:        tailType    - 尾道牌型 (每次先求尾道)
              MyCardsObj  - 手牌 BaseMycards 对象
返回值:      取到的顺子数据信息
 ]]--------------------------------------------------------------------------------------------------
function SszCardLogic.GetSZ(self,tailType, MyCardsObj)
    local oneSix = MyCardsObj.CouterOneSix

    local TCCardsInfo = {}
    TCCardsInfo.type = BaseCardType.SZ
    TCCardsInfo.funcID = GetFuncID.GetSZ

    local straits = MyCardsObj:GetAllStraights(5, 1)

    if tailType  then             -- 如果是中道求,那只要求到就直接返回
        if #straits > 0 then
            TCCardsInfo.cards = straits[1]
            return TCCardsInfo
        end
    end

    --判断是否能组成两个顺子,所在的顺子中找没有同一张牌的一组
    for i=1, #straits - 1 do
        for j=i+1, #straits do
            if not MyCardsObj:HasSamePoker(straits[i], straits[j]) then
                TCCardsInfo.cards = straits[1]
                return TCCardsInfo
            end
        end
    end

    if #straits > 0 then
        TCCardsInfo.cards = straits[1]
        return TCCardsInfo
    end 
    return nil
end

          这么长的代码有点吓到看官老爷们了,没错,就为了判断一个顺子而已,使用者无从下手,假如我手上有一组数据{0x15,0x16,0x17,0x18,0x19,0x1A},这个你必须了解他几个类才能使用,否则你还不知道怎么使用他的接口,不就搜索个顺子,全是脱裤子放屁的设计,搞得这么复杂,唉。这个看起来,该有的顺子都有了从1-5,10-A都有,可是呢有的游戏不需要A-5,或者不需要带A的顺子呢?离我想要的版本还差很远,好吧,说了那么多,到底怎么设计能做到通用,易用,功能还足够强大呢?放上我自己写的版本:

function GameLogic:SearchLineCardType(cbHandCardData, cbHandCardCount, cbReferCard, cbBlockCount, cbLineCount)
    -- //设置结果
    -- //定义变量
    local cbResultCount = 1
    if (cbLineCount == 0) then
        if (cbBlockCount == 1) then
            cbLineCount = DEF.SHUNZI_NUM_MIN
        elseif (cbBlockCount == 2) then
            cbLineCount = DEF.G_DOUBLE_LINE_NUM
        else
            cbLineCount = 2
        end
    else
        cbLineCount = cbLineCount
    end

    local pSearchCardResult = self:GetSearchCardResult()

    local cbReferIndex = DEF.SHUNZI_START_ZHI
    if (cbReferCard ~= 0) then
        --cbReferIndex = self:GetCardLogicValue(cbReferCard) - cbLineCount + 2
        cbReferIndex = self:GetCardLogicValue(cbReferCard) +1
    end
    -- //超过A
    if (cbReferIndex + cbLineCount-1 > DEF.SHUNZI_END_ZHI) then
        return pSearchCardResult
    end
    -- //超过A
    if (cbLineCount > DEF.SHUNZI_NUM_MAX) then
        return pSearchCardResult
    end

    -- //长度判断
    if (cbHandCardCount < cbLineCount) then
        return pSearchCardResult
    end

    -- //构造扑克
    local cbCardData = mytools.clone(cbHandCardData)
    local cbCardCount = cbHandCardCount
    -- //排列扑克
    self:SortCardList(cbCardData, cbCardCount)

    -- //分析扑克
    local Distributing = self:AnalysebDistributing(cbCardData, cbCardCount)
    --把A添加在后面,完成方便找到,10,J,Q,K,A这种顺子
    Distributing.cbDistributing[14] = mytools.clone(Distributing.cbDistributing[1])
    -- //搜索顺子
    local cbTmpLinkCount = 0
    for cbValueIndex = cbReferIndex, DEF.SHUNZI_END_ZHI do
        -- //继续判断
        if (Distributing.cbDistributing[cbValueIndex][cbIndexCount] < cbBlockCount) then
            if (cbTmpLinkCount < cbLineCount) then
                cbTmpLinkCount = 0
            else
                cbValueIndex = cbValueIndex - 1
            end
        else
            cbTmpLinkCount = cbTmpLinkCount + 1
        end

        if (cbTmpLinkCount ~= 0 and cbTmpLinkCount >= cbLineCount) then
            -- //复制扑克
            local cbCount = 0
            for cbIndex = cbValueIndex + 1 - cbTmpLinkCount, cbValueIndex do
                local cbTmpCount = 0
                for cbColorIndex = 1, 4 do
                    for cbColorCount = 1, Distributing.cbDistributing[cbIndex][5 - cbColorIndex] do
                        cbCount = cbCount + 1
                        local card = 0
                        --末位A的时候要特殊处理一下
                        if cbIndex == DEF.SHUNZI_END_ZHI then
                            card = self:MakeCardData(1, 5 - cbColorIndex)
                        else
                            card = self:MakeCardData(cbIndex, 5 - cbColorIndex)
                        end
                        pSearchCardResult.cbResultCard[cbResultCount][cbCount] = card
                        cbTmpCount = cbTmpCount + 1
                        if (cbTmpCount == cbBlockCount) then
                            break
                        end
                    end
                    if (cbTmpCount == cbBlockCount) then
                        break
                    end
                end
            end
            -- //设置变量
            pSearchCardResult.cbCardCount[cbResultCount] = cbCount
            cbResultCount = cbResultCount + 1
            if (cbLineCount ~= 0) then
                cbTmpLinkCount = cbTmpLinkCount - 1
            else
                cbTmpLinkCount = 0
            end
        end
    end
    for  i = 1, cbResultCount-1 do
		self:SortCardList(pSearchCardResult.cbResultCard[i], pSearchCardResult.cbCardCount[i]);
	end
    if (pSearchCardResult) then
        pSearchCardResult.cbSearchCount = cbResultCount - 1
    end
    return pSearchCardResult
end




使用示范

 local tmpSearchCardResult =
        self:SearchLineCardType(cbCardData, cbCardCount, cbTmpTurnCard[1], cbBlockCount, cbLineCount)

这个接口使用时只需要传入数组,和数组大小,要压的牌,不是压牌的情况填0就可以了,不仅可以搜索单连顺子,还可以搜索对连,三连。只要在定义处修改以下四个值:DEF.SHUNZI_NUM_MIN,DEF.SHUNZI_NUM_MAX,DEF.SHUNZI_START_ZHI,DEF.SHUNZI_END_ZHI,还可以配置是否需要A顺子,还可以配置顺子长度,基本上搜索顺子的常见扑克里面通用的需求,这个都可以通过配置完成,不需要修改接口代码,使用也简单。

       写一个只能自己项目使用的代码大多数人可以做到,但要写一个移植性高,复用性强,扩展性强,易用的接口是一件很难的事,不信你看看你公司的项目就明白了,需要不断重构,总结经验,多思考,才能写得出好的接口。  有更加好写法的朋友欢迎拍砖。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值