线性规划在明日方舟基建系统中的应用
前言
接触过明日方舟NGA社区的人大概都知道一个工具叫ArkPlanner,用来求解最优的刷图线路,其用到的理论工具便是线性规划。
如果要让我用一句话来向对此不太熟悉的人介绍线性规划的作用,我会这样说:如果你需要100个苹果和100个梨,A副本掉落5苹果,B副本掉落5梨,C副本掉落3苹果3梨,那么最优解就是刷C副本,而这个最优方案可以用线性规划来求得。
也许有人会说,我肉眼都看出C副本掉得多,这还要求解吗?然而大部分真实情况下,苹果和梨的价值不统一,如果我们每天最多可消耗100个苹果和6个梨,多余的梨没有使用途径,这时候是否A、C副本的组合成了最优解?又或者,5个梨在商店可以卖100元,然后100元可以购买10个苹果,商品买卖不限量,此时B副本是否又成了最优解?
下面这个链接是我利用线性规划求解基建最优方案的的Python代码,挂在谷歌的Colab线上环境中,可以直接登录运行:
https://colab.research.google.com/drive/10p6xC-R8Mmue-Ra-pCP35LWoPjL-9Fuq?usp=sharing
当你对物品的期望价值不同时,你的最优基建形式也会不同,这里其实涉及到一个很有意思的问题,策划在设计道具时自己有一个内禀价值预期,而玩家/拆解者是不知道这个预期值的。因此怎么评估这个价值也是值得思考的事情。举一个例子就是,明日方舟的经验卡副本掉落非常不“划算”,因此一些低练度玩家会完全不刷这个本,而是等待基建自然产出,在这种条件下,经验卡的边际价格还能用经验卡副本来衡量吗?
我个人认为这里的设计非常巧妙,让玩家自发地完成了一个阶梯式价格歧视的经济系统。其他游戏中随时间产出的保底收益也有这种意思在里面,但是缺少了基建系统的边际价格偏移的表现。明日方舟中,肝帝由于需求大量经验卡,经验卡副本产出效率可以评估其期望价值,所以在基建中也会对应地调整其生产线,使其向经验卡偏移;而咸鱼放弃了经验卡副本,其对经验卡的价值评估仅来自于基建建筑中,与其他产物的生成效率比,因此会向龙门币偏移。
一个单纯产出资源的系统,也能产生“有意义的选择”,给予玩家可玩性,我认为这是优秀的设计(当然,实际应用中也要考虑到游戏类型适不适合)。
其实还有一些东西想写,但是由于现在太晚了,想睡觉了,周末有空再补一下。
具体代码我也列一下: 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120import cvxpy as cp
import numpy as np
#造经验书/金条的1/2/3级制造站数量
x_exp = cp.Variable(3,integer=True)
x_gold = cp.Variable(3,integer=True)
#1/2/3级贸易站数量
x_trading = cp.Variable(3,integer=True)
#3级电站的数量
x_elec = cp.Variable(integer=True)
x_drone_ratio = cp.Variable(3, nonneg=True)
#1/2/3/4/5级宿舍的数量
x_dorm = cp.Variable(5,integer=True)
#1/2/3级会客室的数量
x_meeting = cp.Variable(3, integer=True)
#加工站默认3级 消耗电力10
#1/2/3级办公室的数量
x_office = cp.Variable(3, integer=True)
#1/2/3级训练室的数量
x_training = cp.Variable(3, integer=True)
##地块约束##
constraints = [
#左侧共九个建筑
cp.sum(x_exp)+cp.sum(x_gold)+cp.sum(x_trading)+x_elec==9,
#制造站总数少于5个
cp.sum(x_exp)+cp.sum(x_gold)<=5,
#贸易站总数少于5个
cp.sum(x_trading)<=5,
#电站总量少于或等于3个
x_elec>=1,
x_elec<=3,
#无人机分配比例之和等于总电站数
cp.sum(x_drone_ratio)==x_elec,
#至少需要2个宿舍,宿舍总等级合计4级以上才能提供足够回复
1*x_dorm[0]+2*x_dorm[1]+3*x_dorm[2]+4*x_dorm[3]+5*x_dorm[4]>=4,
cp.sum(x_dorm)>=2,
cp.sum(x_meeting)==1,
cp.sum(x_office)==1,
cp.sum(x_training)==1,
]
#合法值约束
for lst in (x_exp,x_gold,x_trading,x_dorm,x_meeting,x_office,x_training):
for item in lst:
constraints.append(item>=0)
#电力约束
left_power = -10*(x_exp[0]+x_gold[0]+x_trading[0])\
-30*(x_exp[1]+x_gold[1]+x_trading[1])\
-60*(x_exp[2]+x_gold[2]+x_trading[2])\
+270*x_elec
middle_power = -10*x_dorm[0]-20*x_dorm[1]-30*x_dorm[2]-45*x_dorm[3]-60*x_dorm[4]
right_power = -10\
-10*(x_meeting[0]+x_office[0]+x_training[0])-30*(x_meeting[1]+x_office[1]+x_training[1])-60*(x_meeting[1]+x_office[1]+x_training[1])
constraints.append(left_power+middle_power+right_power>=0)
##收益##
#经验书制造干员平均加成
bonus1 = 0.3
#金条制造干员平均加成
bonus2 = 0.35
#贸易站干员平均加成
bonus3 = 0.35
#电站干员平均加成
bonus4 = 0.2
#经验值产出
obj_exp = 1000*1440/180*(x_exp[0]*(1.01+bonus1)+x_exp[1]*(1.02+2*bonus1)+x_exp[2]*(1.03+3*bonus1)+0.5*(1.05+bonus4)*x_drone_ratio[0])
#金条产出消耗
obj_gold_in = 1*1440/72*(x_gold[0]*(1.01+bonus2)+x_gold[1]*(1.02+2*bonus2)+x_gold[2]*(1.03+3*bonus2)+0.5*(1.05+bonus4)*x_drone_ratio[1])
obj_gold_out = 1440/72*(x_trading[0]*(1.08+bonus3)+x_trading[1]*(1.08+bonus3)+x_trading[2]*(1.08+bonus3)+0.5*(1.05+bonus4)*x_drone_ratio[2])
#龙门币产出
obj_money = 500*1440/72*(x_trading[0]*(1.08+bonus3)+x_trading[1]*(1.08+bonus3)+x_trading[2]*(1.08+bonus3)+0.5*(1.05+bonus4)*x_drone_ratio[2])
#来自日常+活动的每日金条平均不超过20根
constraints.append(obj_gold_in-obj_gold_out>=-20)
#剩余金条平均不超过20根
constraints.append(obj_gold_in-obj_gold_out<=20)
#信用点产出
obj_point = (1000/125+10)*x_dorm[0]+(2000/125+10)*x_dorm[1]+(3000/125+10)*x_dorm[2]+(4000/125+10)*x_dorm[3]+(5000/125+10)*x_dorm[4]
#一级会客室认为信用点30*5
# #默认必须3级会客室
# constraints.append(x_meeting[2]==1)
# #默认必须2级办公室
# constraints.append(x_office[1]==1)
# #默认必须1级训练室
# constraints.append(x_training[0]==1)
constraints.append(obj_money>=obj_exp)
exp_and_money = obj_exp+obj_money
objective = cp.Maximize(obj_exp*0.81/200+obj_money*0.004+obj_point*0.144+(obj_gold_in-obj_gold_out)*1.92)
prob = cp.Problem(objective,constraints)
result = prob.solve()
print("求解状态:",prob.status)
print("经验书制造站",x_exp.value)
print("赤金制造站",x_gold.value)
print("贸易站",x_trading.value)
print("发电站数量:",x_elec.value)
print("产出经验",obj_exp.value)
print("产出金条",obj_gold_in.value)
print("消耗金条",obj_gold_out.value)
print("产出龙门币",obj_money.value)






