引言
《植物大战僵尸》是一款经典的塔防游戏,玩家需要在花园中种植各种植物来抵御不断进攻的僵尸。从算法视角来看,这款游戏的核心挑战在于波次调度(何时、以何种组合生成僵尸)与防御布局优化(如何选择和放置植物以最小化资源消耗并最大化防御效率)。本文将用Java实现一个简化版的植物大战僵尸核心算法框架,重点讲解波次生成算法和贪心防御布局策略。
核心算法一:波次调度生成算法
僵尸的波次生成遵循一定的节奏和难度曲线。我们使用加权随机抽样结合难度递增函数来生成每一波的僵尸组合。
算法思路
- 定义僵尸类型及其基础权重(普通、路障、铁桶、旗帜等)
- 随着波次增加,高级僵尸的权重线性上升
- 使用别名方法(Alias Method)或累积概率进行O(1)或O(log n)的加权随机选择
- 大波次(Flag Wave)固定出现大量僵尸
import java.util.*;
/**
* 僵尸类型枚举,包含基础属性和权重
*/
enum ZombieType {
NORMAL(50, 1.0, 1.0), // 普通僵尸:血量50,速度1.0,基础权重1.0
CONE(100, 1.0, 0.6), // 路障僵尸
BUCKET(200, 0.8, 0.3), // 铁桶僵尸
FLAG(50, 1.2, 0.1), // 旗帜僵尸,带领一波进攻
NEWSPAPER(100, 1.5, 0.2); // 报纸僵尸,受伤后加速
final int hp;
final double speed;
final double baseWeight;
ZombieType(int hp, double speed, double baseWeight) {
this.hp = hp;
this.speed = speed;
this.baseWeight = baseWeight;
}
}
/**
* 波次调度器:负责生成每一波的僵尸组合
*/
public class WaveScheduler {
private static final Random rand = new Random();
private final int totalLanes; // 总车道数(通常为5或6)
private final int totalWaves; // 总波次数
private final double difficultyCurve; // 难度曲线系数
public WaveScheduler(int totalLanes, int totalWaves, double difficultyCurve) {
this.totalLanes = totalLanes;
this.totalWaves = totalWaves;
this.difficultyCurve = difficultyCurve;
}
/**
* 计算第wave波中某僵尸类型的动态权重
* 权重随波次递增,实现难度曲线
*/
private double computeDynamicWeight(ZombieType type, int wave) {
double progress = (double) wave / totalWaves;
// 高级僵尸在后期波次权重显著提升
double difficultyMultiplier = 1.0 + difficultyCurve * progress * type.hp / 50.0;
return type.baseWeight * difficultyMultiplier;
}
/**
* 使用累积概率进行加权随机选择(可优化为Alias Method达到O(1))
* @return 选中的僵尸类型
*/
private ZombieType weightedRandomSelect(ZombieType[] types, double[] weights) {
double total = 0;
for (double w : weights) total += w;
double threshold = rand.nextDouble() * total;
double cumulative = 0;
for (int i = 0; i < types.length; i++) {
cumulative += weights[i];
if (cumulative >= threshold) {
return types[i];
}
}
return types[types.length - 1];
}
/**
* 生成第wave波的僵尸部署计划
* @param wave 当前波次(从1开始)
* @return 每个车道上的僵尸列表
*/
public List<ZombieType>[] generateWave(int wave) {
@SuppressWarnings("unchecked")
List<ZombieType>[] lanes = new ArrayList[totalLanes];
for (int i = 0; i < totalLanes; i++) {
lanes[i] = new ArrayList<>();
}
// 计算本波僵尸总数:随波次递增,大波次额外增加
boolean isBigWave = (wave % 10 == 0) || (wave == totalWaves);
int baseCount = 2 + wave / 3;
int zombieCount = isBigWave ? baseCount * 3 : baseCount;
ZombieType[] allTypes = ZombieType.values();
double[] weights = new double[allTypes.length];
for (int i = 0; i < allTypes.length; i++) {
weights[i] = computeDynamicWeight(allTypes[i], wave);
}
// 为每个僵尸随机分配车道
for (int i = 0; i < zombieCount; i++) {
int lane = rand.nextInt(totalLanes);
ZombieType type = weightedRandomSelect(allTypes, weights);
lanes[lane].add(type);
}
return lanes;
}
public static void main(String[] args) {
WaveScheduler scheduler = new WaveScheduler(5, 50, 2.0);
// 模拟生成前5波
for (int w = 1; w <= 5; w++) {
List<ZombieType>[] wave = scheduler.generateWave(w);
int total = Arrays.stream(wave).mapToInt(List::size).sum();
System.out.println("第" + w + "波,共" + total + "只僵尸");
for (int lane = 0; lane < wave.length; lane++) {
System.out.println(" 车道" + lane + ": " + wave[lane]);
}
}
}
}
核心算法二:贪心防御布局策略
面对不断生成的僵尸波次,玩家(或AI)需要决定在每个车道种植什么植物。我们采用贪心策略结合威胁评估来进行自动化布局决策。
算法思路
- 威胁评估:根据每个车道即将到达的僵尸数量和类型,计算车道威胁值
- 植物选择:根据威胁类型选择最优植物(豌豆射手对付普通僵尸,坚果墙拖延时间,樱桃炸弹应对集群)
- 资源约束:阳光值有限,需要在各车道间进行资源分配
- 贪心决策:每次选择”单位阳光收益最高”的种植方案
import java.util.*;
/**
* 植物类型及其属性
*/
enum PlantType {
PEASHOOTER(100, 25, 1, 20), // 阳光成本,冷却时间,攻击范围,伤害
SUNFLOWER(50, 5, 0, 0), // 产生阳光,无攻击
WALLNUT(50, 15, 0, 0), // 高血量防御墙
CHERRY_BOMB(150, 30, 3, 500), // 范围爆炸伤害,范围3格
REPEATER(200, 25, 1, 40); // 双发射手,伤害翻倍
final int sunCost;
final int cooldown; // 冷却回合数
final int range; // 攻击范围(0表示无攻击,3表示3x3范围)
final int damage;
PlantType(int sunCost, int cooldown, int range, int damage) {
this.sunCost = sunCost;
this.cooldown = cooldown;
this.range = range;
this.damage = damage;
}
}
/**
* 表示一个车道的威胁状态
*/
class LaneThreat {
int laneId;
double threatScore; // 综合威胁值
List<ZombieInfo> incoming; // 即将到来的僵尸
LaneThreat(int laneId) {
this.laneId = laneId;
this.threatScore = 0;
this.incoming = new ArrayList<>();
}
}
class ZombieInfo {
ZombieType type;
double distance; // 距离家园的距离(格子数)
double arriveTurn; // 预计到达回合
ZombieInfo(ZombieType type, double distance, double speed) {
this.type = type;
this.distance = distance;
this.arriveTurn = distance / speed;
}
}
/**
* 贪心防御布局决策器
*/
public class DefensePlanner {
private final int totalLanes;
private final int mapLength; // 地图长度(格子数)
private int availableSun; // 可用阳光
public DefensePlanner(int totalLanes, int mapLength, int initialSun) {
this.totalLanes = totalLanes;
this.mapLength = mapLength;
this.availableSun = initialSun;
}
/**
* 计算每个车道的威胁值
* 威胁值 = Σ(僵尸血量 / 预计到达回合^2)
* 越近且越强的僵尸威胁越大
*/
public LaneThreat[] evaluateThreats(List<ZombieType>[] incomingZombies) {
LaneThreat[] threats = new LaneThreat[totalLanes];
for (int i = 0; i < totalLanes; i++) {
threats[i] = new LaneThreat(i);
if (incomingZombies[i] == null) continue;
for (ZombieType z : incomingZombies[i]) {
// 假设僵尸从地图最右侧进入,速度决定到达时间
double arriveTurn = mapLength / z.speed;
double urgency = 1.0 / (arriveTurn * arriveTurn);
threats[i].threatScore += z.hp * urgency;
threats[i].incoming.add(new ZombieInfo(z, mapLength, z.speed));
}
}
return threats;
}
/**
* 贪心选择最优种植方案
* @return 建议的种植操作列表 [车道, 植物类型]
*/
public List<int[]> planDefense(LaneThreat[] threats) {
List<int[]> plans = new ArrayList<>();
// 按威胁值降序排序车道
Integer[] laneOrder = new Integer[totalLanes];
for (int i = 0; i < totalLanes; i++) laneOrder[i] = i;
Arrays.sort(laneOrder, (a, b) -> Double.compare(threats[b].threatScore, threats[a].threatScore));
for (int laneIdx : laneOrder) {
LaneThreat threat = threats[laneIdx];
if (threat.threatScore < 10) continue; // 威胁过低,暂不处理
PlantType chosen = selectBestPlant(threat);
if (chosen != null && availableSun >= chosen.sunCost) {
plans.add(new int[]{laneIdx, chosen.ordinal()});
availableSun -= chosen.sunCost;
}
}
return plans;
}
/**
* 根据威胁特征选择最优植物
*/
private PlantType selectBestPlant(LaneThreat threat) {
boolean hasCluster = threat.incoming.size() >= 3;
boolean hasTank = threat.incoming.stream().anyMatch(z -> z.type.hp >= 200);
boolean urgent = threat.incoming.stream().anyMatch(z -> z.arriveTurn < 5);
// 集群威胁 → 樱桃炸弹
if (hasCluster && availableSun >= PlantType.CHERRY_BOMB.sunCost) {
return PlantType.CHERRY_BOMB;
}
// 紧急且高血量 → 坚果墙拖延
if (urgent && hasTank && availableSun >= PlantType.WALLNUT.sunCost) {
return PlantType.WALLNUT;
}
// 高血量但距离较远 → 双发射手
if (hasTank && availableSun >= PlantType.REPEATER.sunCost) {
return PlantType.REPEATER;
}
// 默认选择豌豆射手
if (availableSun >= PlantType.PEASHOOTER.sunCost) {
return PlantType.PEASHOOTER;
}
return null;
}
public static void main(String[] args) {
DefensePlanner planner = new DefensePlanner(5, 10, 500);
// 模拟5个车道的 incoming 僵尸
@SuppressWarnings("unchecked")
List<ZombieType>[] zombies = new ArrayList[5];
for (int i = 0; i < 5; i++) zombies[i] = new ArrayList<>();
zombies[0].add(ZombieType.NORMAL); zombies[0].add(ZombieType.NORMAL);
zombies[1].add(ZombieType.BUCKET);
zombies[2].add(ZombieType.NORMAL); zombies[2].add(ZombieType.CONE); zombies[2].add(ZombieType.NORMAL);
zombies[3].add(ZombieType.FLAG); zombies[3].add(ZombieType.NORMAL); zombies[3].add(ZombieType.CONE);
LaneThreat[] threats = planner.evaluateThreats(zombies);
List<int[]> plans = planner.planDefense(threats);
System.out.println("防御部署计划:");
for (int[] plan : plans) {
System.out.println(" 车道" + plan[0] + " 种植 " + PlantType.values()[plan[1]]);
}
}
}
核心算法三:战斗模拟与伤害结算
将波次生成与防御布局结合,进行回合制战斗模拟。每个回合僵尸前进、植物攻击、判断胜负。
import java.util.*;
/**
* 简化版战斗模拟引擎
*/
public class BattleSimulator {
static class Plant {
PlantType type;
int hp = 100; // 植物基础血量
int cooldownRemaining = 0;
Plant(PlantType type) {
this.type = type;
if (type == PlantType.WALLNUT) this.hp = 400;
}
}
static class Zombie {
ZombieType type;
int hp;
double position; // 距离家园的格子数
double speed;
Zombie(ZombieType type, double startPosition) {
this.type = type;
this.hp = type.hp;
this.speed = type.speed;
this.position = startPosition;
}
}
private final int mapLength;
private final List<Plant>[] lanePlants; // 每个车道的植物队列
private final List<Zombie>[] laneZombies;
public BattleSimulator(int lanes, int mapLength) {
this.mapLength = mapLength;
lanePlants = new ArrayList[lanes];
laneZombies = new ArrayList[lanes];
for (int i = 0; i < lanes; i++) {
lanePlants[i] = new ArrayList<>();
laneZombies[i] = new ArrayList<>();
}
}
public void addPlant(int lane, PlantType type) {
lanePlants[lane].add(new Plant(type));
}
public void addZombie(int lane, ZombieType type) {
laneZombies[lane].add(new Zombie(type, mapLength));
}
/**
* 模拟一回合战斗
* @return true 表示家园安全,false 表示有僵尸突破
*/
public boolean simulateTurn() {
for (int lane = 0; lane < lanePlants.length; lane++) {
List<Plant> plants = lanePlants[lane];
List<Zombie> zombies = laneZombies[lane];
// 1. 植物攻击阶段
for (Plant p : plants) {
if (p.hp <= 0) continue;
if (p.type == PlantType.SUNFLOWER) continue; // 向日葵不攻击
if (p.cooldownRemaining > 0) {
p.cooldownRemaining--;
continue;
}
if (p.type == PlantType.CHERRY_BOMB) {
// 樱桃炸弹:消灭范围内所有僵尸,然后自毁
zombies.removeIf(z -> z.position <= 3);
p.hp = 0;
continue;
}
// 单体攻击:攻击最前方的僵尸
if (!zombies.isEmpty()) {
Zombie target = zombies.get(0);
target.hp -= p.type.damage;
}
p.cooldownRemaining = p.type.cooldown;
}
// 2. 移除死亡僵尸
zombies.removeIf(z -> z.hp <= 0);
// 3. 僵尸前进与啃食
Iterator<Zombie> it = zombies.iterator();
while (it.hasNext()) {
Zombie z = it.next();
// 检查前方是否有植物
boolean blocked = false;
for (Plant p : plants) {
if (p.hp > 0 && Math.abs(z.position - mapLength / 2) < 1.0) {
// 简化:僵尸啃食植物
p.hp -= 10;
blocked = true;
break;
}
}
if (!blocked) {
z.position -= z.speed;
}
if (z.position <= 0) {
return false; // 僵尸突破防线
}
}
// 4. 清理死亡植物
plants.removeIf(p -> p.hp <= 0);
}
return true;
}
public static void main(String[] args) {
BattleSimulator sim = new BattleSimulator(1, 10);
sim.addPlant(0, PlantType.PEASHOOTER);
sim.addPlant(0, PlantType.PEASHOOTER);
sim.addZombie(0, ZombieType.NORMAL);
sim.addZombie(0, ZombieType.CONE);
for (int turn = 1; turn <= 20; turn++) {
boolean safe = sim.simulateTurn();
System.out.println("回合" + turn + ": " + (safe ? "防守成功" : "僵尸突破!"));
if (!safe) break;
}
}
}
复杂度分析
| 模块 | 时间复杂度 | 空间复杂度 | 说明 |
|---|---|---|---|
| 波次生成 | O(W × Z × T) | O(W × L × Z) | W=总波次,Z=僵尸数,T=类型数,L=车道数 |
| 威胁评估 | O(L × Z) | O(L × Z) | 遍历所有车道的僵尸 |
| 贪心布局 | O(L log L + L × Z) | O(L × Z) | 排序车道 + 植物选择 |
| 战斗模拟 | O(Turn × L × (P + Z)) | O(L × (P + Z)) | 每回合遍历所有植物和僵尸 |
总结
本文通过Java实现了一个简化版植物大战僵尸的核心算法框架,涵盖:
– 波次调度:加权随机抽样实现动态难度曲线
– 贪心防御:基于威胁评估的自动化植物布局
– 战斗模拟:回合制伤害结算与胜负判定
读者可以在此基础上扩展更多植物类型、僵尸AI行为以及更复杂的策略网络,进一步探索塔防游戏中的算法之美。