88 KiB
19-AI导演系统逻辑语言
本文档把 18-AI导演系统策划文档 翻译成可落地到代码的逻辑语言。
本文档只描述新 AI 目标实现,不对照既有 AI 实现。
1. 文档约定
1.1 伪代码格式
函数 X(ctx, a, b):
如果 条件:
返回 value
对每个 item in list:
...
返回 value
1.2 动作约束
AI 只能选择合法 Action。
AIAction:
CommonActionId
CommonActionParams
ActionLogicBase
执行动作必须走:
Param.RefreshParams()
ActionLogic.CheckCan(Param)
ActionLogic.CompleteExecute(Param)
AI 不直接修改:
MapData
PlayerData
CityData
UnitData
GridData
SkillBase
1.3 决策原则
车道之间用固定优先级。
车道内部用轻量分数排序。
分数只比较同类动作,不做全局万能评分。
所有最终动作必须再次 CheckCan。
同分时使用稳定排序,避免随机和不同步。
2. 核心结构
2.1 AIContext
结构 AIContext:
Map: MapData
Player: PlayerData
Config: AIConfig
Cache: WorldCache
Actions: ActionPool
Trace: List<string>
BlockedActionKeysThisTurn: Set<string>
BlockedIntentKeysThisTurn: Set<string>
2.2 AIConfig
结构 AIConfig:
EmergencyRange = 3
FrontRange = 6
LocalBattleRange = 2
DevelopmentSearchRange = 6
MaxActionCount = 4096
MaxFrontCount = 12
MaxDevelopmentTargetCount = 20
MaxExpansionTargetScanCount = 6
MaxMoveActionsPerUnit = 2
MaxSelfCityMoveAnchorCount = 3
MaxEmergencyResponderScanCount = 8
SoftGrowthActionBudget = 10
MaxNonCriticalActionsPerPlayerTurn = 18
MinGrowthPriorityAfterSoftBudget = 430
MaxGrowthCityActionsPerDecision = 18
MaxGrowthGridActionsPerDecision = 24
MaxGrowthPlayerActionsPerDecision = 10
MaxExpansionMoveIntentsPerTurn = 3
MaxEmergencyMoveIntentsPerTurn = 2
MaxFrontMoveIntentsPerTurn = 1
MaxEmergencyRescueDistance = 6
MaxFrontMoveDistance = 8
PriorityTacticScore = 780
LowHpRatio = 0.45
CriticalHpRatio = 0.25
HeroLowHpRatio = 0.60
CityDangerEnemyCount = 2
CityCriticalEnemyCount = 4
CityThreatPowerRatio = 1.25
HeroTaskStartTurn = 8
HeroTaskInterval = 4
HeroLevel2MinTurn = 15
HeroLevel3MinTurn = 25
这些是默认值。难度、地图尺寸和关卡规则可以覆盖 Config,但不能改变车道顺序。
2.3 Lane
枚举 Lane:
Emergency
HeroManagement
Expansion
HeroPlaybook
Tactic
UnitOpportunity
Front
Growth
Fallback
2.4 Candidate
结构 Candidate:
Action: AIAction
Lane: Lane
Priority: float
Reason: string
Unit: UnitData
TargetUnit: UnitData
City: CityData
Grid: GridData
TargetGrid: GridData
IsFallback: bool
函数 Candidate.IsValid:
如果 Action == null:
返回 false
如果 Action.Param == null:
返回 false
如果 Action.ActionLogic == null:
返回 false
Action.Param.RefreshParams()
返回 Action.ActionLogic.CheckCan(Action.Param)
2.5 WorldCache
结构 WorldCache:
SelfUnits: List<UnitData>
SelfHeroes: List<UnitData>
EnemyUnits: List<UnitData>
EnemyHeroes: List<UnitData>
SelfCities: List<CityData>
EnemyCities: List<CityData>
SelfTerritoryGridIds: HashSet<uint>
VisibleOrKnownGrids: List<GridData>
Diplomacy: DiplomacyView
StrategicPosture: StrategicPosture
WarTargetPlayers: List<PlayerData>
CityThreats: List<CityThreat>
CityPlans: List<CityPlan>
DevelopmentTargets: List<DevelopmentTarget>
Fronts: List<Front>
LocalBattles: List<LocalBattle>
HeroStates: List<HeroState>
UnitOpportunities: List<UnitOpportunity>
HasCriticalCityThreat: bool
HasAnyEnemyContact: bool
SelfMilitaryValue: float
EnemyMilitaryValue: float
2.6 ActionPool
结构 ActionPool:
All: List<AIAction>
Attacks: List<AIAction>
AttackAllies: List<AIAction>
AttackGrounds: List<AIAction>
Moves: List<AIAction>
UnitActions: List<AIAction>
CityActions: List<AIAction>
GridActions: List<AIAction>
PlayerActions: List<AIAction>
HeroManagementActions: List<AIAction>
ByUnit: Map<UnitId, UnitActionBucket>
ByCity: Map<CityId, List<AIAction>>
ByGrid: Map<GridId, List<AIAction>>
结构 UnitActionBucket:
Attacks
AttackAllies
AttackGrounds
Moves
UnitActions
3. 总入口
3.1 Decide
函数 Decide(map, player, config, blockedActionKeysThisTurn, blockedIntentKeysThisTurn):
ctx = new AIContext(map, player, config)
ctx.BlockedActionKeysThisTurn = blockedActionKeysThisTurn
ctx.BlockedIntentKeysThisTurn = blockedIntentKeysThisTurn
如果 map == null 或 player == null 或 player.Alive == false:
返回 NoDecision
ctx.Cache = BuildWorldCache(ctx)
ctx.Actions = BuildActionPool(ctx)
如果 ctx.Actions.All.Count == 0:
返回 NoDecision
candidate = TryEmergency(ctx)
如果 candidate.IsValid:
返回 candidate
如果 HasUnresolvedEmptyCityCoreThreat(ctx):
返回 NoDecision
lanes = [
TryHeroManagement,
TryPriorityTactic,
TryExpansion,
TryHeroPlaybook,
TryTactic,
TryUnitOpportunity,
TryFront,
TryGrowth,
TryFallback
]
对每个 lane in lanes:
candidate = lane(ctx)
如果 candidate.IsValid:
返回 candidate
返回 NoDecision
3.2 ExecuteDecision
函数 ExecuteDecision(candidate):
如果 !candidate.IsValid:
返回 false
param = candidate.Action.Param
param.RefreshParams()
如果 !candidate.Action.ActionLogic.CheckCan(param):
返回 false
返回 candidate.Action.ActionLogic.CompleteExecute(param)
4. ActionPool
4.1 BuildActionPool
函数 BuildActionPool(ctx):
pool = new ActionPool
moveAnchors = BuildMoveAnchorGridIds(ctx)
rawActions = GenerateDirectorActions(ctx.Map, ctx.Player, moveAnchors, Config.MaxMoveActionsPerUnit)
对每个 rawAction in rawActions 按稳定顺序:
如果 pool.All.Count >= Config.MaxActionCount:
break
action = CopyAction(rawAction)
如果 action == null:
继续
如果 StableActionKey(action) in ctx.BlockedActionKeysThisTurn:
继续
action.Param.RefreshParams()
如果 !action.ActionLogic.CheckCan(action.Param):
继续
如果 IsDangerousAction(action):
继续
AddActionToPool(pool, action)
返回 pool
函数 BuildMoveAnchorGridIds(ctx):
anchors = []
对每个 cityThreat in ctx.Cache.CityThreats:
anchors.Add(cityThreat.CityGrid)
anchors.Add(每个威胁单位所在格)
对每个 developmentTarget in ctx.Cache.DevelopmentTargets:
anchors.Add(developmentTarget.Grid)
对每个 front in ctx.Cache.Fronts:
anchors.Add(front.TargetGrid)
anchors.Add(front.AnchorGrid)
selfCityAnchors = 0
对每个 cityPlan in ctx.Cache.CityPlans:
如果 selfCityAnchors >= Config.MaxSelfCityMoveAnchorCount:
break
anchors.Add(cityPlan.CityGrid)
selfCityAnchors++
如果 selfCityAnchors == 0:
对每个 selfCity in ctx.Cache.SelfCities:
如果 selfCityAnchors >= Config.MaxSelfCityMoveAnchorCount:
break
anchors.Add(selfCity.Grid)
selfCityAnchors++
对每个 localBattle in ctx.Cache.LocalBattles:
anchors.Add(localBattle.EnemyGrid)
anchors.Add(localBattle.SelfGrid)
返回去重后的 anchors
函数 GenerateDirectorActions(map, player, moveAnchors, maxMoveActionsPerUnit):
普通非移动动作按 CheckCan 生成。
对每个可移动单位:
先计算 UnitMoveInfo。
收集该单位本回合所有可移动候选格。
按“距离 moveAnchors 最近、城市格优先、已占格降权、稳定 GridId”排序。
只保留前 maxMoveActionsPerUnit 个 UnitMove 动作。
对每个可攻击单位:
只在 moveAnchors 中检查可见敌方单位格是否可攻击。
如果可攻击则生成 UnitAttack 动作。
返回动作列表
4.2 动作分类
函数 AddActionToPool(pool, action):
pool.All.Add(action)
id = action.ActionId
如果 id.ActionType == UnitAttack:
pool.Attacks.Add(action)
pool.ByUnit[action.UnitId].Attacks.Add(action)
如果 id.ActionType == UnitAttackAlly:
pool.AttackAllies.Add(action)
pool.ByUnit[action.UnitId].AttackAllies.Add(action)
如果 id.ActionType == UnitAttackGround:
pool.AttackGrounds.Add(action)
pool.ByUnit[action.UnitId].AttackGrounds.Add(action)
如果 id.ActionType == UnitMove:
pool.Moves.Add(action)
pool.ByUnit[action.UnitId].Moves.Add(action)
如果 id.ActionType == UnitAction 或 UnitSkill:
pool.UnitActions.Add(action)
pool.ByUnit[action.UnitId].UnitActions.Add(action)
如果 id.ActionType in [TrainUnit, CityLevelUpAction, CityAction, StartWonder, BuildWonder]:
pool.CityActions.Add(action)
pool.ByCity[action.CityId].Add(action)
如果 id.ActionType in [Gain, Build, GridMisc, HakureiEinherjarCityDevelopment]:
pool.GridActions.Add(action)
pool.ByGrid[action.GridId].Add(action)
如果 id.ActionType == LearnTech:
pool.PlayerActions.Add(action)
如果 id.ActionType == BuyCultureCard:
如果 IsAIHeroSlotCultureCard(player, id.CultureCardType):
pool.HeroManagementActions.Add(action)
否则:
继续
如果 id.ActionType == PlayerAction:
如果 id.PlayerActionType in [SelectHero, FinishHeroTask]:
pool.HeroManagementActions.Add(action)
否则:
pool.PlayerActions.Add(action)
函数 IsAIHeroSlotCultureCard(player, cardType):
如果 player 没有 HeroData 或 CultureInfo:
返回 false
如果 player 已拥有 cardType:
返回 false
如果 cardType == SecondHero:
返回 MaxHeroCount == 1 且 HeroCount >= 1
如果 cardType == ThirdHero:
返回 MaxHeroCount == 2 且 HeroCount >= 2 且 已拥有 SecondHero
返回 false
4.3 危险动作过滤
函数 IsDangerousAction(action):
id = action.ActionId
如果 id.UnitActionType in [Disband, ForceDisband, Demolish, Disperse]:
返回 true
如果 id.GridMiscActionType == Destroy:
返回 true
返回 false
危险动作后续只能由专门车道显式调用,不能进入普通 AI 兜底。
4.4 ActionPool 查询
函数 FindBestAttack(ctx, unit, target = null):
actions = ctx.Actions.ByUnit[unit.Id].Attacks
best = None
对每个 action in actions:
如果 target != null 且 action.TargetUnit != target:
继续
score = ScoreAttackAction(ctx, action)
如果 score <= 0:
继续
best = MaxByScore(best, action, score)
返回 best.Action
函数 FindBestMoveToward(ctx, unit, targetGrid):
cacheKey = (unit.Id, targetGrid.Id)
如果 ctx.ActionIndex.BestMoveCache 包含 cacheKey:
返回缓存结果
actions = ctx.Actions.ByUnit[unit.Id].Moves
best = None
对每个 action in actions:
endGrid = GetActionEndGrid(action)
score = -Distance(endGrid, targetGrid)
score -= GridThreat(ctx, unit, endGrid) * 0.5
score += GridOpportunityBonus(ctx, unit, endGrid)
best = MaxByScore(best, action, score)
写入 ctx.ActionIndex.BestMoveCache[cacheKey] = best.Action
返回 best.Action
函数 FindUnitAction(ctx, unit, unitActionType):
actions = ctx.Actions.ByUnit[unit.Id].UnitActions
返回第一个 ActionId.UnitActionType == unitActionType 且 CheckCan 的 action
5. WorldCache 构建
5.1 BuildWorldCache
函数 BuildWorldCache(ctx):
cache = new WorldCache
CollectWorldObjects(ctx, cache)
BuildDiplomacyView(ctx, cache)
BuildMilitarySummary(ctx, cache)
BuildCityThreats(ctx, cache)
BuildStrategicPosture(ctx, cache)
BuildCityPlans(ctx, cache)
BuildDevelopmentTargets(ctx, cache)
BuildFronts(ctx, cache)
BuildLocalBattles(ctx, cache)
BuildHeroStates(ctx, cache)
返回 cache
UnitOpportunities 依赖 ActionPool,可以在 ActionPool 建好后构建:
函数 BuildPostActionCache(ctx):
BuildUnitOpportunities(ctx, ctx.Cache, ctx.Actions)
如果实现上 ActionPool 先于 WorldCache,也可以拆成:
基础 Cache
→ ActionPool
→ 补充 UnitOpportunity
5.2 CollectWorldObjects
函数 CollectWorldObjects(ctx, cache):
cache.SelfUnits = map.GetUnitDataListByPlayerId(player.Id).Where(IsAlive)
cache.SelfCities = map.GetCityDataListByPlayerId(player.Id)
cache.SelfTerritoryGridIds = map.GetGridDataSetByPlayerId(player.Id)
cache.VisibleOrKnownGrids = GetVisibleOrKnownGrids(ctx)
对每个 unit in map.UnitMap.UnitList:
如果 unit == null 或 !unit.IsAlive():
继续
owner = map.GetPlayerDataByUnitId(unit.Id)
如果 owner 与 player 同盟:
如果 owner.Id == player.Id:
如果 IsHero(unit):
cache.SelfHeroes.Add(unit)
继续
cache.EnemyUnits.Add(unit)
如果 IsHero(unit):
cache.EnemyHeroes.Add(unit)
对每个 city in map.CityMap.CityList:
owner = map.GetPlayerDataByCityId(city.Id)
如果 owner 存在 且 owner 与 player 非同盟:
cache.EnemyCities.Add(city)
5.3 BuildDiplomacyView
结构 DiplomacyView:
Allies
Enemies
WarPlayers
HighTrustPlayers
LowTrustPlayers
函数 BuildDiplomacyView(ctx, cache):
对每个 otherPlayer in map.PlayerMap.PlayerList:
如果 otherPlayer == player 或 !otherPlayer.Alive:
继续
relation = GetDiplomacy(player, otherPlayer)
如果 SameUnion(player, otherPlayer):
Diplomacy.Allies.Add(otherPlayer)
否则:
Diplomacy.Enemies.Add(otherPlayer)
如果 relation.State == War:
Diplomacy.WarPlayers.Add(otherPlayer)
如果 relation.Feeling >= 80:
Diplomacy.HighTrustPlayers.Add(otherPlayer)
如果 relation.Feeling <= 30:
Diplomacy.LowTrustPlayers.Add(otherPlayer)
5.4 BuildMilitarySummary
函数 BuildMilitarySummary(ctx, cache):
对每个 unit in cache.SelfUnits:
cache.SelfMilitaryValue += UnitMilitaryValue(ctx, unit)
对每个 unit in cache.EnemyUnits:
cache.EnemyMilitaryValue += UnitMilitaryValue(ctx, unit)
函数 UnitMilitaryValue(ctx, unit):
如果 unit == null 或 !unit.IsAlive():
返回 0
value = UnitCostOrMilitary(unit)
如果 HealthRatio(unit) <= 0.5:
value *= 0.5
如果 IsHero(unit):
value += HeroValue(unit)
返回 max(1, value)
5.5 BuildCityThreats
结构 CityThreat:
City
CityGrid
IsCapital
HasWall
EnemyUnits
DefenderUnits
EnemyPower
DefenderPower
RescuePower
NearestEnemyDistance
HasEnemyOnTerritory
CanBeThreatenedNextTurn
DangerScore
IsCritical
函数 BuildCityThreats(ctx, cache):
对每个 city in cache.SelfCities:
threat = BuildSingleCityThreat(ctx, cache, city)
cache.CityThreats.Add(threat)
cache.HasCriticalCityThreat |= threat.IsCritical
cache.HasAnyEnemyContact |= threat.EnemyUnits.Count > 0
cache.CityThreats.SortByDescending(DangerScore)
函数 BuildSingleCityThreat(ctx, cache, city):
cityGrid = city.Grid(ctx.Map)
threat = new CityThreat(city, cityGrid)
threat.IsCapital = city.IsCapital
threat.HasWall = CityHasWall(city)
对每个 enemy in cache.EnemyUnits:
enemyGrid = enemy.Grid(ctx.Map)
d = Distance(cityGrid, enemyGrid)
isInCityTerritory = city.CheckIsInTerritory(enemyGrid.Id)
如果 d <= Config.EmergencyRange + AttackRange(enemy) 或 isInCityTerritory:
threat.EnemyUnits.Add(enemy)
threat.EnemyPower += UnitMilitaryValue(ctx, enemy) / max(1, d)
threat.NearestEnemyDistance = min(threat.NearestEnemyDistance, d)
如果 isInCityTerritory:
threat.HasEnemyOnTerritory = true
如果 CanThreatenCityNextTurn(ctx, enemy, city):
threat.CanBeThreatenedNextTurn = true
对每个 unit in cache.SelfUnits:
unitGrid = unit.Grid(ctx.Map)
d = Distance(cityGrid, unitGrid)
如果 d <= Config.EmergencyRange:
threat.DefenderUnits.Add(unit)
threat.DefenderPower += UnitMilitaryValue(ctx, unit) / max(1, d)
如果 d <= Config.FrontRange:
threat.RescuePower += UnitMilitaryValue(ctx, unit) / max(1, d)
threat.DangerScore =
threat.EnemyPower
- threat.DefenderPower
+ (threat.HasEnemyOnTerritory ? 4 : 0)
+ (threat.CanBeThreatenedNextTurn ? 6 : 0)
+ (threat.IsCapital ? 3 : 0)
- (threat.HasWall ? 2 : 0)
threat.IsCritical =
threat.CanBeThreatenedNextTurn
或 threat.EnemyUnits.Count >= Config.CityCriticalEnemyCount
或 threat.HasEnemyOnTerritory
或 threat.EnemyPower > threat.DefenderPower * Config.CityThreatPowerRatio
返回 threat
函数 CanThreatenCityNextTurn(ctx, enemy, city):
enemyGrid = enemy.Grid(ctx.Map)
cityGrid = city.Grid(ctx.Map)
reach = MoveRange(enemy) + AttackRange(enemy)
如果 enemy 有 Capture 行动能力:
reach += 1
如果 Distance(enemyGrid, cityGrid) <= reach:
返回 true
返回 false
5.6 BuildStrategicPosture
枚举 StrategicPosture:
Defense
Expansion
Attack
Development
函数 BuildStrategicPosture(ctx, cache):
如果 cache.HasCriticalCityThreat:
cache.StrategicPosture = Defense
返回
如果 ExistsHighValueVillageOrRuinNearCities(ctx, cache):
cache.StrategicPosture = Expansion
返回
attackTargets = BuildAttackTargetPlayers(ctx, cache)
如果 attackTargets.Count > 0:
cache.StrategicPosture = Attack
cache.WarTargetPlayers = attackTargets
返回
cache.StrategicPosture = Development
函数 BuildAttackTargetPlayers(ctx, cache):
targets = []
对每个 enemyPlayer in cache.Diplomacy.Enemies:
如果 enemyPlayer in cache.Diplomacy.Allies:
继续
nearestDistance = NearestCityDistance(ctx.Player, enemyPlayer)
militaryGap = MilitaryGapWithCounter(ctx, ctx.Player, enemyPlayer)
threat = ThreatFromPlayer(ctx, enemyPlayer)
feeling = GetFeeling(ctx.Player, enemyPlayer)
如果 enemyPlayer in Diplomacy.WarPlayers:
targets.Add(enemyPlayer)
继续
如果 feeling <= 30:
targets.Add(enemyPlayer)
继续
如果 nearestDistance <= 5 且 militaryGap >= 0:
targets.Add(enemyPlayer)
继续
如果 threat >= 10 且 militaryGap >= -3 且 nearestDistance <= 7:
targets.Add(enemyPlayer)
继续
返回 targets 按 AttackTargetScore 降序
5.7 BuildCityPlans
枚举 CityPlanKind:
EmergencyDefense
Mobilize
Frontline
BacklineGrowth
Wonder
结构 CityPlan:
City
CityGrid
Kind
Threat
NeedWall
NeedMilitary
NeedGrowth
NeedWonder
Priority
函数 BuildCityPlans(ctx, cache):
对每个 city in cache.SelfCities:
threat = FindThreatForCity(cache, city)
plan = new CityPlan(city, threat)
如果 threat.IsCritical:
plan.Kind = EmergencyDefense
否则如果 threat.DangerScore > 0:
plan.Kind = Mobilize
否则如果 IsNearAttackFront(ctx, cache, city):
plan.Kind = Frontline
否则如果 CanReasonablyPursueWonder(ctx, city):
plan.Kind = Wonder
否则:
plan.Kind = BacklineGrowth
plan.NeedWall = plan.Kind in [EmergencyDefense, Mobilize, Frontline] 且 !CityHasWall(city)
plan.NeedMilitary = plan.Kind in [EmergencyDefense, Mobilize, Frontline]
plan.NeedGrowth = plan.Kind in [BacklineGrowth, Wonder]
plan.NeedWonder = plan.Kind == Wonder
plan.Priority = ScoreCityPlan(ctx, plan)
cache.CityPlans.Add(plan)
cache.CityPlans.SortByDescending(Priority)
5.8 BuildDevelopmentTargets
枚举 DevelopmentTargetType:
Village
EnemyEmptyCity
Treasure
Ruin
Resource
FogBoundary
结构 DevelopmentTarget:
Grid
Type
Value
NearestSelfCity
Distance
函数 BuildDevelopmentTargets(ctx, cache):
targets = []
对每个 grid in cache.VisibleOrKnownGrids:
target = TryBuildDevelopmentTarget(ctx, cache, grid)
如果 target == null:
继续
如果 target.Distance > Config.DevelopmentSearchRange:
继续
如果 GridThreatToAnySelfUnit(ctx, grid) 太高 且 target.Type 不是 EnemyEmptyCity:
继续
targets.Add(target)
targets.SortByDescending(Value - DistancePenalty)
cache.DevelopmentTargets = targets.Take(Config.MaxDevelopmentTargetCount)
函数 TryBuildDevelopmentTarget(ctx, cache, grid):
如果 grid.Resource == CityCenter 且 grid 上没有己方城市:
返回 DevelopmentTarget(grid, Village, 900)
如果 grid 是敌方空城中心:
返回 DevelopmentTarget(grid, EnemyEmptyCity, 950)
如果 grid 有遗迹或宝物可探索:
返回 DevelopmentTarget(grid, Treasure, 820)
如果 grid 有可采集或可开发高价值资源:
返回 DevelopmentTarget(grid, Resource, 650 + ResourceValue(ctx, grid))
如果 grid 是安全探索边界:
返回 DevelopmentTarget(grid, FogBoundary, 420)
返回 null
可占城中心的识别规则:
函数 ResolveExpansionCityTarget(ctx, city):
owner = GetPlayerDataByCityId(city.Id)
如果 owner == null:
返回 Village
如果 owner 与 ctx.Player 同盟:
返回 None
如果 city 中心可被 Capture 行为占领:
返回 EnemyEmptyCity
返回 None
说明:TH1 的地图数据里,有些策划意义上的“村庄/空城”已经存在 CityData 和 owner 映射。
所以 Expansion 不能只认 owner 为空的城;二城前也必须把可占的非同盟城中心当作扩张目标处理。
5.9 BuildFronts
枚举 FrontType:
Defense
Attack
Development
Hold
结构 Front:
Type
AnchorGrid
TargetGrid
City
TargetCity
TargetPlayer
Pressure
Opportunity
Distance
Priority
函数 BuildFronts(ctx, cache):
fronts = []
fronts.AddRange(BuildDefenseFronts(ctx, cache))
fronts.AddRange(BuildAttackFronts(ctx, cache))
fronts.AddRange(BuildDevelopmentFronts(ctx, cache))
fronts.AddRange(BuildHoldFronts(ctx, cache))
fronts.SortByDescending(Priority)
cache.Fronts = fronts.Take(Config.MaxFrontCount)
函数 BuildDefenseFronts(ctx, cache):
对每个 threat in cache.CityThreats:
如果 threat.DangerScore <= 0:
继续
yield Front(
Type = Defense,
AnchorGrid = threat.CityGrid,
City = threat.City,
Pressure = threat.DangerScore,
Priority = 1000 + threat.DangerScore
)
函数 BuildAttackFronts(ctx, cache):
对每个 targetCity in cache.EnemyCities:
owner = targetCity.Owner
如果 owner 不在 cache.WarTargetPlayers 且 cache.StrategicPosture != Attack:
继续
selfCity = NearestSelfCity(targetCity.Grid)
distance = Distance(selfCity.Grid, targetCity.Grid)
opportunity = EstimateCityAttackOpportunity(ctx, targetCity)
yield Front(
Type = Attack,
AnchorGrid = selfCity.Grid,
TargetGrid = targetCity.Grid,
City = selfCity,
TargetCity = targetCity,
TargetPlayer = owner,
Distance = distance,
Opportunity = opportunity,
Priority = 700 + opportunity - distance
)
函数 BuildDevelopmentFronts(ctx, cache):
对每个 target in cache.DevelopmentTargets:
如果 己方城市数 < Config.ExpansionUrgentCityThreshold 且 target.Type in [Village, EnemyEmptyCity]:
继续
selfCity = target.NearestSelfCity
yield Front(
Type = Development,
AnchorGrid = selfCity.Grid,
TargetGrid = target.Grid,
City = selfCity,
Distance = target.Distance,
Opportunity = target.Value,
Priority = 500 + target.Value - target.Distance * 8
)
函数 BuildHoldFronts(ctx, cache):
对每个 city in cache.SelfCities:
如果 city 是首都 或 高等级后方城市:
yield Front(
Type = Hold,
AnchorGrid = city.Grid,
City = city,
Priority = 300 + CityValue(city)
)
5.10 BuildLocalBattles
结构 LocalBattle:
SelfUnit
EnemyUnit
SelfGrid
EnemyGrid
Distance
AttackAction
Value
函数 BuildLocalBattles(ctx, cache):
battles = []
对每个 self in cache.SelfUnits:
对每个 enemy in cache.EnemyUnits:
d = Distance(self.Grid, enemy.Grid)
如果 d > Config.LocalBattleRange + AttackRange(self):
继续
value = EstimateAttackValue(ctx, self, enemy)
如果 enemy 正在威胁城市:
value += 120
如果 enemy 是英雄:
value += 100
如果 CanKill(ctx, self, enemy):
value += 160
battles.Add(LocalBattle(self, enemy, d, value))
cache.LocalBattles = battles.SortByDescending(Value)
5.11 BuildHeroStates
枚举 HeroContext:
Recovery
Defense
FieldBattle
Siege
Economy
Mobility
Control
结构 HeroState:
Hero
GiantType
Level
HealthRatio
Context
NearestFront
IsThreatened
IsOnFront
函数 BuildHeroStates(ctx, cache):
对每个 hero in cache.SelfHeroes:
state = new HeroState
state.Hero = hero
state.GiantType = GetHeroGiantType(hero)
state.Level = GetHeroLevel(ctx.Player, state.GiantType)
state.HealthRatio = HealthRatio(hero)
state.NearestFront = FindNearestFront(cache.Fronts, hero.Grid)
state.IsOnFront = state.NearestFront != null 且 Distance(hero.Grid, state.NearestFront.AnchorGrid) <= Config.FrontRange
state.IsThreatened = state.HealthRatio <= Config.HeroLowHpRatio 或 GridThreat(ctx, hero, hero.Grid) > 0
state.Context = ResolveHeroContext(ctx, state)
cache.HeroStates.Add(state)
函数 ResolveHeroContext(ctx, state):
如果 state.HealthRatio <= Config.HeroLowHpRatio:
返回 Recovery
如果 state.NearestFront.Type == Defense:
返回 Defense
如果 state.NearestFront.Type == Attack:
返回 Siege
如果 state.IsOnFront 或 HasEnemyNear(ctx, state.Hero, Config.LocalBattleRange):
返回 FieldBattle
如果 HeroRole(state.GiantType) == Economy:
返回 Economy
如果 HeroRole(state.GiantType) == Mobility:
返回 Mobility
如果 HeroRole(state.GiantType) == Control:
返回 Control
返回 FieldBattle
5.12 BuildUnitOpportunities
结构 UnitOpportunity:
Unit
Action
Type
TargetGrid
TargetUnit
Value
函数 BuildUnitOpportunities(ctx):
opportunities = []
对每个 action in ctx.Actions.UnitActions:
unit = action.Param.UnitData
type = action.ActionId.UnitActionType
value = ScoreUnitOpportunity(ctx, unit, action, type)
如果 value <= 0:
继续
opportunities.Add(UnitOpportunity(unit, action, type, value))
ctx.Cache.UnitOpportunities = opportunities.SortByDescending(Value)
6. Emergency Lane
6.1 TryEmergency
函数 TryEmergency(ctx):
对每个 threat in ctx.Cache.CityThreats:
如果 !ShouldUseEmergency(threat):
继续
candidate = TryEmergencyCityProduction(ctx, threat)
如果 IsUrgentCityDefenseAction(candidate):
返回 candidate
如果 threat.CityGrid 没有己方驻守 且 threat.CanBeThreatenedNextTurn:
attack = TryEmergencyAttack(ctx, threat)
如果 attack 能直接击杀最后一个威胁单位:
返回 attack
move = TryEmergencyMoveToCity(ctx, threat)
如果 move.IsValid:
返回 move
如果 attack.IsValid:
返回 attack
candidate = TryEmergencyAttack(ctx, threat)
如果 candidate.IsValid:
返回 candidate
candidate = TryEmergencyMoveToCity(ctx, threat)
如果 candidate.IsValid:
返回 candidate
candidate = TryEmergencyCityProduction(ctx, threat)
如果 candidate.IsValid:
返回 candidate
返回 None
函数 IsUrgentCityDefenseAction(candidate):
如果 !candidate.IsValid:
返回 false
如果 candidate.ActionId.ActionType == TrainUnit:
返回 true
如果 candidate.ActionId.ActionType == CityAction 且 candidate.ActionId.CityActionType == BuildCityWall:
返回 true
返回 false
函数 ShouldUseEmergency(threat):
如果 threat == null:
返回 false
如果 threat.HasEnemyOnTerritory:
返回 true
如果 threat.EnemyUnits.Count >= Config.CityCriticalEnemyCount:
返回 true
如果 threat.EnemyPower > threat.DefenderPower * Config.CityThreatPowerRatio
且 threat.EnemyUnits.Count >= Config.CityDangerEnemyCount:
返回 true
如果 threat.DangerScore >= Config.EmergencyDangerScore:
返回 true
如果 threat.IsCritical 且 threat.CanBeThreatenedNextTurn 且 threat.DefenderPower <= 0:
返回 true
返回 false
6.2 EmergencyAttack
函数 TryEmergencyAttack(ctx, threat):
best = None
对每个 defender in threat.DefenderUnits:
action = FindBestUsefulAttack(ctx, defender, BestThreatTarget(ctx, threat))
如果 action == None:
action = FindBestUsefulAttack(ctx, defender)
candidate = Candidate(action, Emergency, ScoreEmergencyAttack(ctx, action, threat), "守军攻击城市威胁")
best = MaxCandidate(best, candidate)
返回 best
函数 ScoreEmergencyAttack(ctx, action, threat):
target = action.TargetUnitData
score = 900
score += ThreatTargetValue(ctx, target)
如果 target 下回合可威胁 threat.City:
score += 180
如果 CanKillByAction(ctx, action):
score += 160
如果 threat.CityGrid 没有己方驻守 且 threat.CanBeThreatenedNextTurn:
如果 本次攻击不能直接击杀最后一个威胁单位:
score -= threat.IsCapital ? 340 : 240
如果 action.Unit 是该威胁的唯一守军 或 action.Unit 本可回到城心:
score -= 180
返回 score
6.3 EmergencyMoveToCity
函数 TryEmergencyMoveToCity(ctx, threat):
best = None
allowStackedCityGuard = IsUnresolvedEmptyCityCoreThreat(threat)
如果 !allowStackedCityGuard:
intentKey = BuildIntentKey(Emergency, "MoveToCity", threat.City.Id)
如果 IsIntentBlocked(ctx, intentKey):
返回 None
如果 IsIntentBudgetReached(ctx, Emergency, Config.MaxEmergencyMoveIntentsPerTurn):
返回 None
否则:
intentKey = ""
对每个 unit in ctx.Cache.SelfUnits:
如果 !CanUseAsDefender(ctx, unit, threat):
继续
startGrid = unit.Grid(ctx.Map)
startDistance = Distance(startGrid, threat.CityGrid)
如果 !ShouldScanEmergencyResponder(ctx, threat, unit, startGrid, startDistance, scannedResponders):
继续
scannedResponders += 1
action = FindBestMoveToward(ctx, unit, threat.CityGrid)
endGrid = GetActionEndGrid(action)
如果 !IsUsefulMoveToward(ctx, startGrid, endGrid, threat.CityGrid):
继续
如果 unit 正站在某个危险城市中心 且 endGrid 会离开当前城心:
继续
candidate = Candidate(action, Emergency, ScoreEmergencyMove(ctx, unit, action, threat), "回防城市")
candidate.IntentKey = intentKey
best = MaxCandidate(best, candidate)
返回 best
函数 ShouldScanEmergencyResponder(ctx, threat, unit, startGrid, startDistance, scannedResponders):
如果 startGrid 为空:
返回 false
如果 startDistance > Config.MaxEmergencyRescueDistance 且 !threat.IsCritical:
返回 false
如果 startDistance > Config.MaxEmergencyRescueDistance + 2 且 unit 不是英雄:
返回 false
如果 Config.MaxEmergencyResponderScanCount <= 0:
返回 true
如果 scannedResponders < Config.MaxEmergencyResponderScanCount:
返回 true
如果 unit 是英雄 且 startDistance <= Config.MaxEmergencyRescueDistance + 1:
返回 true
如果 unit 是 Defender 且 startDistance <= 2:
返回 true
返回 false
函数 ScoreEmergencyMove(ctx, unit, action, threat):
startGrid = unit.Grid(ctx.Map)
endGrid = GetActionEndGrid(action)
score = 900
score += threat.DangerScore * 20
score += UnitMilitaryValue(ctx, unit)
score += max(0, Distance(startGrid, threat.CityGrid) - Distance(endGrid, threat.CityGrid)) * 55
score -= Distance(endGrid, threat.CityGrid) * 14
如果 unit 是英雄:
score += 60
如果 threat.CityGrid 没有己方驻守:
如果 Distance(endGrid, threat.CityGrid) == 0:
score += 360
否则:
score += max(0, 220 - Distance(endGrid, threat.CityGrid) * 35)
如果 threat.IsCapital:
score += 140
返回 score
6.4 EmergencyCityProduction
函数 TryEmergencyCityProduction(ctx, threat):
plan = FindCityPlan(threat.City)
actions = ctx.Actions.ByCity[threat.City.Id]
best = None
对每个 action in actions:
score = ScoreEmergencyCityAction(ctx, action, plan, threat)
candidate = Candidate(action, Emergency, score, "危险城市生产或防御")
best = MaxCandidate(best, candidate)
返回 best
函数 ScoreEmergencyCityAction(ctx, action, plan, threat):
id = action.ActionId
score = 0
如果 id.ActionType == TrainUnit:
score = 760 + TrainUnitDefenseValue(ctx, action, threat)
如果 threat.DefenderPower <= 0:
score += 240
如果 threat.IsCapital:
score += 120
如果 threat.CanBeThreatenedNextTurn:
score += 100
如果 threat.CityGrid 没有己方驻守:
score += threat.IsCapital ? 300 : 220
如果 id.ActionType == CityAction 且 id.CityActionType == BuildCityWall:
score = 740
如果 threat.DefenderPower <= 0:
score += 180
如果 id.ActionType == CityLevelUpAction 且 能选城墙或人口防守收益:
score = 700
如果 id.ActionType in [StartWonder, BuildWonder]:
score = 0
返回 score
7. HeroManagement Lane
7.1 TryHeroManagement
函数 TryHeroManagement(ctx):
candidate = TrySelectHero(ctx)
如果 candidate.IsValid:
返回 candidate
candidate = TryBuyHeroSlotCultureCard(ctx)
如果 candidate.IsValid 且 ShouldPrioritizeHeroSlotBeforeSpawn(ctx):
返回 candidate
candidate = TrySpawnSelectedHero(ctx)
如果 candidate.IsValid:
返回 candidate
candidate = TryBuyHeroSlotCultureCard(ctx)
如果 candidate.IsValid:
返回 candidate
candidate = TryForceFinishHeroTask(ctx)
如果 candidate.IsValid:
返回 candidate
返回 None
7.2 SelectHero
函数 TrySelectHero(ctx):
action = FindHeroManagementAction(ctx, SelectHero)
返回 Candidate(action, HeroManagement, 890, "选择英雄")
7.3 SpawnSelectedHero
函数 TrySpawnSelectedHero(ctx):
如果 ctx.Player 没有已选择英雄:
返回 None
best = None
对每个 cityPlan in ctx.Cache.CityPlans:
对每个 action in ctx.Actions.ByCity[cityPlan.City.Id]:
如果 !IsSelectedHeroSpawnAction(ctx.Player, action):
继续
score = 880
score += max(cityPlan.Priority, 0) * 0.08
如果 cityPlan.Kind == EmergencyDefense:
score += 40
如果 cityPlan.Kind == Mobilize:
score += 30
如果 cityPlan.Kind == Frontline:
score += 25
candidate = Candidate(action, HeroManagement, score, "英雄出场")
candidate.City = cityPlan.City
candidate.TargetGrid = cityPlan.CityGrid
best = MaxCandidate(best, candidate)
如果 best.IsValid:
返回 best
对每个 action in ctx.Actions.CityActions:
如果 IsSelectedHeroSpawnAction(ctx.Player, action):
candidate = Candidate(action, HeroManagement, 880, "英雄出场")
best = MaxCandidate(best, candidate)
返回 best
函数 IsSelectedHeroSpawnAction(player, action):
id = action.ActionId
返回 id.ActionType == TrainUnit
且 id.UnitType == Giant
且 id.GiantType != None
且 player.PlayerHeroData.HasHero(id.GiantType)
英雄出场仍然走 TrainUnitAction 的 CheckCan 和 Execute。AI 只负责更早选择这个合法动作,不直接改变英雄状态。
7.4 BuyHeroSlotCultureCard
函数 TryBuyHeroSlotCultureCard(ctx):
action = FindHeroSlotCultureCard(ctx.Player)
如果 action == None:
返回 None
score = ScoreCultureCard(ctx, action.CultureCardType)
返回 Candidate(action, HeroManagement, score, "购买英雄槽位卡")
函数 ShouldPrioritizeHeroSlotBeforeSpawn(ctx):
heroData = ctx.Player.PlayerHeroData
如果 heroData == null:
返回 false
如果 heroData.MaxHeroCount >= 3:
返回 false
如果 ctx.Cache.SelfHeroes.Count < heroData.HeroCount:
返回 false
如果 heroData.HeroCount > 0 且 heroData.HeroCount >= heroData.MaxHeroCount:
返回 true
返回 false
7.5 FinishHeroTask
函数 TryForceFinishHeroTask(ctx):
如果 ctx.Player.Turn < Config.HeroTaskStartTurn:
返回 None
如果 ctx.Player.Turn % Config.HeroTaskInterval != 0:
返回 None
best = None
对每个 action in ctx.Actions.HeroManagementActions:
如果 action.PlayerActionType != FinishHeroTask:
继续
giant = action.ActionId.GiantType
level = GetHeroLevel(ctx.Player, giant)
如果 !CanForceFinishHeroTask(ctx, giant, level):
继续
score = 860 - level * 20
candidate = Candidate(action, HeroManagement, score, "推进最低等级英雄任务")
best = MaxCandidate(best, candidate)
返回 best
函数 CanForceFinishHeroTask(ctx, giant, level):
如果 HeroTask 已完成或已强制完成:
返回 false
如果 level == 0:
返回 true
如果 level == 1:
返回 ctx.Player.Turn >= Config.HeroLevel2MinTurn
如果 level == 2:
返回 ctx.Player.Turn >= Config.HeroLevel3MinTurn
返回 false
8. Expansion Lane
Expansion Lane 只处理早期扩张的刚性目标,不处理普通资源开发。 它的职责是让 AI 在没有严重城防危机时,优先拿到第二座城市或明显高价值的可占城中心。
8.1 TryExpansion
函数 TryExpansion(ctx):
如果 !ShouldPushExpansion(ctx):
返回 None
best = None
scannedTargetCount = 0
requireSafeUrgentTarget = 有严重城市威胁
对每个 target in ctx.Cache.DevelopmentTargets:
如果 !IsExpansionTarget(ctx, target):
继续
如果 requireSafeUrgentTarget 且 !IsSafeUrgentExpansionTarget(ctx, target):
继续
如果 Config.MaxExpansionTargetScanCount > 0 且 scannedTargetCount >= Config.MaxExpansionTargetScanCount:
break
scannedTargetCount += 1
capture = TryExpansionCapture(ctx, target)
best = MaxCandidate(best, capture)
move = TryExpansionMove(ctx, target)
best = MaxCandidate(best, move)
返回 best
8.2 ShouldPushExpansion
函数 ShouldPushExpansion(ctx):
如果 有严重城市威胁 且 不存在安全紧急扩张目标:
返回 false
如果 ctx.Cache.DevelopmentTargets 为空:
返回 false
如果 己方城市数 < Config.ExpansionUrgentCityThreshold:
返回 true
如果 当前回合 <= Config.ExpansionHardPressureTurn 且存在扩张目标:
返回 true
返回 false
函数 HasSafeUrgentExpansionTarget(ctx):
对每个 target in ctx.Cache.DevelopmentTargets:
如果 IsSafeUrgentExpansionTarget(ctx, target):
返回 true
返回 false
函数 IsSafeUrgentExpansionTarget(ctx, target):
如果 己方城市数 >= Config.ExpansionUrgentCityThreshold:
返回 false
如果 target.Type 不在 [Village, EnemyEmptyCity]:
返回 false
如果 target.Distance > 3:
返回 false
如果 GridThreat(ctx, target.Grid) > 0:
返回 false
返回 true
8.3 IsExpansionTarget
函数 IsExpansionTarget(ctx, target):
如果 target == null 或 target.Grid == null:
返回 false
如果 target.Type == Village:
返回 true
如果 target.Type == EnemyEmptyCity:
如果 target.Distance > Config.DevelopmentSearchRange:
返回 false
如果 己方城市数 < Config.ExpansionUrgentCityThreshold:
返回 true
如果 当前回合 > Config.ExpansionHardPressureTurn:
返回 true
返回 false
8.4 TryExpansionCapture
函数 TryExpansionCapture(ctx, target):
best = None
对每个 action in ctx.Actions.UnitActions:
如果 action.UnitActionType != Capture:
继续
unit = action.Unit
unitGrid = unit.Grid
如果 unitGrid != target.Grid:
继续
riskPenalty = ExpansionTargetRiskPenalty(ctx, target, unitGrid)
score = ScoreExpansionTarget(ctx, target) + 420 - riskPenalty
如果 score <= 0:
继续
candidate = Candidate(action, Expansion, score, "扩张占领")
best = MaxCandidate(best, candidate)
返回 best
8.5 TryExpansionMove
函数 TryExpansionMove(ctx, target):
best = None
intentKey = BuildIntentKey(Expansion, "Move." + target.Type, target.Grid.Id)
如果 IsIntentBlocked(ctx, intentKey):
返回 None
如果 IsIntentBudgetReached(ctx, Expansion, Config.MaxExpansionMoveIntentsPerTurn):
返回 None
对每个 unit in ctx.Cache.SelfUnits:
如果 unit 没有移动行动点:
继续
startGrid = unit.Grid
如果 startGrid == target.Grid:
继续
startDistance = Distance(startGrid, target.Grid)
如果 ShouldSkipExpansionUnit(ctx, unit, target, startGrid, startDistance):
继续
如果 unit 是脆弱特殊衍生单位 且 target.Grid 有即时威胁:
继续
action = FindBestMove(unit, target.Grid)
endGrid = ActionEndGrid(action)
如果 endGrid == null:
继续
如果 endGrid 没有更接近 target 且 startGrid 距离 target > 1:
继续
score = ScoreExpansionTarget(ctx, target)
score += 接近距离 * 90
如果 endGrid == target.Grid: score += 360
如果 endGrid 距离 target == 1: score += 160
如果 unit 是高机动单位: score += 80
如果 startGrid 距离 target >= 5 且 本次只推进 1 格:
score -= 120
score -= endGrid 到 target 的距离 * 28
score -= GridThreat(ctx, endGrid) * ThreatFactor(target)
riskPenalty = ExpansionTargetRiskPenalty(ctx, target, endGrid)
score -= riskPenalty
如果 score <= 0:
继续
candidate = Candidate(action, Expansion, score, "扩张移动")
candidate.IntentKey = intentKey
best = MaxCandidate(best, candidate)
返回 best
8.6 ScoreExpansionTarget
函数 ScoreExpansionTarget(ctx, target):
score = 740 + target.Value
如果 target.Type == Village:
score += 520
如果 target.Type == EnemyEmptyCity:
score += 180
如果 己方城市数 < Config.ExpansionUrgentCityThreshold:
score += 360
如果 当前回合 <= Config.ExpansionHardPressureTurn:
score += 180
score -= target.Distance * 20
返回 score
函数 ExpansionTargetRiskPenalty(ctx, target, actingGrid):
penalty = GridThreat(ctx, target.Grid) * 0.75
如果 actingGrid != null:
penalty += GridThreat(ctx, actingGrid) * 0.25
如果 target.Type == EnemyEmptyCity:
penalty += GridThreat(ctx, target.Grid) * 0.35
如果 有严重城市威胁:
penalty += 180
如果 己方单位数 <= 己方城市数 + 1 且 GridThreat(ctx, target.Grid) > 0:
penalty += 120
返回 penalty
函数 ShouldSkipExpansionUnit(ctx, unit, target, startGrid, startDistance):
如果 startDistance > Config.DevelopmentSearchRange + 2:
返回 true
如果 unit 低血且 startGrid 有威胁:
返回 true
如果 unit 不是危险城市守军:
返回 false
如果 unit 是危险城市唯一守军:
返回 true
如果 己方单位数 <= 己方城市数 + 1:
返回 true
如果 target.Grid 有威胁:
返回 true
返回 false
9. HeroPlaybook Lane
9.1 TryHeroPlaybook
函数 TryHeroPlaybook(ctx):
best = None
对每个 state in ctx.Cache.HeroStates:
candidate = EvaluateHeroPlaybook(ctx, state)
best = MaxCandidate(best, candidate)
返回 best
9.2 HeroRule
结构 HeroRule:
GiantType
MinLevel
Context
ActionKind:
UnitAction
AttackEnemy
AttackAlly
AttackGround
MoveToward
MoveRetreat
Hold
UnitActionType
TargetPolicy
Priority
Condition
Reason
9.3 EvaluateHeroPlaybook
函数 EvaluateHeroPlaybook(ctx, state):
rules = GetHeroRules(state.GiantType)
best = None
对每个 rule in rules:
如果 state.Level < rule.MinLevel:
继续
如果 !HeroContextMatches(ctx, state, rule.Context):
继续
如果 !rule.Condition(ctx, state):
继续
action = BuildHeroAction(ctx, state, rule)
candidate = Candidate(action, HeroPlaybook, ScoreHeroRule(ctx, state, rule, action), rule.Reason)
best = MaxCandidate(best, candidate)
如果 best.IsValid:
返回 best
返回 EvaluateGenericHeroRule(ctx, state)
9.4 BuildHeroAction
函数 BuildHeroAction(ctx, state, rule):
hero = state.Hero
targetUnit = ResolveHeroTargetUnit(ctx, state, rule.TargetPolicy)
targetGrid = ResolveHeroTargetGrid(ctx, state, rule.TargetPolicy)
如果 rule.ActionKind == UnitAction:
返回 FindUnitAction(ctx, hero, rule.UnitActionType)
如果 rule.ActionKind == AttackEnemy:
返回 FindBestAttack(ctx, hero, targetUnit)
如果 rule.ActionKind == AttackAlly:
返回 FindBestAttackAlly(ctx, hero, targetUnit)
如果 rule.ActionKind == AttackGround:
返回 FindBestAttackGround(ctx, hero, targetGrid)
如果 rule.ActionKind == MoveToward:
返回 FindBestMoveToward(ctx, hero, targetGrid)
如果 rule.ActionKind == MoveRetreat:
retreatGrid = FindLowestThreatGrid(ctx, hero)
返回 FindBestMoveToward(ctx, hero, retreatGrid)
返回 null
9.5 通用英雄保命
函数 EvaluateGenericHeroRule(ctx, state):
hero = state.Hero
如果 state.HealthRatio <= Config.CriticalHpRatio:
action = FindUnitAction(ctx, hero, Recover)
如果 action 可用:
返回 Candidate(action, HeroPlaybook, 840, "英雄低血恢复")
retreat = FindLowestThreatGrid(ctx, hero)
move = FindBestMoveToward(ctx, hero, retreat)
如果 move 可用:
返回 Candidate(move, HeroPlaybook, 820, "英雄低血撤退")
attack = FindBestUsefulAttack(ctx, hero, BestHeroOrKillableEnemy(ctx, hero))
如果 attack 可用:
roleBonus = 0
如果 state.Role 是 Assassin:
roleBonus = 140
否则如果 state.Role 是 Vanguard:
roleBonus = 115
否则如果 state.Role 是 Caster 或 Control:
roleBonus = 75
返回 Candidate(attack, HeroPlaybook, 760 + TargetValue(attack.Target) + roleBonus, "英雄通用高价值攻击")
如果 state.Role 是 Caster 或 Control:
ground = FindBestAttackGround(ctx, hero, state.Front.TargetGrid 或 state.Front.AnchorGrid 或 PrimaryFront.TargetGrid)
如果 ground 可用:
score = 710
如果 state.IsOnFront:
score += 80
如果 ctx.Cache.HasAnyEnemyContact:
score += 60
返回 Candidate(ground, HeroPlaybook, score, "英雄通用地面控制")
target = state.Front.TargetGrid 或 state.Front.AnchorGrid
move = FindBestMoveToward(ctx, hero, target)
score = ScoreHeroFrontMove(ctx, state, move, target)
如果 move 可用 且 score > 0:
返回 Candidate(move, HeroPlaybook, score, "英雄前线补位")
返回 None
函数 ScoreHeroFrontMove(ctx, state, move, target):
如果 move 不存在 或 target 不存在:
返回 0
start = hero 当前格
end = move 目标格
如果 end 没有缩短到 target 的距离:
返回 0
如果 hero 已在前线 且 end 距离 target 仍大于 1:
返回 0
如果 hero 是 Support / Economy 且当前不是 Defense front:
返回 0
如果 hero 是 Caster / Control 且当前没有敌我接触 且 end 距离 target 大于 2:
返回 0
score = 500
score += 缩短距离 * 30
score -= end 到 target 距离 * 18
如果 front 是 Defense:
score += 70
如果 hero 是 Mobility:
score += 45
否则如果 hero 是 Vanguard / Assassin:
score += 20
如果 hero 是 Caster / Control:
score -= 45
如果 hero 已受威胁:
如果低血: score -= 170
否则: score -= 60
返回 score
9.6 默认英雄规则
Flandre:
如果有可击杀高价值敌人 -> AttackEnemy(KillableEnemy)
如果友军低血且攻击友军能触发收益 -> AttackAlly(InjuredAlly)
Remilia:
如果接战且红雾/增益不足 -> UnitAction(REMILIABUFF)
如果低血且可吸收 -> UnitAction(REMILIAABSORB)
Sakuya:
如果有可击杀目标 -> AttackEnemy(KillableEnemy)
如果残血英雄或关键友军附近有敌人 -> MoveToward(ProtectAllyGrid)
如果可用保护型友军攻击 -> AttackAlly(InjuredAlly)
Meiling:
如果低血 -> UnitAction(Recover)
如果城市防守缺前排 -> MoveToward(DefenseFront)
Patchouli:
如果元素/地面攻击收益可触发 -> AttackGround(BestGroundTarget)
如果友军缺血且元素治疗可用 -> AttackAlly(InjuredAlly)
如果危险高 -> MoveRetreat
Kaguya:
如果 Lv>=2 且周围友军占敌城或低血 -> UnitAction(KAGUYAFRENCHAROUND)
如果射程内友军占敌城或低血 -> AttackAlly(BestAlly)
Reisen:
如果真实伤害可击杀 -> AttackEnemy(KillableEnemy)
如果敌人密集且爆破可用 -> UnitAction(ReisenFrenchBoom)
Tewi:
如果 Lv>=3 且相邻有作战友军 -> UnitAction(TEWIFRENCHBUFF)
Eirin:
如果射程内残血英雄 -> AttackAlly(InjuredHero)
如果射程内敌方英雄且无治疗目标 -> AttackEnemy(BestEnemyHero)
如果射程内高价值友军需要治疗或协同 -> AttackAlly(BestAlly)
Mokou:
如果 Lv>=3 且低血且敌人密集且友军风险低 -> UnitAction(MOKOUFRENCHBOOM)
如果蛋形态低血 -> MoveRetreat
如果蛋形态安全且需要牵制 -> MoveToward(EnemyDenseGrid)
Kanako:
如果坐镇且需要移动或接敌 -> UnitAction(KANAKOUNSIT)
如果安全且可坐镇且附近有战略收益 -> UnitAction(KANAKOSIT)
Suwako:
如果可攻击地块并能召唤/压制 -> AttackGround(FarthestUsefulGround)
Sanae:
如果射程内残血友军 -> AttackAlly(InjuredAlly)
如果射程内敌方英雄且无治疗目标 -> AttackEnemy(BestEnemyHero)
Aya:
如果二动能追击、回防或进入高收益格 -> UnitAction(AYAMOVEAGAIN)
移动优先选接近敌人但威胁可控的远端格
Momiji:
如果猎物标记目标可攻击 -> AttackEnemy(PreyTarget)
如果猎物标记目标附近有安全格 -> MoveToward(PreyGrid)
Satori:
如果可攻击未被封禁的敌方英雄 -> AttackEnemy(BestEnemyHeroWithoutSkillBan)
如果恐惧目标收益高 -> AttackEnemy(FearTarget)
Koishi:
如果骨堆友军不满血 -> MoveToward(BonePileAlly)
如果可友军施法给骨堆目标 -> AttackAlly(BonePileAlly)
Rin:
如果有 CorpseBuff 层数且可行动 -> UnitAction(KomeijiRinFire)
如果城市经验转换收益更高 -> UnitAction(KomeijiRinCityExp)
Utsuho:
如果骨堆爆破命中价值高 -> UnitAction(KomeijiBonePileBoom)
如果移动范围内有敌方城市中心 -> MoveToward(EnemyCityCenter)
Yuugi:
如果三格移动可进入高攻击收益格 -> MoveToward(BestAttackGrid)
否则攻击高价值近战目标
Reimu:
如果关键友军低血或即将承伤 -> AttackAlly(InjuredAlly)
如果退魔层数/压力过高 -> UnitAction(ReimuPayClearExtermination)
Sumireko:
如果灵球/地面目标能控线 -> AttackGround(BestGroundTarget)
Kasen:
如果高压战斗且切形态收益高 -> UnitAction(KasenToggleOniForm)
Aunn:
如果防守线需要支援 -> MoveToward(DefenseFront)
保持与支援对象距离不过远
Suika:
如果安全且生成小萃香收益高 -> UnitAction(SuikaCreateMiniByHp)
如果被围攻或小萃香可解围 -> UnitAction(SuikaShakeOffMinis)
Byakuren / Miko / Zanmu:
如果没有专属规则:
使用 GenericHeroRule
如果后续补专属技能:
只增加 HeroRule,不改车道结构
10. Tactic Lane
函数 TryPriorityTactic(ctx):
返回 TryTactic(ctx, priorityOnly = true)
函数 TryTactic(ctx, priorityOnly = false):
best = None
对每个 battle in ctx.Cache.LocalBattles:
action = FindBestUsefulAttack(ctx, battle.SelfUnit, battle.EnemyUnit)
attackScore = ScoreAttackAction(ctx, action)
如果 attackScore <= 0:
继续
score = 700 + battle.Value + attackScore * 0.1
如果 priorityOnly 且 (score < Config.PriorityTacticScore 或 !IsPriorityTacticAction(ctx, action, attackScore)):
继续
candidate = Candidate(action, Tactic, score, priorityOnly ? "高价值局部战斗攻击" : "局部战斗攻击")
best = MaxCandidate(best, candidate)
对每个 action in ctx.Actions.Attacks:
score = ScoreAttackAction(ctx, action)
如果 score <= 0:
继续
如果 priorityOnly 且 (score < Config.PriorityTacticScore 或 !IsPriorityTacticAction(ctx, action, score)):
继续
candidate = Candidate(action, Tactic, score, priorityOnly ? "高价值攻击收益" : "普通攻击收益")
best = MaxCandidate(best, candidate)
返回 best
函数 IsPriorityTacticAction(ctx, action, attackScore):
attacker = action.UnitData
target = action.TargetUnitData
damage = CalcDamage(ctx.Map, attacker, target)
如果 !CanTreatAttackAsUseful(attacker, target, damage):
返回 false
如果 damage >= target.Health:
返回 true
如果 target 是英雄:
返回 true
如果 target 正在威胁城市:
返回 true
targetValue = UnitTargetValue(ctx, target)
counterThreat = EstimateCounterDamageValue(ctx, target, attacker)
如果 attackScore >= Config.PriorityTacticScore + 60 且 counterThreat <= targetValue * 0.15:
返回 true
返回 false
函数 ScoreAttackAction(ctx, action):
attacker = action.UnitData
target = action.TargetUnitData
damage = CalcDamage(ctx.Map, attacker, target)
如果 !CanTreatAttackAsUseful(attacker, target, damage):
返回 0
score = 620
score += EstimateDamageValue(ctx, attacker, target) * 2
score -= EstimateCounterDamageValue(ctx, target, attacker)
score += CounterBonus(ctx, attacker, target)
如果 CanKillByAction(ctx, action):
score += 160
如果 target 是英雄:
score += 120
如果 target 正在威胁城市:
score += 120
如果 attacker 站在己方城市中心:
score -= EstimateCounterDamageValue(ctx, target, attacker)
如果 与 target 所属玩家好感很高且未交战:
score -= 80
返回 score
函数 FindBestUsefulAttack(ctx, unit, preferredTarget = null):
best = None
bestScore = -INF
对每个 action in ctx.Actions.GetAttackActions(unit):
target = action.TargetUnitData
如果 preferredTarget != null 且 target != preferredTarget:
继续
score = ScoreAttackAction(ctx, action)
如果 score <= 0:
继续
如果 score > bestScore:
bestScore = score
best = action
返回 best
函数 CanTreatAttackAsUseful(attacker, target, damage):
如果 attacker == null 或 target == null:
返回 false
如果 damage > 0:
返回 true
如果 attacker 带有攻击附加收益:
返回 true
返回 false
攻击附加收益包括:
YuugiPush
HakureiDragonshipRam
REISENFRENCHATTAK
KaguyaFrenchWolf
11. UnitOpportunity Lane
函数 TryUnitOpportunity(ctx):
BuildUnitOpportunities(ctx)
对每个 opportunity in ctx.Cache.UnitOpportunities:
candidate = Candidate(opportunity.Action, UnitOpportunity, opportunity.Value, "单位机会")
如果 candidate.IsValid:
返回 candidate
返回 None
函数 ScoreUnitOpportunity(ctx, unit, action, type):
如果 type == Capture:
返回 ScoreCapture(ctx, unit, action)
如果 type == Examine:
返回 ScoreExamine(ctx, unit, action)
如果 type == Gather:
返回 ScoreGather(ctx, unit, action)
如果 type == HeroUpgrade:
返回 ScoreHeroUpgrade(ctx, unit, action)
如果 type == Upgrade:
返回 ScoreUpgrade(ctx, unit, action)
如果 type == CultureUnitUpgrade:
返回 ScoreCultureUpgrade(ctx, unit, action)
如果 type == Recover:
返回 ScoreRecover(ctx, unit, action)
如果 type == ShipUpgrade:
返回 ScoreShipUpgrade(ctx, unit, action)
如果 type == AbsorbMarker:
返回 ScoreAbsorbMarker(ctx, unit, action)
返回 0
函数 ScoreCapture(ctx, unit, action):
grid = ActionTargetGrid(action)
score = 820
如果 grid 是敌方城市中心:
score += 220
如果 grid 是村庄:
score += 180
如果 grid 是敌方首都:
score += 120
如果 GridThreat(ctx, unit, grid) 高:
score -= 120
如果 unit 是危险城市关键守军:
score -= 200
返回 score
函数 ScoreExamine(ctx, unit, action):
grid = ActionTargetGrid(action)
score = 760
score += DevelopmentTargetValue(ctx, grid)
score -= GridThreat(ctx, unit, grid) * 0.3
返回 score
函数 ScoreGather(ctx, unit, action):
grid = ActionTargetGrid(action)
score = 680 + ResourceValue(ctx, grid)
如果 当前战略是 Expansion 或 Development:
score += 80
如果 unit 是前线主力且城市危急:
score -= 160
返回 score
函数 ScoreRecover(ctx, unit, action):
hp = HealthRatio(unit)
hasNegativeState = unit 有 KomeijiFear 或 SAKUYATIRED
如果 hp > Config.LowHpRatio 且 !hasNegativeState:
返回 0
score = 520 + (1 - hp) * 220
如果 hasNegativeState:
score += 160
如果 unit 是英雄:
score += 120
如果 unit 当前能击杀高价值目标:
score -= 180
返回 score
函数 ScoreShipUpgrade(ctx, unit, action):
如果 action 不是合法船升级:
返回 0
cost = GetShipUpgradeCost(unit, action.ActionId.UnitType)
如果 ctx.Player.Coin < cost:
返回 0
score = 560 + UnitPower(unit) * 0.2
如果 cost == 0:
score += 120
否则:
score -= cost * 18
如果 ctx.Cache.StrategicPosture 是 Expansion 或 Attack:
score += 90
返回 score
函数 ScoreAbsorbMarker(ctx, unit, action):
missing = 1 - HealthRatio(unit)
如果 missing <= 0.05:
返回 0
score = 500 + missing * 260
如果 unit 是英雄:
score += 100
返回 score
函数 ScoreUpgrade(ctx, unit, action):
如果 unit 当前承担 Emergency 或可击杀高价值目标:
返回 0
score = 610
如果 升级后解锁克制当前敌军的兵种:
score += 120
如果 升级是海军转换且附近有海战目标:
score += 80
返回 score
函数 ScoreHeroUpgrade(ctx, unit, action):
如果 unit 不是英雄:
返回 0
score = 760
如果 unit 在前线或即将参战:
score += 100
如果 unit 低血:
score += 60
返回 score
12. Front Lane
函数 TryFront(ctx):
best = None
对每个 front in ctx.Cache.Fronts:
对每个 unit in ctx.Cache.SelfUnits:
如果 ShouldSkipFrontMove(ctx, unit, front):
继续
action = FindBestMoveToward(ctx, unit, ResolveFrontTarget(front))
candidate = Candidate(action, Front, ScoreFrontMove(ctx, unit, action, front), "战线移动")
best = MaxCandidate(best, candidate)
返回 best
函数 ShouldSkipFrontMove(ctx, unit, front):
如果 unit 没有移动行动点:
返回 true
如果 unit 是英雄 且 HeroPlaybook 对它有合法动作:
返回 true
如果 unit 低血 且 front.Type == Attack:
返回 true
如果 unit 是危险城市的守军,且城市处于 Emergency:
返回 true
如果 unit 是某个危险城市的唯一守军:
返回 true
返回 false
函数 ResolveFrontTarget(front):
如果 front.Type == Defense:
返回 front.AnchorGrid
如果 front.TargetGrid != null:
返回 front.TargetGrid
返回 front.AnchorGrid
函数 ScoreFrontMove(ctx, unit, action, front):
endGrid = GetActionEndGrid(action)
target = ResolveFrontTarget(front)
score = 0
如果 front.Type == Defense:
score = 620 + front.Pressure * 20
如果 front.Type == Attack:
score = 560 + front.Opportunity
如果 front.Type == Development:
score = 500 + front.Opportunity * 0.5
如果 front.Type == Hold:
score = 420
score -= Distance(endGrid, target) * 16
score -= GridThreat(ctx, unit, endGrid) * 0.4
如果 endGrid 可在下回合攻击/占领目标:
score += 80
如果 unit 是高机动单位 且 target 较远:
score += 40
返回 score
13. Growth Lane
函数 TryGrowth(ctx):
best = None
如果 ShouldStopNonCriticalLane(ctx):
返回 None
best = MaxCandidate(best, TryCityGrowth(ctx))
best = MaxCandidate(best, TryGridGrowth(ctx))
best = MaxCandidate(best, TryPlayerGrowth(ctx))
返回 best
13.1 CityGrowth
函数 TryCityGrowth(ctx):
best = None
scanned = 0
对每个 plan in ctx.Cache.CityPlans:
对每个 action in ctx.Actions.GetCityActions(plan.City):
如果 scanned >= Config.MaxGrowthCityActionsPerDecision:
跳出
scanned += 1
score = ScoreCityGrowth(ctx, action, plan)
如果 ShouldSkipLowValueGrowth(ctx, score):
继续
candidate = Candidate(action, Growth, score, "城市发展")
best = MaxCandidate(best, candidate)
返回 best
函数 ScoreCityGrowth(ctx, action, plan):
id = action.ActionId
score = 360 + plan.Priority
如果 plan.Kind == EmergencyDefense:
如果 id.ActionType == TrainUnit:
score += TrainUnitDefenseValue(ctx, action, plan.Threat)
如果 plan.Threat.DefenderPower <= 0:
score += 240
如果 plan.Threat.IsCapital:
score += 120
如果 plan.Threat.CanBeThreatenedNextTurn:
score += 100
如果 id.ActionType == CityAction 且 id.CityActionType == BuildCityWall:
score += 220
如果 plan.Threat.DefenderPower <= 0:
score += 180
如果 id.ActionType in [StartWonder, BuildWonder]:
score -= 300
如果 plan.Kind == Mobilize 或 plan.Kind == Frontline:
如果 id.ActionType == TrainUnit:
score += TrainUnitWarValue(ctx, action)
如果 id.ActionType == CityAction 且 id.CityActionType == BuildCityWall:
score += 100
如果 id.ActionType == CityLevelUpAction:
score += 50
如果 plan.Kind == BacklineGrowth:
如果 id.ActionType == CityLevelUpAction:
score += 140
如果 id.ActionType == TrainUnit:
score += NeedStandingArmy(ctx, plan.City) ? 80 : 20
如果 plan.Kind == Wonder:
如果 id.ActionType in [StartWonder, BuildWonder]:
score += 160 + WonderValue(ctx, action)
返回 score
13.2 GridGrowth
函数 TryGridGrowth(ctx):
best = None
scanned = 0
对每个 action in ctx.Actions.GridActions:
如果 scanned >= Config.MaxGrowthGridActionsPerDecision:
跳出
scanned += 1
score = ScoreGridGrowth(ctx, action)
如果 ShouldSkipLowValueGrowth(ctx, score):
继续
candidate = Candidate(action, Growth, score, "地块发展")
best = MaxCandidate(best, candidate)
返回 best
函数 ScoreGridGrowth(ctx, action):
grid = ActionGrid(action)
score = 330
如果 grid 属于危险城市附近 且 action 不是防守/道路/关键资源:
score -= 100
如果 action 是 Build:
score += BuildResourceValue(ctx, action)
如果 action 是 Gain:
score += ResourceValue(ctx, grid)
如果 action 是 GridMisc:
score += GridMiscValue(ctx, action)
如果 grid 接近 DevelopmentTarget:
score += 60
返回 score
13.3 PlayerGrowth
函数 TryPlayerGrowth(ctx):
best = None
scanned = 0
对每个 action in ctx.Actions.PlayerActions:
如果 scanned >= Config.MaxGrowthPlayerActionsPerDecision:
跳出
scanned += 1
score = ScorePlayerGrowth(ctx, action)
如果 ShouldSkipLowValueGrowth(ctx, score):
继续
candidate = Candidate(action, Growth, score, "玩家发展")
best = MaxCandidate(best, candidate)
返回 best
函数 ShouldStopNonCriticalLane(ctx):
如果 HasUnresolvedEmptyCityCoreThreat(ctx):
返回 true
如果 Config.MaxNonCriticalActionsPerPlayerTurn <= 0:
返回 false
如果 ctx.Cache.SelfCities.Count < Config.ExpansionUrgentCityThreshold:
返回 false
返回 ctx.ExecutedActionCountThisTurn >= Config.MaxNonCriticalActionsPerPlayerTurn
函数 HasUnresolvedEmptyCityCoreThreat(ctx):
对每个 threat in ctx.Cache.CityThreats:
如果 IsUnresolvedEmptyCityCoreThreat(threat):
返回 true
返回 false
函数 IsUnresolvedEmptyCityCoreThreat(threat):
返回 threat 非空
且 !threat.HasCityCenterDefender
且 threat.CanBeThreatenedNextTurn
且 (threat.IsCapital 或 threat.IsCritical)
函数 ShouldSkipLowValueGrowth(ctx, score):
如果 Config.SoftGrowthActionBudget <= 0:
返回 false
如果 ctx.ExecutedActionCountThisTurn < Config.SoftGrowthActionBudget:
返回 false
返回 score < Config.MinGrowthPriorityAfterSoftBudget
函数 ScorePlayerGrowth(ctx, action):
id = action.ActionId
如果 id.ActionType == LearnTech:
返回 ScoreTech(ctx, action)
如果 id.ActionType == BuyCultureCard:
返回 0
如果 id.ActionType == PlayerAction:
返回 ScorePlayerAction(ctx, action)
返回 0
函数 ScoreTech(ctx, action):
score = 300
如果 ctx.Cache.StrategicPosture == Defense:
score += TechDefenseValue(ctx, action)
如果 ctx.Cache.StrategicPosture == Attack:
score += TechMilitaryCounterValue(ctx, action)
如果 ctx.Cache.StrategicPosture == Expansion:
score += TechResourceAndMobilityValue(ctx, action)
如果 ctx.Cache.StrategicPosture == Development:
score += TechEconomicValue(ctx, action)
score += TechSuccessorPreviewValue(ctx, action) * 0.5
如果 TechRequiresSeaButNoSeaNearby(ctx, action):
score = 0
返回 score
函数 ScoreCultureCard(ctx, action):
如果 !IsAIHeroSlotCultureCard(ctx.Player, action.CultureCardType):
返回 0
score = 860
如果 action.CultureCardType == SecondHero 且 MaxHeroCount == 1:
score += 80
如果 action.CultureCardType == ThirdHero 且 MaxHeroCount == 2:
score += 70
如果 当前没有在场英雄:
score -= 120
返回 score
函数 ScorePlayerAction(ctx, action):
id = action.ActionId.PlayerActionType
如果 id 是宝物选项:
返回 520 + TreasureOptionValue(ctx, action)
如果 id == Embassy:
如果 目标好感 >= 70 且 金币足够:
返回 360
返回 0
如果 id == OfferAlly:
如果 目标好感 >= 90 且 无直接扩张冲突:
返回 340
返回 0
如果 id == BreakAlly:
如果 目标好感 <= 35 且 非队友:
返回 220
返回 0
返回 180
14. Fallback
函数 TryFallback(ctx):
如果 ShouldStopNonCriticalLane(ctx):
返回 None
如果 IsPastSoftGrowthBudget(ctx):
action = FindStrictFallbackAction(ctx)
reason = "软预算后严格兜底"
否则:
action = FindBestFallbackAction(ctx)
reason = "兜底合法动作"
candidate = Candidate(action, Fallback, 1, reason)
如果 candidate.IsValid:
candidate.IsFallback = true
返回 candidate
返回 None
函数 FindBestFallbackAction(ctx):
order = [
ctx.Actions.Attacks,
ctx.Actions.AttackGrounds,
ctx.Actions.CityActions,
ctx.Actions.GridActions,
ctx.Actions.PlayerActions,
ctx.Actions.UnitActions
]
对每个 list in order:
对每个 action in list:
如果 IsFallbackAction(action):
返回 action
返回 None
函数 FindStrictFallbackAction(ctx):
对每个 action in ctx.Actions.Attacks:
如果 ScoreAttackAction(ctx, action) > 0:
返回 action
对每个 action in ctx.Actions.AttackGrounds:
返回 action
对每个 action in ctx.Actions.UnitActions:
如果 action.UnitActionType in [Capture, Recover, Upgrade, CultureUnitUpgrade, HeroUpgrade, AbsorbRedMist, HakureiAbsorbRune, WarCry]:
返回 action
对每个 action in ctx.Actions.PlayerActions:
如果 action.PlayerActionType 是宝藏选择或宝藏收益:
返回 action
返回 None
Fallback 的目标是不断回合,不负责聪明。大量进入 Fallback 说明前面车道缺规则。软预算之后 Fallback 只能执行明确收益动作,避免强国后期用低价值建设或清理动作把回合拖长。
15. 评分辅助函数
15.1 稳定排序
函数 MaxCandidate(a, b):
如果 a 无效:
返回 b
如果 b 无效:
返回 a
如果 b.Priority > a.Priority:
返回 b
如果 b.Priority == a.Priority 且 StableActionKey(b.Action) < StableActionKey(a.Action):
返回 b
返回 a
函数 StableActionKey(action):
返回 (
action.ActionId.ActionType,
action.Param.PlayerId,
action.Param.CityId,
action.Param.UnitId,
action.Param.GridId,
action.Param.TargetUnitId,
action.Param.TargetGridId,
action.ActionId 子类型字段
)
函数 BuildIntentKey(lane, kind, targetId):
如果 targetId == 0:
返回 ""
返回 lane + ":" + kind + ":" + targetId
函数 IsIntentBlocked(ctx, intentKey):
如果 intentKey 为空:
返回 false
返回 intentKey in ctx.BlockedIntentKeysThisTurn
函数 IsIntentBudgetReached(ctx, lane, maxCount):
如果 maxCount <= 0:
返回 false
count = ctx.BlockedIntentKeysThisTurn 中以 lane + ":" 开头的数量
返回 count >= maxCount
函数 IsUsefulMoveToward(ctx, startGrid, endGrid, targetGrid):
如果 startGrid == null 或 endGrid == null 或 targetGrid == null:
返回 false
startDistance = Distance(startGrid, targetGrid)
endDistance = Distance(endGrid, targetGrid)
返回 endDistance < startDistance 或 endDistance <= 1
15.2 伤害和威胁
函数 EstimateDamageValue(ctx, attacker, target):
damage = EstimateDamage(ctx, attacker, target)
return damage / MaxHealth(target) * UnitMilitaryValue(ctx, target)
函数 GridThreat(ctx, unit, grid):
如果 ctx.GridThreatCache 包含 grid.Id:
返回 ctx.GridThreatCache[grid.Id]
threat = 0
对每个 enemy in ctx.Cache.EnemyUnits:
enemyGrid = enemy.Grid
如果 Distance(enemyGrid, grid) <= MoveRange(enemy) + AttackRange(enemy):
threat += EstimateDamageValue(ctx, enemy, unit)
ctx.GridThreatCache[grid.Id] = threat
返回 threat
函数 CanKill(ctx, attacker, target):
return EstimateDamage(ctx, attacker, target) >= target.Health
15.3 资源和建设价值
函数 ResourceValue(ctx, grid):
value = 0
如果 grid 资源能补当前短缺:
value += 100
如果 grid 资源可被当前科技开发:
value += 80
如果 grid 靠近后方安全城市:
value += 40
如果 grid 处于高威胁区:
value -= 80
返回 value
函数 BuildResourceValue(ctx, action):
grid = ActionGrid(action)
value = ResourceValue(ctx, grid)
如果建筑能形成加工链:
value += 60
如果建筑是阵营特色高收益建筑:
value += 80
返回 value
15.4 训练单位价值
函数 TrainUnitDefenseValue(ctx, action, threat):
unitType = action.ActionId.UnitFullType
score = UnitBaseDefenseValue(unitType)
对每个 enemy in threat.EnemyUnits:
score += CounterValue(unitType, enemy.UnitFullType)
score += SimulatedFightValue(unitType, enemy)
返回 score
函数 TrainUnitWarValue(ctx, action):
unitType = action.ActionId.UnitFullType
score = UnitBaseMilitaryValue(unitType)
对每个 enemy in 当前进攻目标玩家单位:
score += CounterValue(unitType, enemy.UnitFullType)
如果 当前需要扩张:
score += TrainUnitExpansionValue(unitType)
如果 unitType 是远程单位:
如果 已接敌:
score += 95
否则:
score += 45
如果 unitType 是攻城单位或机动单位:
score += 45
如果 unitType 是特殊压力单位:
如果 已接敌:
score += 70
否则:
score += 30
返回 score
函数 FindBestFrontMove(ctx, unit, target, front):
如果 unit 不是远程或攻城:
返回 FindBestMove(unit, target)
如果 front 不是 Attack 或 Defense:
返回 FindBestMove(unit, target)
best = 空
bestScore = 负无穷
对每个 moveAction in ActionIndex.GetMoveActions(unit) 的前 8 个候选:
endGrid = ActionEndGrid(moveAction)
如果 endGrid 没有朝 target 推进:
continue
distance = Distance(endGrid, target)
threat = GridThreat(ctx, endGrid)
score = -distance * 18 - threat * 0.7
如果 distance <= unit.AttackRange:
score += 280
如果 threat <= 0:
score += 120
否则如果 threat <= UnitPower(unit) * 0.25:
score += 55
否则:
score -= 160
best = Max(score)
如果 best 存在:
返回 best
返回 FindBestMove(unit, target)
函数 ScoreFrontMove(ctx, unit, action, front):
endGrid = ActionEndGrid(action)
target = ResolveFrontTarget(front)
distance = Distance(endGrid, target)
threat = GridThreat(ctx, endGrid)
score = FrontBaseScore(front)
score -= distance * 16
score -= threat * 0.4
如果 unit 下回合能压到 target:
score += 80
如果 unit 是远程或攻城:
如果 front 是 Attack 或 Defense:
如果 distance <= unit.AttackRange:
score += 150
否则:
score -= 35
如果 threat <= UnitPower(unit) * 0.25:
score += 115
否则:
score -= 140
如果 distance <= unit.AttackRange 且 threat <= 0:
score += 80
如果 unit 是脆弱特殊衍生单位 且 front 是 Attack:
如果 unit 血量偏低:
score -= 180
如果 不能安全压到 target:
score -= 220
否则:
score -= 50
返回 score
16. 正确性约束
16.1 权威行为
所有最终动作:
必须来自 ActionPool
必须 CheckCan
必须 CompleteExecute
AI 不复制:
资源扣除
行动点扣除
城市易主
单位死亡
技能触发
网络同步
回放记录
16.2 确定性
禁止在权威选择路径使用:
UnityEngine.Random
当前系统时间
未排序 Dictionary/HashSet 迭代结果作为最终选择
真实 Main.MapData 假设
UI/Renderer 状态
同分必须用稳定 key 解决。
16.3 技能安全
英雄和单位技能只通过 Action 入口触发:
UnitAttack
UnitAttackAlly
UnitAttackGround
UnitAction
UnitMove
AI 不直接调用 SkillBase 的效果方法。
17. 性能约束
每次 Decide:
构建一次基础 WorldCache
构建一次 ActionPool
补一次 UnitOpportunity
每个车道只查缓存和 ActionPool
复杂度目标:
CityThreat: SelfCities * nearby EnemyUnits
LocalBattle: SelfUnits * nearby EnemyUnits
Front: SelfCities * EnemyCities + DevelopmentTargets
ActionPool: MaxActionCount 上限
Growth: 按 CityPlan 顺序扫描 TopN 城市动作、TopN 地块动作、TopN 玩家动作
性能保护:
限制移动候选数量
限制 DevelopmentTarget TopN
限制 Front TopN
限制自城移动锚点 TopN
限制 Emergency responder 扫描数量
限制 Growth 每次决策扫描数量
软预算后过滤低价值 Growth,硬预算后 Growth/Fallback 停手
限制 LocalBattle 搜索半径
一次决策内缓存 FindBestMoveToward(unit, targetGrid)
一次决策内缓存 GridThreat(grid)
ActionPool 构建阶段完成 CopyAction + CheckCan
各车道 Candidate 只引用已验证 action,不重复 CopyAction + CheckCan
最终 Execute 前再 CheckCan 一次兜底
compact batch 诊断只写分析所需字段;Decision 保留 lane/reason/action/actionPool,Execution 保留 action、关键 delta、轻量 before/after,完整候选明细只在非 compact 调试时打开
科技只看当前可学和一层后继预览
18. 测试脚本标准
18.0 批量诊断指标
批量指标不参与单次决策,只服务测试循环。
结构 DefenseMetrics:
CriticalCityThreatTurns
CapitalThreatTurns
EmptyThreatenedCityTurns
CityLost
CityGained
CityThreatResolved
CityThreatWorsened
EmergencyDecisions
EmergencyExecutions
DefenderReturn
HeroDefensiveUse
EmergencyResponseRate
DefenseScore
结构 ActorExpressionMetrics:
Decisions
Executions
Kills
SelfDeaths
DamageToTarget
DamageTaken
SelfHeal
Moves
Captures
RecoverActions
SkillChanged
ThreatResolved
ThreatWorsened
CityOwnerChanged
NoEffect
TopActions
结构 BatchExpressionMetrics:
HeroPersonalExpression: Map<GiantType, ActorExpressionMetrics>
UnitRoleExpression: Map<UnitRole, ActorExpressionMetrics>
UnitTypeExpression: Map<UnitType, ActorExpressionMetrics>
SkillSignatureExpression: Map<CompactSkillSignature, ActorExpressionMetrics>
函数 BuildTurnDefenseSnapshot(map, player):
snapshot.CityIds = 当前 player 拥有城市 Id 排序签名
snapshot.CapitalCityIds = 当前 player 首都 Id 排序签名
snapshot.CityThreatCount = 受威胁城市数量
snapshot.CriticalCityThreatCount = 危急城市数量
snapshot.CapitalThreatCount = 受威胁首都数量
snapshot.EmptyThreatenedCityCount = 受威胁且城市中心无己方/同盟单位驻守的城市数量
snapshot.MaxCityDangerScore = 所有城市威胁中的最高危险值
返回 snapshot
函数 BuildActionDefenseDelta(before, after):
delta.CityThreatResolved =
before.CityThreatCount > 0
且 (
after.CriticalCityThreatCount < before.CriticalCityThreatCount
或 after.CityThreatCount < before.CityThreatCount
或 after.MaxCityDangerScore < before.MaxCityDangerScore
)
delta.CityThreatWorsened =
after.CriticalCityThreatCount > before.CriticalCityThreatCount
或 after.CityThreatCount > before.CityThreatCount
或 after.EmptyThreatenedCityCount > before.EmptyThreatenedCityCount
或 after.MaxCityDangerScore > before.MaxCityDangerScore
返回 delta
城市丢失不从单个 action 直接统计,按同一玩家两次 TurnStart 对比:
函数 AccumulateCityLoss(previousTurnStart, currentTurnStart):
如果 current.CityCount < previous.CityCount:
CityLost += previous.CityCount - current.CityCount
如果 current.CityCount > previous.CityCount:
CityGained += current.CityCount - previous.CityCount
如果 current.CapitalCityIds != previous.CapitalCityIds:
CapitalOwnershipChanged += 1
防守综合分只用于批量对比:
DefenseScore =
CityThreatResolved * 3
+ EmergencyExecutions
+ DefenderReturn
+ HeroDefensiveUse * 2
- CityLost * 8
- CapitalThreatTurns * 2
- EmptyThreatenedCityTurns * 2
- CityThreatWorsened * 2
EmergencyResponseRate =
EmergencyDecisions / max(1, CriticalCityThreatTurns + EmptyThreatenedCityTurns)
英雄风格表达按 reason/action 归桶:
函数 ClassifyHeroStyle(reason, actionKey):
如果 包含 LowHp、Recover、Heal、Eirin、Sanae、Patchouli、Absorb、Revive:
返回 Recovery
如果 包含 Defense、Protect、Guard、MoveToCity、Aunn、Meiling、Sakuya、Reimu:
返回 Defense
如果 包含 Kill、Flandre、AttackValue、Boom、Mokou、Reisen、Yuugi:
返回 Burst
如果 包含 Ban、Fear、Control、Satori、Koishi、Sumireko、Orb、Ground:
返回 Control
如果 包含 Summon、CreateMini、Suwako、Snake、BonePile、Mini:
返回 Summon
如果 包含 Economy、CityExp、Corpse、KanakoSit、Rin、Tewi、Kaguya:
返回 Economy
如果 包含 MoveAgain、MoveToFront、Retreat、Mobility、Aya、Kasen:
返回 Mobility
如果 包含 SelectHero、SpawnHero、FinishLowestTask:
返回 HeroLifecycle
返回 General
特色单位表达按技能签名统计:
函数 AccumulateUnitSkillExpression(execution):
before = execution.Before
action = execution.Action
delta = execution.Delta
如果 before.UnitSkillSignature 为空:
返回
UnitSkillActions[action.ActionKey] += 1
ActorSkillSignatures[Compact(before.UnitSkillSignature)] += 1
如果 delta.UnitSkillSignatureChanged
或 delta.TargetUnitSkillSignatureChanged
或 delta.UnitSkillDelta != 0
或 delta.TargetUnitSkillDelta != 0:
UnitSkillChangedActions[action.ActionKey] += 1
英雄个人表达按真实执行统计,不只看 HeroPlaybook:
函数 AccumulateHeroPersonalExpression(decision, execution):
action = execution.Action
delta = execution.Delta
hero = action.ActorGiantType 或 action.GiantType 或 action.TargetActorGiantType
如果 hero 为空:
返回
metrics = HeroPersonalExpression[hero]
metrics.Executions += 1
metrics.DamageToTarget += max(0, -delta.TargetUnitHealthDelta)
metrics.DamageTaken += max(0, -delta.UnitHealthDelta)
metrics.SelfHeal += max(0, delta.UnitHealthDelta)
如果 delta.TargetUnitDied: metrics.Kills += 1
如果 delta.UnitDied: metrics.SelfDeaths += 1
如果 delta.UnitMoved: metrics.Moves += 1
如果 delta.UnitSkillSignatureChanged 或 delta.TargetUnitSkillSignatureChanged: metrics.SkillChanged += 1
如果 delta.SelectedHeroDelta > 0: metrics.Selected += delta.SelectedHeroDelta
如果 delta.HeroDelta > 0: metrics.Spawned += delta.HeroDelta
metrics.TopActions[action.ActionKey] += 1
小兵定位表达按角色和具体类型同时统计:
函数 ResolveUnitRole(unitType):
如果 unitType in [Warrior, Swordsman, KaguyaFrenchWarrior, KaguyaFrenchAnimalWarrior, HakureiBerserk]:
返回 Melee
如果 unitType in [Rider, Knights, MoriyaRider, MoriyaKnight, KomeijiIndianRider, KomeijiIndianKnight, HakureiValkyrie]:
返回 Mobility
如果 unitType in [Archer, KomeijiIndianArcher]:
返回 Ranged
如果 unitType in [Catapult, KaguyaFrenchCatapult, KomeijiIndianCatapult]:
返回 Siege
如果 unitType in [Defender, HakureiRoundShieldman]:
返回 Defender
如果 unitType 是船:
返回 Naval
如果 unitType 是召唤物:
返回 Summon
返回 Special
函数 AccumulateUnitRoleExpression(execution):
action = execution.Action
delta = execution.Delta
unitType = action.ActorUnitType 或 action.UnitType
如果 unitType 为空 或 unitType 是英雄 Giant:
返回
role = ResolveUnitRole(unitType)
同时累加 UnitRoleExpression[role] 和 UnitTypeExpression[unitType]:
Executions、Kills、SelfDeaths、DamageToTarget、DamageTaken、Moves、Captures、SkillChanged、NoEffect、TopActions
18.1 城市防守
给 AI 城市附近放敌军。
期望:
Emergency 返回攻击威胁、回防、或危险城市生产。
批量日志中 cityLost 尽量为 0。
capitalThreatTurns 不应长期升高。
resolved 应高于 worsened。
18.2 占领扩张
给 AI 单位脚下放村庄、敌空城、遗迹。
期望:
无更高车道时 UnitOpportunity 执行占领或探索。
18.3 英雄机制
给 AI 放残血友军、敌方英雄、地面攻击目标、自爆窗口。
期望:
HeroPlaybook 选择对应英雄动作。
styleBuckets 中出现对应英雄风格,不应长期只有 General。
18.3.1 特色小兵机制
给 AI 放置新阵营特色小兵和普通小兵混合场景。
期望:
unitSkillActions 中出现特色小兵动作。
actorSkillSignatures 能看到该阵营技能组合。
如果技能会改变状态,unitSkillChangedActions 应出现对应 action。
18.4 战线移动
无局部战斗、无机会动作。
期望:
DefenseFront 优先于 AttackFront。
AttackFront 优先于 DevelopmentFront。
DevelopmentFront 优先于 HoldFront。
18.5 内政发展
安全局面。
期望:
后方城市升级、建设、科技文化。
前线城市生产军力。
18.6 性能
中大型地图 AI 连续执行。
期望:
单次 Decide 不重复全量生成动作。
候选数量不长期超过 MaxActionCount。
无明显卡顿尖峰。