简单兑换过程的设计和数据结构讨论
一 需求简要描述
游戏中物品道具,同类不同物兑换过程。
道具中,有某种具有不同等级的物品,比如绿色宝石,蓝色宝石,紫色宝石,金色宝石,红色宝石,等等。它们之间具有一定的兑换关系,即,绿色宝石40个可以兑换一个蓝色宝石,蓝色宝石20个可以换一个紫色宝石,紫色宝石5个可以换一个金色宝石等,宝石兑换过程不能逆向兑换。绿色=》蓝色=》紫色=》金色=》红色是等级递推关系。要求,在兑换过程中可以自动计算跨越等级的宝石之间的兑换过程。比如:足够绿色也可以换紫色宝石。
40绿色 =》 蓝色
20蓝色 =》 紫色
…………
800绿色 =》 紫色
…………
在游戏中,不同颜色的宝石具有不同的作用。比如,绿色宝石用作镶嵌护手装备;蓝色宝石用作镶嵌靴子等等。
我们的问题就是实现一个兑换过程,能够通过计算宝石之间的数据对应关系,扣除绿色宝石并加成蓝色宝石。
二 分析需求中的可变因素
相信很容易就能得到不同颜色前面的那个数字会是一个可变的因素,我们会将其作为宏替换在程序中。另外不能逆向递推等级的兑换宝石,我们可以讲其设置为一个线性结构,是最节省空间,并且容易计算的。
三 实现过程
(近期一直在使用Lua,所以使用Lua实现)
遵循一定的编程规范。首先,设计出程序需要的数据结构;其次,根据数据结构得到程序的结构。
我们需要一个线性结构,描述逐级兑换过程的数据:
Exchange_table = {
[“green”] = 40,
[“blue”] = 20,
[“violet”] = 5,
[“golden”] = 2,
[“red”] = 1,
}
记录不同等级的宝石兑换到上一个等级的兑换数量,这样,数字需要修改时,不需要改变程序中逻辑。
这里记录的是单位兑换数量,兑换一个上层宝石需要的下层宝石数量,即如果给出80个绿色宝石,需要得到2个蓝色宝石。
我们为了便于比较和计算,将宝石的颜色值设置为id,记录到另一个表里。
Stones = { 1 , 2 , 3, 4 , 5 }
--对应于:1:绿宝石;2:蓝宝石;3:紫宝石…………
Exchange = { 40 , 20 , 5 , 2 , 1}
--同上面的兑换数量值,跨越等级兑换的时候,需要自动计算扣除宝石数量,以及加成宝石数量
函数实现兑换过程:
----------------------------------------------------------------------------------
-- 计算宝石兑换的单位数量:一个toid需要多少fromid
-- 参数:fromid , toid
-- 返回值:from_num_need 或者false是逆向兑换 或者1是和自己换
function get_from_count( fromid , toid )
local from_num_need = 1
if fromid > toid then
return false --不可以逆向兑换
elseif fromid == toid then
return from_num_need
end
for i = fromid , toid-1 , 1 do
--由于for循环 exp1 等于 exp2 也要执行一次body,所以toid-1
from_num_need = from_num_need * Exchange[i]
end
return from_num_need
end
----------------------------------------------------------------------------------
-- 计算可以兑换几个高级宝石
-- 参数:from_num_need(兑换单位) , from_num(提供数量)
-- 返回值:第一个:0:能兑换0个(提供数量不够);1:能兑换一个;2:能兑换2个......
-- 第二个:余数,表示提供的低等级兑换数量大于兑换需要的低等级数量。
function get_num_toid( from_num_need , from_num )
if from_num_need > from_num then
return 0
end
return math.floor( from_num / from_num_need ) , from_num % from_num_need
end
那么接下来就按部就班,根据得到的能够兑换高级宝石的数量,以及消耗低级宝石数量。
举例:(1607个绿色宝石,换紫色宝石)==》换2个紫色宝石
消耗低级宝石数量: get_num_toid( get_from_count( 1, 3 ) , 1607 ) * get_from_count( 1, 3 )
加成高级宝石数量: get_num_toid( get_from_count( 1, 3 ) , 1607 )
将需要兑换的高级宝石数量和低级宝石数量的计算过程:第一步得到单位兑换数量,第二步计算实际兑换数量。
四 反思实现过程
目前看来过程并没有什么大的漏洞和设计偏差,差不多是按照需求完成。然而,这段代码中间有一个死穴:首先需求中明确要求不能逆向兑换,所以代码设计也遵从不能逆向兑换,那么有一天,产品策划跑来说:那个,为了使我们的玩家更加自由,我们决定将规则改为双向自由兑换,并且升级兑换和降级兑换的数据最好使用表格(CSV)来控制。除了一顿抱怨和争吵之后,我们反观自己,这段程序的灵魂——数据结构部分,设计的是否足够灵活,能够完美适应变化。很明显不能适应变化。
这个Exchange表本身是具有方向的表,即:下标1的元素记录的是兑换到下标2表示的宝石需要花费的数量,下标2记录的是下标3宝石的数量,以此类推。那么什么地方记录下标2记录下标1宝石花费的数量?所以,问题就在这里,Exchange表是单向的。
需要设计出,更加灵活的数据结构,可能更应该关心的是我们要做的是什么,我们要做的是:兑换,换句话说,就是 扣除一种东西,加成另一种东西。
那么,我们需要的可能不是一个线性的数据结构。那么,树和图,谁更合适呢?个人认为是图,因为,树是节点选择很多,但方向性依然存在。
得到,使用图结构描述这个问题,那么再次改写上面程序。抽象一下问题:扣除谁,扣除多少,加成谁,加成多少。那么图的节点from_id和to_id就是这句话里面的2个“谁”,这两个节点之间的边上的权值就是2个“多少”。
五 再次实现过程
Stones = { 1 , 2 , 3, 4 , 5 }
Exchange_2 = {
[ 1 ] = { [ 2 ] = { 40 , 1 }, [ 3 ] = { 800 , 1 }, [4] = { 4000 , 1}, [5] = {8000 , 1} },
[ 2 ]= { [ 1 ] = { 1 , 40 }, [ 3 ] = { 20 , 1 }, [4] = { 100 , 1}, [5] = {200 , 1} },
[ 3 ]= { [1] …………},
…………
}
上面的数据内容是用Lua中的table实现的,那么,可以使用CSV以表格形式填写之后,专门写一个功能加载为Lua的table类型,方便程序外部修改数值。
根据上面的数据结构,我们的代码,可能更加清晰并且不易变动了。
再次实现:
----------------------------------------------------------------------------------
-- 根据表格中数据返回本次兑换的单位数据
-- 参数: from_id , to_id , provide , flag
-- 返回值: from_dec_num , to_add_num 若为0,则表示上行provide不够单位交换
function get_add_dec_num( from_id , to_id , provide , flag)
if not Exchange_2 then return end
-- 表里没有本次兑换的数据
if not Exchange_2 [from_id][to_id] then return end
--得到本次兑换 的 单位比率
local from_num = Exchange_2 [from_id][to_id].from_num
local to_num = Exchange_2 [from_id][to_id].to_num
--计算本次兑换 实际倍数
local times = 0
if flag == 'from' then -- provide描述 from_num
times = math.floor( provide / from_num )
elseif flag == 'to' then -- provide描述 to_num
times = math.floor( provide / to_num )
end
return (from_num * times) , (to_num * times)
end
调用该函数得到的数字就是一次兑换需要扣除的“多少”,和加成的“多少”。
举例:(1607个绿色宝石,换紫色宝石)==》换2个紫色宝石
local dec_num,add_num = get_add_dec_num( 1 , 3 , 1607 , ‘from’)
到这里,这个需求想要描述的问题,也已经解决了,应对本问题可能出现的变更也考虑到了,兑换率(单位兑换数量)在表中变化即可。当产品策划需要变化时,比如:正向兑换和逆向兑换的数据想要不一致,就可以很舒服的说:自己改表去。
六 最后总结
- 首先,需要说的是,程序的设计要和数据结构的设计结合起来。
- 其次,分析需求的时候,着重关注可变因素,更重要的是,虽然明说的要求,也要再三思量,不要绝对作为硬规则。
- 最后,关注需求本质,而不是需求的描述,主动应对需求变更。
==========================================================================================================