模拟控制
我们可以开始搞模拟控制了。
首先,我们需要能够根据指定属性,给出一份DPS。如何来做?
我们采用最通俗易懂的架构。
1-重置现场(时间、布林、怪兽都恢复初始状态)
2-循环直至怪兽死亡:◦按照动作优先级列表运行一遍。
◦将时间向前推进。
•依据怪兽血量和战斗时长,计算DPS。
Code (c):
bling().respawn(str,c); //重置布林
enemy().respawn(health_max); //重置怪兽
now()=0; //重置时间
while(enemy().current_health()>0){ //循环直至怪兽死亡
if(bling().has_such_power(51.0)) bling().smackthat_dot_exe->execute(); //优先级1:如果能量高于50,使用打脸
bling().bsod->execute(); //优先级2:使用蓝屏
bling().smackthat_dot_exe->execute(); //优先级3:使用打脸
now()+=100; //将时间向前推进100毫秒。
}
dps = health_max/now()*1000.0; //DPS=怪兽血量除以战斗时长。
这样就完成了一次模拟实验,这属于一种最简单的时间驱动架构。
显然,蒙特卡洛法要求实验次数必须足够多,否则误差会比较大。
那么我们就需要重复这个过程。重复的次数,叫做迭代数(Iterations)。
例如,我们重复五万次。
Code (c):
double sim(double str, double c){
size_t i;
double health_max = health_max_default;
double dps, dps_sum;
dps_sum=0;
for (i=1;i<=50000;i++){ //循环五万次
bling().respawn(str,c);
enemy().respawn(health_max);
now()=0;
while(enemy().current_health()>0){
if(bling().has_such_power(51.0)) bling().smackthat_dot_exe->execute();
bling().bsod->execute();
bling().smackthat_dot_exe->execute();
now()+=100;
}
dps = health_max/now()*1000.0;
dps_sum+=dps; //统计DPS
}
i--;
return dps_sum/i; //返回DPS平均值
}
这样每次调用sim(力量,暴击),就可以进行一次五万迭代的DPS模拟。
SimC中还有战斗时长的选项,我们也希望控制战斗时长,怎么办?
我们可以通过每一次的DPS乘上想要的战斗时长,来估算总伤害量,然后将怪兽的最大血量设置到这么多。
这样我们在几次迭代后就能得到在期望的时长附近随机分布的战斗时长了。
SimC还会把与期望时长差距过大的模拟结果剔除掉,所以SimC的10000次迭代总是得到大约9994次实验结果的平均值。这个我们就不做了。
另外,在代码顶端添加一个头文件“stdio.h”,方便我们输出运算结果。
Code (c):
#include <stdio.h>
double sim(double str, double c){
size_t i;
double health_max = health_max_default;
double dps, dps_sum;
dps_sum=0;
for (i=1;i<=50000;i++){
bling().respawn(str,c);
enemy().respawn(health_max);
now()=0;
while(enemy().current_health()>0){
if(bling().has_such_power(51.0)) bling().smackthat_dot_exe->execute();
bling().bsod->execute();
bling().smackthat_dot_exe->execute();
now()+=100;
}
dps = health_max/now()*1000.0;
dps_sum+=dps;
health_max = dps * 150.0; //期望战斗时长150秒,以此估算一个浮动的怪兽血量提供给下一次迭代。
if (i%500==0)
printf(" \tIterations: %Iu DPS: %.3lf Averange DPS: %.3lf ", i, dps, dps_sum/i); //每500次迭代就刷新一下输出的结果
}
i--;
printf(" \tIterations: %Iu DPS: %.3lf Averange DPS: %.3lf ", i, dps, dps_sum/i); //输出最终结果。
return dps_sum/i;
}
属性权值
如何得到属性权值?
SimC很简单地使用两点差商来计算属性权值。
假如布林有10000的力量和30%的暴击,我们要计算力量的权值,就先计算10000力量30%暴击的布林的伤害,记D1,然后再计算11000力量30%暴击的布林的伤害,记D2。
这1000点力量差距叫做差商区间(Delta),力量的权值就等于(D2-D1)/1000。
我们在构建Blingdas世界的时候说好了,要给布林来一个属性评分,那么我们就真的来一个。
我们不使用“1000点”这样固定大小的Delta,我们使用5%的现有力量当作力量的Delta,使用5%暴击几率当作暴击的Delta。
对原始属性的模拟叫做“基线(Baseline)”。
Code (c):
int main(void){
double str = 10000.0; //在这里设置初始力量
double crit = 0.5; //在这里设置初始暴击率,0.5意思就是50%
double delta;
srand(time(NULL)); //设定种子值为Unix时间戳,保证每次运行都提供不完全相同的模拟结果。
printf("Generating Baseline: ");
double baseline = sim(str, crit);
printf("Generating Strength: ");
delta = 0.05 * str;
double str_scale_factor = sim(str + delta, crit);
str_scale_factor -= baseline;
str_scale_factor /= delta;
printf("Generating Crit Rate: ");
delta = 0.05;
double crit_scale_factor = sim(str, crit + delta);
crit_scale_factor -= baseline;
crit_scale_factor /= delta;
printf(
" "
"Bling's Averange DPS\t= %.3f "
"Strength Scale Factor\t= %.3f "
"Crit Rate Scale Factor\t= %.3f ",
baseline,
str_scale_factor,
crit_scale_factor
);
return 0;
}
这可能是这个模拟器的最后一段代码。盖高楼讲究喜封金顶,咱们这从高往低写的程序是不是应该讲喜踏金靴呢……
小结
整个模拟器已经完成。现在按下界面左上的齿轮三角图标,就可以运行查看效果了。
如果你一直跟下来,现在整个模拟器的完整代码应该如下:
Code (c):
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
enum damage_type_e{
DAMAGE_NORMAL=0,
DAMAGE_MECHANICAL=1,
DAMAGE_ELECTRIC=2,
DAMAGE_TYPE_TOTAL=3,
};
static const double health_max_default = 100000.0;
class kaiju_t{
private:
double health_max;
double health;
static const double damage_reduction_coeff[DAMAGE_TYPE_TOTAL];
public:
double current_health() const {
return health;
}
void take_damage(double damage, damage_type_e type){
health -= damage * damage_reduction_coeff[type];
}
kaiju_t(double h) : health_max(h), health(h) {}
kaiju_t() : health_max(health_max_default), health(health_max_default) {}
void respawn(){
health_max=health_max_default;
health=health_max_default;
}
void respawn(double h){
health_max=h;
health=h;
}
};
const double kaiju_t::damage_reduction_coeff[] = {
1.0, //DAMAGE_NORMAL
0.7, //DAMAGE_MECHANICAL
1.3, //DAMAGE_ELECTRIC
};
kaiju_t& enemy(){
static kaiju_t knifehead;
return knifehead;
}
time_t& now(){
static time_t t;
return t;
}
class bling_t{
private:
double crit;
double strength;
double power;
static const double power_max;
time_t gcd_expire;
public:
double get_crit() const{
return crit;
}
double get_str() const{
return strength;
}
bool has_such_power(double p) const{
return power>=p;
}
bool is_gcd_expired() const{
return gcd_expire<=now();
}
void consume_power(double p){
power-=p;
if (power<0) power=0;
}
void resume_power(double p){
power+=p;
if (power>power_max) power=power_max;
}
void trigger_gcd(time_t duration){
gcd_expire=now()+duration;
}
class bling_spell_t{
protected:
bling_t* bling_ptr;
private:
time_t cd;
time_t gcd;
time_t cd_expire;
double power_consume;
double power_resume;
public:
bling_spell_t(bling_t* ptr, time_t _cd, time_t _gcd, double _power_consume, double _power_resume) :
bling_ptr(ptr),
cd(_cd),
gcd(_gcd),
cd_expire(0),
power_consume(_power_consume),
power_resume(_power_resume)
{};
virtual ~bling_spell_t(){};
virtual bool execute(){
if (gcd>0&&!bling_ptr->is_gcd_expired()) return false;
if (cd>0&&cd_expire>now()) return false;
if (power_consume>0&&!bling_ptr->has_such_power(power_consume)) return false;
if (gcd>0) bling_ptr->trigger_gcd(gcd);
if (cd>0) cd_expire=now()+cd;
if (power_consume>0) bling_ptr->consume_power(power_consume);
if (power_resume>0) bling_ptr->resume_power(power_resume);
return true;
};
};
class bsod_t : public bling_spell_t{
public:
bsod_t(bling_t* ptr) : bling_spell_t(ptr, 6000, 1500, 0, 50) {}
};
class smackthat_dot_exe_t : public bling_spell_t{
public:
smackthat_dot_exe_t(bling_t* ptr) : bling_spell_t(ptr, 0, 1500, 30, 0) {}
virtual bool execute(){
if (!bling_spell_t::execute()) return false;
double d = 236.0;
d += bling_ptr->get_str();
double c = 0.0;
c += bling_ptr->get_crit();
if ( static_cast<double>(rand())/RAND_MAX < c )
d *= 2.0;
enemy().take_damage(d,DAMAGE_MECHANICAL);
return true;
};
};
bling_spell_t* smackthat_dot_exe;
bling_spell_t* bsod;
bling_t(double str, double c) :
crit(c),
strength(str),
power(power_max),
gcd_expire(0){
smackthat_dot_exe = new smackthat_dot_exe_t(this);
bsod = new bsod_t(this);
}
~bling_t(){
delete smackthat_dot_exe;
delete bsod;
}
void respawn(){
delete smackthat_dot_exe;
delete bsod;
power=power_max;
gcd_expire=0;
smackthat_dot_exe = new smackthat_dot_exe_t(this);
bsod = new bsod_t(this);
}
void respawn(double str, double c){
respawn();
strength=str;
crit=c;
}
};
const double bling_t::power_max = 100.0;
bling_t& bling(){
static bling_t blington3k(500,0.3);
return blington3k;
}
double sim(double str, double c){
size_t i;
double health_max = health_max_default;
double dps, dps_sum;
dps_sum=0;
for (i=1;i<=50000;i++){
bling().respawn(str,c);
enemy().respawn(health_max);
now()=0;
while(enemy().current_health()>0){
if(bling().has_such_power(51.0)) bling().smackthat_dot_exe->execute();
bling().bsod->execute();
bling().smackthat_dot_exe->execute();
now()+=100;
}
dps = health_max/now()*1000.0;
dps_sum+=dps;
health_max = dps * 150.0;
if (i%500==0) printf(" \tIterations: %Iu DPS: %.3lf Averange DPS: %.3lf ", i, dps, dps_sum/i);
}
i--;
printf(" \tIterations: %Iu DPS: %.3lf Averange DPS: %.3lf ", i, dps, dps_sum/i);
return dps_sum/i;
}
int main(void){
double str = 10000.0;
double crit = 0.5;
double delta;
srand(time(NULL));
printf("Generating Baseline: ");
double baseline = sim(str, crit);
printf("Generating Strength: ");
delta = 0.05 * str;
double str_scale_factor = sim(str + delta, crit);
str_scale_factor -= baseline;
str_scale_factor /= delta;
printf("Generating Crit Rate: ");
delta = 0.05;
double crit_scale_factor = sim(str, crit + delta);
crit_scale_factor -= baseline;
crit_scale_factor /= delta;
printf(
" "
"Bling's Averange DPS\t= %.3f "
"Strength Scale Factor\t= %.3f "
"Crit Rate Scale Factor\t= %.3f ",
baseline,
str_scale_factor,
crit_scale_factor
);
return 0;
}