善用表驱动法
最近碰到个需求,计算游戏得分的规则,类似这样:
......
......
这些奖励规则没有什么规律,随着人数增多,就越发复杂了,并且业务人员可能随时改变这些规则。
显然,奖励规则可以采用策略模式,定义策略接口,根据游戏人数定义不同的规则,本质上就是利用动态的多态调用。可以想见,还是少不了复杂的case...when语句,以及繁多的代码。恰好最近读《unix编程艺术》和《代码大全2》,两者都提到一个结论:人类阅读复杂数据结构远比复杂的控制流程容易,或者说数据驱动开发是非常有价值的。《代码大全2》声称这个是表驱动法。因此,这个奖励系数的计算,能否转化成一个查表过程呢?注意到,在游戏中,名次是根据个人的积分在所有玩家中的排位来决定,大概会有这么个排序的玩家积分数组[100,50,3],这个数组表示3个玩家,第一名100分,第二名50分,第三名才3分。依据规则,第一名的奖励系数就是0.7,第二名就是0.3。我想到类似这样的数组其实都有个形式表示它们的内置结构,比如[100,50,3]数组的“结构”是"111",代表3个位置都有一个人。将"111"作为关键码去查表不就OK了?
将每个排好序的积分数组解码为这样的关键码,然后去查预先写好的奖励系数表,这个奖励系数表大概类似:
这样,我们所有的奖励规则就是维护这么一张hash表,这个表看起来复杂,其实完全可以自动生成,让业务人员来提供样例数据,解码样例数据并生成这个表是很简单的事情。积分数组的“解码”,我给一个Ruby版本:
游戏人数 |
第一名获得赌注 |
第二名获得赌注 |
第三名获得赌注 |
第四名获得赌注 |
二人 |
100% |
0% |
— |
— |
二人(出现2个第1名时) |
50% |
50% |
||
三人 |
70% |
30% |
0% |
— |
三人(出现3个第1名时) |
33.3333% |
33.3333% |
33.3333% |
|
三人(出现2个第1名时) |
50%×2 |
0% |
......
这些奖励规则没有什么规律,随着人数增多,就越发复杂了,并且业务人员可能随时改变这些规则。
显然,奖励规则可以采用策略模式,定义策略接口,根据游戏人数定义不同的规则,本质上就是利用动态的多态调用。可以想见,还是少不了复杂的case...when语句,以及繁多的代码。恰好最近读《unix编程艺术》和《代码大全2》,两者都提到一个结论:人类阅读复杂数据结构远比复杂的控制流程容易,或者说数据驱动开发是非常有价值的。《代码大全2》声称这个是表驱动法。因此,这个奖励系数的计算,能否转化成一个查表过程呢?注意到,在游戏中,名次是根据个人的积分在所有玩家中的排位来决定,大概会有这么个排序的玩家积分数组[100,50,3],这个数组表示3个玩家,第一名100分,第二名50分,第三名才3分。依据规则,第一名的奖励系数就是0.7,第二名就是0.3。我想到类似这样的数组其实都有个形式表示它们的内置结构,比如[100,50,3]数组的“结构”是"111",代表3个位置都有一个人。将"111"作为关键码去查表不就OK了?
将每个排好序的积分数组解码为这样的关键码,然后去查预先写好的奖励系数表,这个奖励系数表大概类似:
@@award_rate_hash={
:"2"=>{
:"11"=>{:"1"=>1,:"2"=>0},
:"20"=>{:"1"=>0.5,:"2"=>0.5}
},
:"3"=>{
:"111"=>{:"1"=>0.7,:"2"=>0.3,:"3"=>0},
:"300"=>{:"1"=>0.33},
:"201"=>{:"1"=>0.5,:"3"=>0},
:"120"=>{:"1"=>1,:"2"=>0}
},
:"4"=>{
:"1111"=>{:"1"=>0.65,:"2"=>0.30,:"3"=>0.05,:"4"=>0},
:"4000"=>{:"1"=>0.25},
:"3001"=>{:"1"=>0.33,:"4"=>0},
:"1300"=>{:"1"=>1,:"2"=>0},
:"2020"=>{:"1"=>0.5,:"3"=>0},
:"1201"=>{:"1"=>0.7,:"2"=>0.15,:"4"=>0},
:"1120"=>{:"1"=>0.7,:"2"=>0.3,:"3"=>0},
:"2011"=>{:"1"=>0.35,:"3"=>0.3,:"4"=>0}
}
}
一个三级hash表,首先根据玩家人数查到特定的系数表,比如要查3个玩家、积分数组是[100,50,3]的奖励系数表就是 @@award_rate_hash[:"3"],然后积分数组[100,50,3]解码为:"111",继续查,如此规则的奖励系数表就是@@award_rate_hash[:"3"][:"111"]——也就是 {:"1"=>0.7,:"2"=>0.3,:"3"=>0},那么查积分是100的玩家系数就是@@award_rate_hash[:"3"][:"111"][([100,50,3].index(100)+1).to_s.to_sym],也就是:"1"=>0.7。[100,50,3].index(100)+1就是积分100的玩家在数组中的名次(即1),也就是:"1"指向的结果0.7。其他玩家的查表过程与此类似,不细说了。:"2"=>{
:"11"=>{:"1"=>1,:"2"=>0},
:"20"=>{:"1"=>0.5,:"2"=>0.5}
},
:"3"=>{
:"111"=>{:"1"=>0.7,:"2"=>0.3,:"3"=>0},
:"300"=>{:"1"=>0.33},
:"201"=>{:"1"=>0.5,:"3"=>0},
:"120"=>{:"1"=>1,:"2"=>0}
},
:"4"=>{
:"1111"=>{:"1"=>0.65,:"2"=>0.30,:"3"=>0.05,:"4"=>0},
:"4000"=>{:"1"=>0.25},
:"3001"=>{:"1"=>0.33,:"4"=>0},
:"1300"=>{:"1"=>1,:"2"=>0},
:"2020"=>{:"1"=>0.5,:"3"=>0},
:"1201"=>{:"1"=>0.7,:"2"=>0.15,:"4"=>0},
:"1120"=>{:"1"=>0.7,:"2"=>0.3,:"3"=>0},
:"2011"=>{:"1"=>0.35,:"3"=>0.3,:"4"=>0}
}
}
这样,我们所有的奖励规则就是维护这么一张hash表,这个表看起来复杂,其实完全可以自动生成,让业务人员来提供样例数据,解码样例数据并生成这个表是很简单的事情。积分数组的“解码”,我给一个Ruby版本:
#解码数组为字符串关键码
def decode_array(array)
len=array.size
trace_list=[]
result=[]
len.times do |time|
result[time]=0
trace_list[time]=false
end
array.each_with_index do |item,index|
result[index]=count_times(array,trace_list,index,len)
end
return result.join('').to_sym
end
def count_times(array,trace_list,index,len)
item=array[index]
result=0
(index..len).each do |i|
if array[i]==item and !trace_list[i]
result+=1
trace_list[i]=true
end
end
return result
end
def decode_array(array)
len=array.size
trace_list=[]
result=[]
len.times do |time|
result[time]=0
trace_list[time]=false
end
array.each_with_index do |item,index|
result[index]=count_times(array,trace_list,index,len)
end
return result.join('').to_sym
end
def count_times(array,trace_list,index,len)
item=array[index]
result=0
(index..len).each do |i|
if array[i]==item and !trace_list[i]
result+=1
trace_list[i]=true
end
end
return result
end