很多有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顺子,还可以配置顺子长度,基本上搜索顺子的常见扑克里面通用的需求,这个都可以通过配置完成,不需要修改接口代码,使用也简单。
写一个只能自己项目使用的代码大多数人可以做到,但要写一个移植性高,复用性强,扩展性强,易用的接口是一件很难的事,不信你看看你公司的项目就明白了,需要不断重构,总结经验,多思考,才能写得出好的接口。 有更加好写法的朋友欢迎拍砖。