天天看點

善用表驅動法

  最近碰到個需求,計算遊戲得分的規則,類似這樣:

遊戲人數

第一名獲得賭注

第二名獲得賭注

第三名獲得賭注

第四名獲得賭注

二人

100%

0%

二人(出現2個第1名時)

50%

三人

70%

30%

三人(出現3個第1名時)

33.3333%

三人(出現2個第1名時)

50%×2

......

    這些獎勵規則沒有什麼規律,随着人數增多,就越發複雜了,并且業務人員可能随時改變這些規則。

    顯然,獎勵規則可以采用政策模式,定義政策接口,根據遊戲人數定義不同的規則,本質上就是利用動态的多态調用。可以想見,還是少不了複雜的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。其他玩家的查表過程與此類似,不細說了。

    這樣,我們所有的獎勵規則就是維護這麼一張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)

    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

    return result

文章轉自莊周夢蝶  ,原文釋出時間2008-04-17