引言
瘟疫公司(Plague Inc.)是一款广受欢迎的策略模拟游戏,玩家扮演病原体,通过进化传播能力、症状和耐药性,最终感染并消灭全人类。要在游戏中取得高分,关键在于如何优化病原体的各项参数(传染性、致命性、耐药性等),使其在不同气候、人口密度和医疗水平的国家中实现最优传播。本文将用Java实现基于差分进化(Differential Evolution, DE)算法的病毒参数自动优化系统,讲解DE算法的变异、交叉与选择三大核心操作,构建一个能自动搜索最优进化策略的智能引擎。
核心算法设计
1. 参数编码与适应度函数
将病原体的进化策略编码为实数向量X = [x₁, x₂, …, xₙ],每个维度代表一项属性:
– x₁:空气传播等级(0~5)
– x₂:水源传播等级(0~5)
– x₃:动物传播等级(0~5)
– x₄:症状严重性(0~10)
– x₅:致命性(0~10)
– x₆:抗寒性(0~5)
– x₇:抗热性(0~5)
– x₈:耐药性(0~5)
适应度函数综合考虑感染速度、死亡率和被治愈风险:
Fitness(X) = w₁×InfectRate(X) + w₂×Lethality(X) – w₃×CureRisk(X)
2. 差分进化算法
DE是一种基于群体智能的进化优化算法,通过向量差分实现搜索方向的自适应调整:
变异:对于每个目标向量Xᵢ,随机选择三个不同个体Xᵣ₁, Xᵣ₂, Xᵣ₃,生成变异向量:
Vᵢ = Xᵣ₁ + F × (Xᵣ₂ – Xᵣ₃)
其中F∈[0,2]为缩放因子,控制差分扰动幅度。
交叉:将变异向量Vᵢ与目标向量Xᵢ按概率CR进行二项交叉,生成试验向量Uᵢ:
Uᵢⱼ = Vᵢⱼ if rand() < CR or j == j_rand
Uᵢⱼ = Xᵢⱼ otherwise
选择:比较试验向量与目标向量的适应度,保留更优者:
Xᵢ_new = Uᵢ if f(Uᵢ) ≤ f(Xᵢ) else Xᵢ
(最小化问题,若最大化则取反)
3. 约束处理
病原体进化受DNA点数约束:Σcost(xᵢ) ≤ DNA_Points。采用罚函数法,将违反约束的解适应度大幅惩罚。
Java实现
import java.util.*;
/**
* 瘟疫公司病毒参数优化系统
* 核心算法:差分进化(Differential Evolution)
*/
public class PlagueIncOptimizer {
// ==================== 病毒参数模型 ====================
static class VirusParams {
double[] genes; // [空气,水源,动物,症状,致命性,抗寒,抗热,耐药]
static final String[] GENE_NAMES = {
"空气传播", "水源传播", "动物传播", "症状严重性",
"致命性", "抗寒性", "抗热性", "耐药性"
};
static final double[] MIN_VAL = {0, 0, 0, 0, 0, 0, 0, 0};
static final double[] MAX_VAL = {5, 5, 5, 10, 10, 5, 5, 5};
static final double[] COST = {1, 1, 1, 2, 3, 1, 1, 2}; // 每级DNA消耗
VirusParams() {
this.genes = new double[8];
}
VirusParams(double[] genes) {
this.genes = genes.clone();
}
/** 将基因值裁剪到合法范围 */
void clip() {
for (int i = 0; i < genes.length; i++) {
genes[i] = Math.max(MIN_VAL[i], Math.min(MAX_VAL[i], genes[i]));
}
}
/** 计算总DNA消耗 */
double totalCost() {
double sum = 0;
for (int i = 0; i < genes.length; i++) {
sum += genes[i] * COST[i];
}
return sum;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
for (int i = 0; i < genes.length; i++) {
sb.append(String.format("%s=%.1f", GENE_NAMES[i], genes[i]));
if (i < genes.length - 1) sb.append(", ");
}
sb.append("]");
return sb.toString();
}
}
// ==================== 传播模拟与适应度评估 ====================
static class WorldModel {
int countries = 20; // 国家数量
double avgPopDensity = 100; // 平均人口密度
double avgWealth = 50; // 平均富裕程度(影响医疗)
double climateVar = 0.3; // 气候变异系数
/**
* 模拟传播并计算适应度
* @param vp 病毒参数
* @return 适应度值(越高越好)
*/
double evaluate(VirusParams vp) {
double air = vp.genes[0];
double water = vp.genes[1];
double animal = vp.genes[2];
double symptom = vp.genes[3];
double lethality = vp.genes[4];
double coldRes = vp.genes[5];
double heatRes = vp.genes[6];
double drugRes = vp.genes[7];
// DNA点数约束检查
double dnaCost = vp.totalCost();
double maxDNA = 60; // 可用DNA点数上限
if (dnaCost > maxDNA) {
// 罚函数:超出越多惩罚越重
return -100 * (dnaCost - maxDNA);
}
// 传播率计算:基础传播 + 各途径加成
double baseSpread = 0.05;
double spreadRate = baseSpread
+ air * 0.08
+ water * 0.06
+ animal * 0.04
+ (coldRes + heatRes) * 0.02; // 气候适应性提升全球传播
// 感染速度模拟(简化SIR模型中的感染项)
double infectSpeed = spreadRate * avgPopDensity / 10.0;
// 症状和致命性加快被发现和研发解药
double detectability = symptom * 0.05 + lethality * 0.08;
double cureSpeed = (avgWealth / 100.0) * (1 + detectability) * (1 - drugRes * 0.15);
// 综合评分:感染速度 + 致命性×持续时间 - 被治愈风险
double duration = Math.max(1.0, 50.0 / (cureSpeed + 0.1)); // 解药完成前持续时间
double totalInfected = infectSpeed * duration * countries;
double totalDeaths = totalInfected * (lethality / 10.0) * 0.3;
// 适应度:加权综合
double fitness = totalInfected * 0.4
+ totalDeaths * 0.5
- detectability * 20 // 过早暴露的惩罚
- cureSpeed * 10; // 解药速度惩罚
return fitness;
}
}
// ==================== 差分进化算法核心 ====================
static class DifferentialEvolution {
int popSize; // 种群大小
int dim; // 维度数
double F; // 缩放因子
double CR; // 交叉概率
int maxGen; // 最大迭代次数
Random rand = new Random(42);
WorldModel world;
DifferentialEvolution(int popSize, int dim, double F, double CR, int maxGen, WorldModel world) {
this.popSize = popSize;
this.dim = dim;
this.F = F;
this.CR = CR;
this.maxGen = maxGen;
this.world = world;
}
/**
* 初始化种群:在边界内随机生成
*/
List<VirusParams> initPopulation() {
List<VirusParams> pop = new ArrayList<>();
for (int i = 0; i < popSize; i++) {
VirusParams vp = new VirusParams();
for (int j = 0; j < dim; j++) {
vp.genes[j] = VirusParams.MIN_VAL[j]
+ rand.nextDouble() * (VirusParams.MAX_VAL[j] - VirusParams.MIN_VAL[j]);
}
pop.add(vp);
}
return pop;
}
/**
* 执行DE优化
*/
VirusParams optimize() {
List<VirusParams> pop = initPopulation();
double[] fitness = new double[popSize];
for (int i = 0; i < popSize; i++) {
fitness[i] = world.evaluate(pop.get(i));
}
VirusParams best = null;
double bestFit = Double.NEGATIVE_INFINITY;
for (int g = 0; g < maxGen; g++) {
for (int i = 0; i < popSize; i++) {
// 1. 变异:随机选择3个不同个体
int r1, r2, r3;
do { r1 = rand.nextInt(popSize); } while (r1 == i);
do { r2 = rand.nextInt(popSize); } while (r2 == i || r2 == r1);
do { r3 = rand.nextInt(popSize); } while (r3 == i || r3 == r1 || r3 == r2);
VirusParams xr1 = pop.get(r1);
VirusParams xr2 = pop.get(r2);
VirusParams xr3 = pop.get(r3);
// V = Xr1 + F * (Xr2 - Xr3)
VirusParams mutant = new VirusParams();
for (int j = 0; j < dim; j++) {
mutant.genes[j] = xr1.genes[j] + F * (xr2.genes[j] - xr3.genes[j]);
}
mutant.clip();
// 2. 交叉:二项交叉生成试验向量
VirusParams trial = new VirusParams(pop.get(i).genes.clone());
int jRand = rand.nextInt(dim);
for (int j = 0; j < dim; j++) {
if (rand.nextDouble() < CR || j == jRand) {
trial.genes[j] = mutant.genes[j];
}
}
trial.clip();
// 3. 选择:优胜劣汰
double trialFit = world.evaluate(trial);
if (trialFit > fitness[i]) {
pop.set(i, trial);
fitness[i] = trialFit;
}
// 更新全局最优
if (fitness[i] > bestFit) {
bestFit = fitness[i];
best = new VirusParams(pop.get(i).genes);
}
}
if ((g + 1) % 50 == 0) {
System.out.println(" 第" + (g + 1) + "代, 最优适应度=" + String.format("%.2f", bestFit));
}
}
return best;
}
}
// ==================== 主程序与测试 ====================
public static void main(String[] args) {
System.out.println("===== 瘟疫公司病毒参数优化系统 =====\n");
System.out.println("核心算法:差分进化(Differential Evolution)");
System.out.println("优化目标:最大化全球感染与致命效果,同时规避解药研发\n");
WorldModel world = new WorldModel();
// DE参数配置
int popSize = 50;
int dim = 8;
double F = 0.8; // 缩放因子
double CR = 0.9; // 交叉概率
int maxGen = 300; // 迭代次数
DifferentialEvolution de = new DifferentialEvolution(popSize, dim, F, CR, maxGen, world);
long start = System.currentTimeMillis();
VirusParams best = de.optimize();
long elapsed = System.currentTimeMillis() - start;
System.out.println("\n===== 优化结果 =====");
System.out.println("最优病毒参数: " + best);
System.out.println("DNA总消耗: " + String.format("%.1f", best.totalCost()) + " / 60");
System.out.println("最优适应度: " + String.format("%.2f", world.evaluate(best)));
System.out.println("优化耗时: " + elapsed + "ms");
System.out.println("\n参数解读:");
for (int i = 0; i < best.genes.length; i++) {
int level = (int) Math.round(best.genes[i]);
String bar = "█".repeat(level) + "░".repeat((int)(VirusParams.MAX_VAL[i]) - level);
System.out.println(" " + VirusParams.GENE_NAMES[i] + ": [" + bar + "] " + level);
}
}
}
复杂度分析
| 操作 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 种群初始化 | O(N×D) | O(N×D) | N为种群大小,D为维度 |
| 变异操作 | O(N×D) | O(D) | 每代每个体生成一个变异向量 |
| 交叉操作 | O(N×D) | O(D) | 二项交叉 |
| 适应度评估 | O(N×D) | O(1) | 每代评估N个试验向量 |
| 选择操作 | O(N) | O(1) | 逐个比较 |
| 整体优化 | O(G×N×D) | O(N×D) | G为迭代代数 |
算法扩展思路
- 自适应参数调整:采用jDE或SaDE策略,让F和CR在进化过程中自适应调整,提升收敛速度和全局搜索能力。
- 多目标优化:将感染速度、致命性、隐蔽性作为独立的帕累托目标,使用NSGA-II等算法求解非支配解集。
- 元启发式混合:将DE的变异机制与模拟退火的Metropolis准则结合,增强跳出局部最优的能力。
总结
本文将瘟疫公司的病原体进化策略抽象为实数向量优化问题,通过差分进化算法自动搜索最优参数组合。DE算法利用向量差分实现自适应搜索方向的生成,在二项交叉和贪婪选择中平衡探索与利用,是求解连续空间组合优化问题的有力工具。该框架不仅适用于游戏AI,也可扩展至药物配比优化、投资组合配置等现实决策场景。