叫我大掌櫃這款手遊中,其中有個活動是古林狩獵(每日中午12:00-14:00)
它的玩法是這樣,你有N隻戰力數值不一的門客,你要挑戰古林裡的野獸
而這些野獸的戰力會由小到大的出現,15萬起跳到幾億
基本上一隻門客只能出場一次(有特殊能力的門客除外)
情況一:門客A數值100萬,目標野獸100萬
那一換一的情況下,很剛好沒什麼問題
情況二:門客A數值100萬,目標野獸15萬
雖然結果一樣是1換1,但你同時也浪費了85萬的戰力,如果可以縮小每個對戰的差值,就可以將戰力做最大化輸出。
我目前作法都是在活動當下,心算或者按計算機去加總
活動限時兩小時內打完,但因為門客的戰力會浮動(因資源投入而提高)
所以每天活動可能都要重新計算門客組合去打一隻野獸,這讓我覺得好花時間
於是決定動手寫個程式來跑這個數值加總>=目標值的門客排列組合,省時多了,幾秒鐘就搞定組合
讓我們一步一步做看看
一、對門客戰力素質做出所有的組合(不重複)
我要做組合的門客總數是26,只挑出1-3個門客為一組的組合
所以總共會有2,951個組合,這個數量你要手寫窮舉一個一個列出來,可想而知要耗費多少時間....
還是交給專業的電腦來處理就好,公式如下
程式碼如下
- from itertools import combinations
- #門客戰力陣列
- nums=[6291,1698,756,661,631.9,615.2,535.7,528,281.6,
- 253.8,240.1,236.9,230.3,216.5,216,209,206.7,
- 197.9,196.8,184.7,170.3,168,167,163.1,162.3,160.1]
- result=[]
- #使用Combinations函式將nums陣列裡的值以i個個數輸出組合,並加到res陣列中
- #filter(None,list),是為了將空陣列過濾掉
- for i in range(4):
- result +=list(filter(None,combinations(nums,i)))
- print("26選1-3門客為一組的組合個數為: {}\n".format(len(result)))
- print("前30筆組合如下:")
- for n in range(30):
- print(result[n])
程式執行結果如下圖
二、找出總合大於等於目標野獸戰力的組合
取得所有1-3位門客一組的組合後,我們透過比對目標值將不符合條件的組合去掉。
假設我現在要打一隻1200萬的野獸,那我們要將所有組合做數值加總,並挑出總合大於等於1200萬的組合,再從中挑出差值最小的。
程式碼如下
- from itertools import combinations
- #野獸目標值
- goal=1200
- #門客戰力陣列
- nums=[6291,1698,756,661,631.9,615.2,535.7,528,281.6,
- 253.8,240.1,236.9,230.3,216.5,216,209,206.7,
- 197.9,196.8,184.7,170.3,168,167,163.1,162.3,160.1]
- result=[]
- #使用Combinations函式將nums陣列裡的值以i個個數輸出組合,並加到res陣列中
- #filter(None,list),是為了將空陣列過濾掉
- for i in range(4):
- result +=list(filter(None,combinations(nums,i)))
- print("1.26選1-3門客為一組的組合個數為: {}\n".format(len(result)))
- #只留下總和>=1200萬以上的組合
- result=[x for x in result if sum(x) >= goal]
- print("2.總和大於1200萬以上的組合個數為: {}\n".format(len(result)))
- print("3.前10筆組合如下(未排序):")
- for n in range(10):
- print(result[n])
- #以總和數值做排序(小->大)
- result.sort(key=sum)
- print("\n4.前10筆組合如下(以總合值排序後):")
- for n in range(10):
- print(result[n])
- print("\n5.差值最小的組合為: {} \t\t總和: {} \t差值: {} \n".format(result[0], round(sum(result[0]),1), round(float(sum(result[0]))-float(goal),1)))
程式執行結果如下圖
三、加入迴圈得到N隻野獸的對戰組合
最後我們將前面的步驟放到副程式裡,在主程式寫一個For迴圈讀入每一隻對戰野獸的目標值,藉以得到對戰組合
最終版程式碼如下,線上版點這
- from itertools import combinations
- #野獸目標值陣列
- targets=[720,900,1000,1200,1450,1550,2100,2450,2650,3300,4000]
- #門客戰力陣列
- nums=[14640,2045,1663,1485,1485,1302,1057,733.5,683.3,646.2,
- 493.9,444.5,389.4,384.7,384.3,366.9,358.1,357.3,357,
- 350.7,342.8,338.5,338.4,338,331.3,328.5]
- #這裡是副程式,gettop1方法,輸入參數為amount(組合個數)與goal(總合目標值)
- def gettop1(amount,goal):
- result=[]
- for i in range(amount):
- result +=list(filter(None,combinations(nums,i)))
- result=[x for x in result if sum(x) >= goal]
- result.sort(key=sum)
- if (len(result) > 0):
- return result[0]
- return None
- #總差值
- deviations=0
- #這裡是主程式
- for target in targets:
- #呼叫副程式gettop1方法,丟入組合個數及目標值
- #組合個數丟入4,在程式中代表0到3,所以會是C26取3 + C26取2 + C26取1 + C26取0
- tmp = gettop1(5,target)
- print('------------目標野獸數值: {} 萬 ------------'.format(target))
- if (tmp is None):
- print("沒有找到結果。\n")
- else:
- #差值
- deviation = round(float(sum(tmp))-float(target),1)
- #加總差值
- deviations += deviation
- print("{} \t\t總合: {} \t差值: {} \n".format(tmp, round(sum(tmp),1), deviation))
- #使用過的門客,從陣列中移除
- for element in tmp:
- nums.remove(element)
- print("總差值:{}".format(deviations))
程式執行結果如下圖
做到這裡基本上,對戰組合就都有了,可以根據戰力去找到對應的門客。
四、加入外迴圈比較不同組合數,取得差值最佳解。
使用了一段時間後,發現偶爾需要調整組合數來比較差值最小化的解,等於用最少戰力達到最大化輸出
什麼意思呢,目前發現最佳解大概落在兩個組合
所以我增加一個外迴圈,來切換組合個數做比較。
最後給出最佳解。
更新後的程式碼如下,線上版點這
- from itertools import combinations
- targets=[]
- nums=[]
- resultmsg=""
- olddeviation=0
- #這裡是副程式,gettop1方法,輸入參數為amount(組合個數)與goal(總合目標值)
- def gettop1(amount,goal):
- result=[]
- for i in range(amount):
- result +=list(filter(None,combinations(nums,i)))
- result=[x for x in result if sum(x) >= goal]
- result.sort(key=sum)
- if (len(result) > 0):
- return result[0]
- return None
- def init():
- global targets
- global nums
- #野獸目標值陣列
- targets=[585,720,900,1000,1200,1450,1550,2100,2450,2650,3300,4000]
- #門客戰力陣列
- nums=[15104,2393,1882,1735,1524,1229,863.5,791.7,725.1,543.6,463.9,451.2,403,392.5,378.2,375.4,371.9,368.1,365,364.5,359.9,354.4,351.7,342.9,333.5]
- #這裡是主程式
- for i in range(4,6):
- tmpmsg=""
- init() #初始化陣列值
- deviations=0 #總差值
- for target in targets:
- #呼叫副程式gettop1方法,丟入組合個數及目標值
- #組合個數丟入4,在程式中代表0到3,所以會是C26取3 + C26取2 + C26取1 + C26取0
- tmp = gettop1(i,target)
- tmpmsg+='------------目標野獸數值: {} 萬 ------------\n'.format(target)
- if (tmp is None):
- tmpmsg+="沒有找到結果。\n"
- else:
- #差值
- deviation = round(float(sum(tmp))-float(target),1)
- #加總差值
- deviations += deviation
- tmpmsg+="{} \t\t總合: {} \t差值: {} \n".format(tmp, round(sum(tmp),1), deviation)
- #使用過的門客,從陣列中移除
- for element in tmp:
- nums.remove(element)
- if (olddeviation == 0):
- olddeviation = deviations
- tmpmsg+="\ni={}, 總差值:{}\n".format(i, deviations)
- resultmsg = tmpmsg
- if (olddeviation > deviations):
- olddeviation = deviations
- print(resultmsg)
結果
五、整理至Excel
我目前有在Google上建了Excel來整理古林狩獵的資料,連結在這,貼給大家做個參考
我先將所有門客的戰力列出來(AB行),對戰組合那邊只需要輸入賺錢(戰力),名字會自己抓對應的,也會自動加總
抓對應戰力門客名字公式如下
- =IFERROR(INDEX($A$2:$B$46,MATCH(F2,$B$2:$B$46,0),1),"")
IFERROR(運算式, "運算式發生錯誤時要顯示的字串"),字串給空值表示發生錯誤值時該欄位會填入空值,就不會出現【#N/A】
這邊用Index+match就為了透過門客的賺錢值找到對應的門客名字。
INDEX(參照範圍,列,欄),概念就像是給(x,y)回傳該位置的值。
MATCH(你要查詢的門客賺錢目標值,門客賺錢的參照數列,搜尋類型)
MATCH的搜尋類型有以下三種
- 0: 未排序並找出完全符合的數值。
- 1(預設): 遞增排序並找出小於或等於的最大值
- 2: 遞減排序並找出大於或等於的最小值
六、結論
果然花時間的運算交給電腦來處理,都只是幾秒鐘的事情,舒服。
後續可以將Excel整合進來,直接對Excel做讀寫檔。
或是改成Web版,網頁做UI,使用者輸入門客資料,傳到Python處理完再傳回來網頁做顯示,門客資料可存在Firebase。
沒有留言:
張貼留言