火力规则:装备匹配规则实现

This commit is contained in:
MHW
2026-04-07 14:41:46 +08:00
parent 866fd215f2
commit 72f4caf555
7 changed files with 583 additions and 3549 deletions

View File

@@ -0,0 +1,33 @@
package com.solution.rule.config;
import java.util.HashMap;
import java.util.Map;
/**
* 装备匹配规则默认参数,与 {@code resources/rules/rule.drl} 中 {@code buildParam} 键保持一致。
*/
public final class FireRuleMatchDefaultParams {
private FireRuleMatchDefaultParams() {
}
public static Map<String, Object> defaults() {
Map<String, Object> param = new HashMap<>();
param.put("weight", 1);
param.put("minSelectedScore", 1);
param.put("tankScore", 1);
param.put("airScore", 2);
param.put("groundScore", 1);
param.put("missileScore", 1);
param.put("airTaskScore", 10);
param.put("bluePlatformKeywords_air", "F-16,J-10,F-35");
param.put("redPreferredWhenBlueAir", "防空,导弹,无人机,直升机,空空");
param.put("redPreferredWhenGround", "远火,榴弹,炮,火箭");
param.put("airTaskKeywords", "空中,制空,拦截,空战");
param.put("groundTaskKeywords", "地面,突击,登陆");
param.put("tankKeywords", "坦克,装甲");
param.put("missileKeywords", "导弹,火箭弹,巡航");
param.put("tieBreak", "equipmentId");
return param;
}
}

View File

@@ -12,6 +12,7 @@ import com.solution.rule.domain.ultimately.dto.FireRuleInputDTO;
import com.solution.rule.domain.ultimately.dto.FireRuleInputForceSideDTO;
import com.solution.rule.domain.ultimately.dto.FireRuleInputRedWeaponElementDTO;
import com.solution.rule.domain.ultimately.dto.FireRuleTaskInputDTO;
import com.solution.rule.config.FireRuleMatchDefaultParams;
import com.solution.rule.domain.ultimately.fact.DroolsFact;
import com.solution.rule.domain.ultimately.vo.FireRuleOutputVO;
import com.solution.rule.domain.vo.ComponentCountVO;
@@ -27,6 +28,7 @@ import com.solution.rule.strategy.SceneStrategy;
import com.solution.rule.strategy.SceneStrategyFactory;
import org.kie.api.KieBase;
import org.kie.api.runtime.KieSession;
import org.kie.api.runtime.rule.FactHandle;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -197,7 +199,7 @@ public class FireRuleServiceImpl implements FireRuleService {
* @return
*/
@Override
public FireRuleOutputVO rule(FireRuleInputDTO task) {
public FireRuleOutputVO rule(FireRuleInputDTO task) {
if(ObjectUtil.isEmpty(task)){
throw new RuntimeException(ExceptionConstants.PARAMETER_EXCEPTION);
}
@@ -207,10 +209,9 @@ public class FireRuleServiceImpl implements FireRuleService {
}
//创建KieSession
KieSession kieSession = kieBase.newKieSession();
//设置Drools全局变量
Map<String, Object> globalParams = new HashMap<>();
// globalParams.putAll(FireRuleMatchDefaultParams.defaults());
kieSession.setGlobal("globalParams", globalParams);
kieSession.insert(globalParams);
//获取红方阵营id
String redObjectHandleId = getObjectHandle(task);
if(ObjectUtil.isEmpty(redObjectHandleId)){
@@ -229,16 +230,25 @@ public class FireRuleServiceImpl implements FireRuleService {
fireRuleOutputVO.setSourceFile(task.getSourceFile());
DroolsFact droolsFact = new DroolsFact();
droolsFact.setRedWeapons(redWeapons);
// droolsFact.getFireRuleOutputVO().setRedWeapons(redWeapons);
droolsFact.setRedWeapons(new ArrayList<>(redWeapons));
droolsFact.setFireRuleOutputVO(fireRuleOutputVO);
for (FireRuleTaskInputDTO fireRuleTaskInputDTO : tasks) {
droolsFact.setTask(fireRuleTaskInputDTO);
kieSession.insert(droolsFact);
FactHandle droolsFactHandle = null;
try {
for (FireRuleTaskInputDTO fireRuleTaskInputDTO : tasks) {
droolsFact.setTask(fireRuleTaskInputDTO);
if (droolsFactHandle == null) {
droolsFactHandle = kieSession.insert(droolsFact);
} else {
kieSession.update(droolsFactHandle, droolsFact);
}
kieSession.fireAllRules();
}
} finally {
kieSession.dispose();
}
return droolsFact.getFireRuleOutputVO();
return fireRuleOutputVO;
}
/**

View File

@@ -0,0 +1,152 @@
package com.solution.rule.utils;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.solution.rule.domain.ultimately.dto.FireRuleInputRedSubComponentsDTO;
import com.solution.rule.domain.ultimately.vo.FireRuleLauncherConfigurationVO;
import com.solution.rule.domain.ultimately.vo.FireRuleMissionListItemVO;
import com.solution.rule.domain.ultimately.vo.FireRuleMountedWeaponRefVO;
import com.solution.rule.domain.ultimately.vo.FireRuleRedSubComponentsVO;
import com.solution.rule.domain.ultimately.vo.FireRuleRedWeaponEquipmentVO;
import com.solution.rule.domain.ultimately.vo.FireRuleRedWeaponSlotVO;
import com.solution.rule.domain.ultimately.vo.FireRuleSceneTaskNodeVO;
import com.solution.rule.domain.ultimately.vo.FireRuleSceneTaskPayloadVO;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
/**
* 将入参中的红方装备 {@code SubComponents}DTO转为输出 VO保持与原始 JSON 结构一致。
*/
public final class FireRuleRedWeaponOutputFillHelper {
private static final ObjectMapper MAPPER = new ObjectMapper()
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
private FireRuleRedWeaponOutputFillHelper() {
}
/**
* DTO 与 VO 字段/JsonProperty 对齐,通过 Jackson 树转换实现原样输出。
*/
public static FireRuleRedSubComponentsVO toOutputSubComponents(FireRuleInputRedSubComponentsDTO src) {
if (src == null) {
return null;
}
return MAPPER.convertValue(src, FireRuleRedSubComponentsVO.class);
}
/**
* 将输出 redWeapons 单项映射为 Tasks 节点(一个装备 -> 一个任务节点)。
*
* <p>字段映射约定:</p>
* <ul>
* <li>drawName = Name + \"打击任务\"</li>
* <li>side = OwnerForceSide按你的要求直接复制</li>
* <li>id 优先 EquipmentID其次 PlatID最后用 index 兜底</li>
* <li>task.payload.weaponId = EquipmentID</li>
* <li>task.payload.sideId = OwnerForceSide</li>
* <li>missionList尽量从 SubComponents.weapon[*] 中提取 mountedWeapon/name/number/launcherType</li>
* </ul>
*/
public static FireRuleSceneTaskNodeVO toTaskNode(FireRuleRedWeaponEquipmentVO redWeapon, int index) {
FireRuleSceneTaskNodeVO node = new FireRuleSceneTaskNodeVO();
if (redWeapon == null) {
node.setId("redWeaponTask_" + index);
node.setDrawName("打击任务");
return node;
}
String name = nz(redWeapon.getName());
node.setName(name);
node.setDrawName(name + "打击任务");
node.setId(buildNodeId(redWeapon, index));
node.setSide(redWeapon.getOwnerForceSide());
// 按你的要求写死 Tasks 节点的展示字段
node.setColor("rgb(220, 39, 39)");
node.setDataType("taskPlane");
node.setGroupType("tasks");
node.setShow(true);
node.setIsSelected(false);
node.setSort((long) index);
FireRuleSceneTaskPayloadVO payload = new FireRuleSceneTaskPayloadVO();
payload.setName(name);
payload.setWeaponId(redWeapon.getEquipmentId());
payload.setSideId(redWeapon.getOwnerForceSide());
payload.setMissionList(buildMissionList(redWeapon));
node.setTask(payload);
return node;
}
/**
* 批量转换redWeapons 有几个就生成几个 Tasks。
*/
public static List<FireRuleSceneTaskNodeVO> toTaskNodes(List<FireRuleRedWeaponEquipmentVO> redWeapons) {
if (redWeapons == null || redWeapons.isEmpty()) {
return Collections.emptyList();
}
List<FireRuleSceneTaskNodeVO> list = new ArrayList<>(redWeapons.size());
for (int i = 0; i < redWeapons.size(); i++) {
list.add(toTaskNode(redWeapons.get(i), i));
}
return list;
}
private static List<FireRuleMissionListItemVO> buildMissionList(FireRuleRedWeaponEquipmentVO redWeapon) {
if (redWeapon == null || redWeapon.getSubComponents() == null || redWeapon.getSubComponents().getWeapon() == null) {
return null;
}
List<FireRuleMissionListItemVO> result = new ArrayList<>();
for (FireRuleRedWeaponSlotVO slot : redWeapon.getSubComponents().getWeapon()) {
if (slot == null) {
continue;
}
FireRuleLauncherConfigurationVO cfg = slot.getConfiguration();
FireRuleMountedWeaponRefVO mounted = cfg != null ? cfg.getMountedWeapon() : null;
FireRuleMissionListItemVO item = new FireRuleMissionListItemVO();
// label优先挂载武器名其次 deviceName
String label = mounted != null ? mounted.getName() : null;
if (isBlank(label)) {
label = slot.getDeviceName();
}
item.setLabel(label);
item.setNumber(cfg != null ? cfg.getNumber() : null);
item.setValue(mounted != null ? mounted.getName() : null);
item.setLauncherType(extractLauncherType(slot.getTwiceModified()));
result.add(item);
}
return result.isEmpty() ? null : result;
}
private static String extractLauncherType(Map<String, Object> twiceModified) {
if (twiceModified == null || twiceModified.isEmpty()) {
return null;
}
Object v = twiceModified.get("launcherType");
return v == null ? null : String.valueOf(v);
}
private static String buildNodeId(FireRuleRedWeaponEquipmentVO redWeapon, int index) {
String id = nz(redWeapon.getEquipmentId());
if (!id.isEmpty()) {
return id;
}
id = nz(redWeapon.getPlatId());
if (!id.isEmpty()) {
return id;
}
return "redWeaponTask_" + index;
}
private static String nz(String s) {
return s == null ? "" : s;
}
private static boolean isBlank(String s) {
return s == null || s.trim().isEmpty();
}
}

View File

@@ -1,34 +1,326 @@
package com.solution.rule.utils;
import com.solution.rule.domain.ultimately.dto.FireRuleInputRedWeaponElementDTO;
import com.solution.rule.domain.ultimately.dto.FireRuleTaskInputDTO;
import com.solution.rule.domain.ultimately.dto.FireRuleTaskWeaponDTO;
import com.solution.rule.domain.ultimately.dto.FireRuleWeaponComponentDTO;
import com.solution.rule.domain.ultimately.fact.DroolsFact;
import com.solution.rule.domain.ultimately.vo.FireRuleOutputVO;
import com.solution.rule.domain.ultimately.vo.FireRuleRedWeaponEquipmentVO;
import com.solution.rule.domain.ultimately.vo.FireRuleTaskInputVO;
import com.solution.rule.domain.ultimately.vo.FireRuleTaskWeaponVO;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* 规则函数方法
* Drools 规则调用的装备匹配逻辑。所有业务词均来自 globalParams由 rule.drl 的 buildParam 注入),本类不写死中文业务词。
* <p>
* <b>总分公式(每件候选红装)</b>
* <pre>
* score(red) = scoreRuleSlots(...) + scoreLegacyLayer(...)
*
* scoreRuleSlots = Σ(i=1..ruleSlotCount) [ containsAny(blueBlob, blueRuleKeywords_i)
* ∧ containsAny(redBlob, redRuleKeywords_i)
* ? ruleScore_i * weight : 0 ]
*
* scoreLegacyLayer = ①空中平台 + ②空中任务 + ③地面任务 + ④坦克 + ⑤导弹 五段条件之和,每段若满足均为「对应 *Score * weight」
* (五段含义见 scoreLegacyLayer 方法注释与 rule.drl 中各键说明)。
* </pre>
* <p>
* <b>文本串定义</b>
* <ul>
* <li>blueBlob{@link #buildBlueTextBlob} — 蓝方任务 drawName、dataType、taskWeapons 及组件名拼接成一大串,用于关键词包含判断。</li>
* <li>redBlob{@link #buildRedTextBlob} — 红装 name、platformType、supportType 拼接。</li>
* </ul>
* <p>
* <b>选优与输出</b>
* <ul>
* <li>在 redWeapons 池中对每件算 score取最大值若并列{@link #compareRedForTieBreak}(由 tieBreak 参数控制,默认 equipmentId 小者优先)。</li>
* <li>若 maxScore &lt; minSelectedScore不追加 fireRuleInputs仍将当前池映射到 FireRuleOutputVO.redWeapons。</li>
* <li>否则:池中 remove 选中项fireRuleInputs 追加一行drawName += outputDrawNameSuffixtaskWeapons 一条映射自选中红装redWeapons 为剩余池。</li>
* </ul>
*/
public class RuleFunction {
public final class RuleFunction {
private RuleFunction() {
}
/**
* 装备匹配
* 单轮规则执行:对当前 DroolsFact 的一条蓝方任务,从 redWeapons 中选一件装备并写回输出。
* <p>
* 参数 pglobalParams中各键含义与运算见 {@link RuleFunction} 类注释及 resources/rules/rule.drl 内 buildParam 行内注释。
*/
public static void equipmentRule(DroolsFact task, Map globalParams){
//空中总分数
Integer airScore = 0;
//反坦克总分数
Integer antitankScore = 0;
//远程打击总分数
Integer remoteScore = 0;
@SuppressWarnings("rawtypes")
public static void equipmentRule(DroolsFact fact, Map globalParams) {
if (fact == null || fact.getTask() == null) {
return;
}
Map<String, Object> p = castParams(globalParams);
FireRuleTaskInputDTO blue = fact.getTask();
List<FireRuleInputRedWeaponElementDTO> pool = fact.getRedWeapons();
if (pool == null) {
pool = new ArrayList<>();
fact.setRedWeapons(pool);
}
FireRuleTaskInputDTO blueTask = task.getTask();
// weight、minSelectedScore见 rule.drl 注释
String blueBlob = buildBlueTextBlob(blue);
int weight = readInt(p, "weight", 1);
int minScore = readInt(p, "minSelectedScore", 1);
//权重因子
Integer weight = (Integer) globalParams.get("weight");
String drawName = blueTask.getDrawName();
if("".contains(drawName)){
int bestIndex = -1;
int bestScore = Integer.MIN_VALUE;
for (int i = 0; i < pool.size(); i++) {
FireRuleInputRedWeaponElementDTO red = pool.get(i);
String redBlob = buildRedTextBlob(red);
int score = scoreRuleSlots(blueBlob, redBlob, p, weight)
+ scoreLegacyLayer(blueBlob, redBlob, p, weight);
if (score > bestScore) {
bestScore = score;
bestIndex = i;
} else if (score == bestScore && bestIndex >= 0) {
// 并列compareRedForTieBreak(a,b)>0 表示当前 best 的 equipmentId 比候选 red 大,应换成更小的 id
if (compareRedForTieBreak(pool.get(bestIndex), red, p) > 0) {
bestIndex = i;
}
}
}
FireRuleOutputVO out = fact.getFireRuleOutputVO();
if (out == null) {
out = new FireRuleOutputVO();
fact.setFireRuleOutputVO(out);
}
// 未达门槛或池空:不写 fireRuleInputs仅同步「剩余池」到输出 redWeapons
if (bestIndex < 0 || pool.isEmpty() || bestScore < minScore) {
out.setRedWeapons(convertPoolToEquipmentVoList(pool));
// Tasks 由最终输出 redWeapons 一一生成(一个装备 -> 一个任务)
out.setTasks(FireRuleRedWeaponOutputFillHelper.toTaskNodes(out.getRedWeapons()));
return;
}
FireRuleInputRedWeaponElementDTO chosen = pool.remove(bestIndex);
out.setRedWeapons(convertPoolToEquipmentVoList(pool));
// Tasks 由最终输出 redWeapons 一一生成(一个装备 -> 一个任务)
out.setTasks(FireRuleRedWeaponOutputFillHelper.toTaskNodes(out.getRedWeapons()));
}
@SuppressWarnings("unchecked")
private static Map<String, Object> castParams(Map raw) {
return raw == null ? new java.util.HashMap<>() : (Map<String, Object>) raw;
}
/**
* 蓝方侧用于「关键词包含」判断的合并文本(空格分隔各字段)。
*/
private static String buildBlueTextBlob(FireRuleTaskInputDTO task) {
StringBuilder sb = new StringBuilder();
append(sb, task.getDrawName());
append(sb, task.getDataType());
if (task.getTaskWeapons() != null) {
for (FireRuleTaskWeaponDTO w : task.getTaskWeapons()) {
if (w == null) {
continue;
}
append(sb, w.getName());
append(sb, w.getSupportType());
append(sb, w.getEquipmentId());
if (w.getComponents() != null) {
for (FireRuleWeaponComponentDTO c : w.getComponents()) {
if (c == null) {
continue;
}
append(sb, c.getDeviceName());
}
}
}
}
return sb.toString();
}
/**
* 红方侧用于「关键词包含」判断的合并文本。
*/
private static String buildRedTextBlob(FireRuleInputRedWeaponElementDTO red) {
if (red == null) {
return "";
}
StringBuilder sb = new StringBuilder();
append(sb, red.getName());
append(sb, red.getPlatformType());
append(sb, red.getSupportType());
return sb.toString();
}
private static void append(StringBuilder sb, String s) {
if (s != null && !s.isEmpty()) {
sb.append(s).append(' ');
}
}
/**
* 规则槽得分:对 i=1..ruleSlotCount若 blueBlob 命中 blueRuleKeywords_i 且 redBlob 命中 redRuleKeywords_i
* 则累加 ruleScore_i * weight。关键词为英文逗号分隔任一词作为子串出现在文本中即命中。
*/
private static int scoreRuleSlots(String blueBlob, String redBlob, Map<String, Object> p, int weight) {
int n = readInt(p, "ruleSlotCount", 0);
int sum = 0;
for (int i = 1; i <= n; i++) {
String bk = str(p, "blueRuleKeywords_" + i, "");
String rk = str(p, "redRuleKeywords_" + i, "");
if (bk.isEmpty() || rk.isEmpty()) {
continue;
}
if (containsAny(blueBlob, bk) && containsAny(redBlob, rk)) {
sum += readInt(p, "ruleScore_" + i, 0) * weight;
}
}
return sum;
}
/**
* 兼容层得分:五段独立条件,可叠加。每段均为「蓝关键词命中 ∧ 红关键词命中 → 加 对应分数 * weight」。
* <ul>
* <li>① bluePlatformKeywords_air + redPreferredWhenBlueAir → airScore</li>
* <li>② airTaskKeywords + redPreferredWhenBlueAir → airTaskScore</li>
* <li>③ groundTaskKeywords + redPreferredWhenGround → groundScore</li>
* <li>④ tankKeywords + redMatchKeywords_tank → tankScore</li>
* <li>⑤ missileKeywords + redMatchKeywords_missile → missileScore</li>
* </ul>
* 键名与 rule.drl 中 param.put 一致。
*/
private static int scoreLegacyLayer(String blueBlob, String redBlob, Map<String, Object> p, int weight) {
int s = 0;
if (containsAny(blueBlob, str(p, "bluePlatformKeywords_air", ""))
&& containsAny(redBlob, str(p, "redPreferredWhenBlueAir", ""))) {
s += readInt(p, "airScore", 0) * weight;
}
if (containsAny(blueBlob, str(p, "airTaskKeywords", ""))
&& containsAny(redBlob, str(p, "redPreferredWhenBlueAir", ""))) {
s += readInt(p, "airTaskScore", 0) * weight;
}
if (containsAny(blueBlob, str(p, "groundTaskKeywords", ""))
&& containsAny(redBlob, str(p, "redPreferredWhenGround", ""))) {
s += readInt(p, "groundScore", 0) * weight;
}
if (containsAny(blueBlob, str(p, "tankKeywords", ""))
&& containsAny(redBlob, str(p, "redMatchKeywords_tank", ""))) {
s += readInt(p, "tankScore", 0) * weight;
}
if (containsAny(blueBlob, str(p, "missileKeywords", ""))
&& containsAny(redBlob, str(p, "redMatchKeywords_missile", ""))) {
s += readInt(p, "missileScore", 0) * weight;
}
return s;
}
/**
* 并列时比较两件红装tieBreak=equipmentId 时返回 id 字典序比较结果(&gt;0 表示 a 的 id 大于 b应选 b
*/
private static int compareRedForTieBreak(
FireRuleInputRedWeaponElementDTO a,
FireRuleInputRedWeaponElementDTO b,
Map<String, Object> p) {
if (a == null) {
return b == null ? 0 : 1;
}
if (b == null) {
return -1;
}
String mode = str(p, "tieBreak", "equipmentId");
if ("equipmentId".equals(mode)) {
String ida = nz(a.getEquipmentId());
String idb = nz(b.getEquipmentId());
return ida.compareTo(idb);
}
return 0;
}
/**
* commaKeywords英文逗号分隔若 text 包含其中任一词trim 后非空)则命中。
*/
private static boolean containsAny(String text, String commaKeywords) {
if (text == null || text.isEmpty() || commaKeywords == null || commaKeywords.isEmpty()) {
return false;
}
for (String part : commaKeywords.split(",")) {
String k = part.trim();
if (!k.isEmpty() && text.contains(k)) {
return true;
}
}
return false;
}
private static String str(Map<String, Object> p, String key, String def) {
Object v = p.get(key);
return v == null ? def : String.valueOf(v);
}
private static int readInt(Map<String, Object> p, String key, int def) {
Object v = p.get(key);
if (v == null) {
return def;
}
if (v instanceof Number) {
return ((Number) v).intValue();
}
try {
return Integer.parseInt(String.valueOf(v).trim());
} catch (NumberFormatException e) {
return def;
}
}
private static String nz(String s) {
return s == null ? "" : s;
}
private static FireRuleTaskWeaponVO toTaskWeaponVo(FireRuleInputRedWeaponElementDTO r) {
FireRuleTaskWeaponVO w = new FireRuleTaskWeaponVO();
if (r != null) {
w.setEquipmentId(r.getEquipmentId());
w.setName(r.getName());
w.setSupportType(r.getSupportType());
}
return w;
}
private static List<FireRuleRedWeaponEquipmentVO> convertPoolToEquipmentVoList(
List<FireRuleInputRedWeaponElementDTO> pool) {
List<FireRuleRedWeaponEquipmentVO> list = new ArrayList<>();
if (pool == null) {
return list;
}
for (FireRuleInputRedWeaponElementDTO e : pool) {
list.add(toRedEquipmentVo(e));
}
return list;
}
private static FireRuleRedWeaponEquipmentVO toRedEquipmentVo(FireRuleInputRedWeaponElementDTO src) {
if (src == null) {
return null;
}
FireRuleRedWeaponEquipmentVO vo = new FireRuleRedWeaponEquipmentVO();
vo.setSupportType(src.getSupportType());
vo.setTroopsDetail(src.getTroopsDetail());
vo.setPlatformType(src.getPlatformType());
vo.setIsStrikeTarget(src.getIsStrikeTarget());
vo.setIsReconTarget(src.getIsReconTarget());
vo.setIsInterferenceTarget(src.getIsInterferenceTarget());
vo.setIsDefendImportantPlace(src.getIsDefendImportantPlace());
vo.setGroupType(src.getGroupType());
vo.setEquipmentId(src.getEquipmentId());
vo.setName(src.getName());
vo.setOwnerForceSide(src.getOwnerForceSide());
vo.setPlatId(src.getPlatId());
vo.setSubComponents(FireRuleRedWeaponOutputFillHelper.toOutputSubComponents(src.getSubComponents()));
return vo;
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -3,35 +3,86 @@ package rules;
import com.solution.rule.domain.ultimately.fact.DroolsFact;
import java.util.Map;
import com.solution.rule.utils.RuleFunction.equipmentRule;
import static com.solution.rule.utils.RuleFunction.equipmentRule;
global java.util.Map globalParams;
/**
* 构建装备匹配所需的全部可调参数(会 merge 进 globalParams覆盖 Java 侧同名默认值)。
*
* ========== 总体运算逻辑(与 RuleFunction.equipmentRule 一致)==========
* 1先拼「蓝方文本串」blueBlob任务 drawName、dataType、taskWeapons 下各武器的 name/supportType/equipmentId、组件 deviceName。
* 2对每个红方装备拼「红方文本串」redBlobname、platform_type、SupportType。
* 3每件红装得分 score = 规则槽得分(scoreRuleSlots) + 兼容层得分(scoreLegacyLayer),均为整数。
* - 规则槽:对 i=1..ruleSlotCount若 blueBlob 命中 blueRuleKeywords_i 且 redBlob 命中 redRuleKeywords_i
* 则加上 ruleScore_i * weight。
* - 兼容层:多组「蓝关键词 + 红关键词 + 对应分数」,见下方各键说明;每组条件同时满足则加上 对应Score * weight。
* 4在池中取 score 最大者;若多人并列,由 tieBreak 决定(见 tieBreak
* 5若 maxScore < minSelectedScore视为未匹配不往 fireRuleInputs 追加行redWeapons 输出仍为当前池。
* 6若匹配成功从池中 remove 该件fireRuleInputs 追加一行drawName 后接 outputDrawNameSuffixtaskWeapons 填选中红装映射。
*
* 关键词格式英文逗号分隔子串包含即算命中contains不要求整词匹配。
*/
function Map buildParam(){
Map param = new java.util.HashMap();
//权重因子
param.put("weight", 1);
//最低入选分数
param.put("minSelectedScore",1);
//蓝方坦克类 -> 红方反坦克加分
param.put("tankScore", 1);
//蓝方空中类 -> 红方反空中加分
param.put("airScore", 2);
//蓝方地面类 -> 红方远程打击加分
param.put("groundScore", 1);
//蓝方有导弹 -> 红方防空加分
param.put("missileScore", 1);
//蓝方是空中任务 -> 红方防空加分
param.put("airTaskScore", 10);
// ---------- 全局倍率与门槛 ----------
// weight对上述所有「基础分数」的统一乘数规则槽的 ruleScore_i、兼容层的 *Score 都会乘 weight
param.put("weight", 1);
// minSelectedScore单件红装总分达到该值及以上才会被选中并写入 fireRuleInputs否则本任务视为未匹配到装备。
param.put("minSelectedScore", 1);
// tieBreak并列最高分时的决胜方式。当前实现仅支持 "equipmentId":装备 ID 字典序更小的优先。
param.put("tieBreak", "equipmentId");
// outputDrawNameSuffix匹配成功写入 fireRuleInputs 时,在蓝方原 drawName 后面拼接的后缀(如「打击任务」)。
param.put("outputDrawNameSuffix", "打击任务");
// ---------- 规则槽(可配置条数,便于只改本文件而不改 Java----------
// ruleSlotCount启用几条槽规则第 i 条使用 blueRuleKeywords_i、redRuleKeywords_i、ruleScore_i。
param.put("ruleSlotCount", 3);
// 槽1蓝方文本中出现任一子串 且 红方文本中出现任一子串 → 加 ruleScore_1 * weight。
param.put("blueRuleKeywords_1", "F-16,F-35");
param.put("redRuleKeywords_1", "防空,导弹,无人机");
param.put("ruleScore_1", 5);
param.put("blueRuleKeywords_2", "坦克,装甲");
param.put("redRuleKeywords_2", "反坦克");
param.put("ruleScore_2", 4);
param.put("blueRuleKeywords_3", "地面,突击");
param.put("redRuleKeywords_3", "远火,榴弹,炮");
param.put("ruleScore_3", 2);
// ---------- 兼容层:按「场景关键词」配对加分(与 RuleFunction.scoreLegacyLayer 一一对应)----------
// 以下每组均为:蓝方文本命中第一列关键词 且 红方文本命中第二列关键词 → 加 第三列分数 * weight。
//
// ① 空中平台类:蓝方像「对空/机型」且红方像对空装备 → + airScore * weight
param.put("bluePlatformKeywords_air", "F-16,J-10,F-35");
param.put("redPreferredWhenBlueAir", "防空,导弹,无人机,直升机,空空");
param.put("airScore", 2);
// ② 任务文案像空中任务 且 红方偏好对空 → + airTaskScore * weight可与①叠加
param.put("airTaskKeywords", "空中,制空,拦截,空战");
param.put("airTaskScore", 10);
// ③ 任务文案像地面任务 且 红方偏好地面火力 → + groundScore * weight
param.put("groundTaskKeywords", "地面,突击,登陆");
param.put("redPreferredWhenGround", "远火,榴弹,炮,火箭");
param.put("groundScore", 1);
// ④ 蓝方像坦克/装甲目标 且 红方文本命中 redMatchKeywords_tank → + tankScore * weight
param.put("tankKeywords", "坦克,装甲");
param.put("redMatchKeywords_tank", "反坦克");
param.put("tankScore", 1);
// ⑤ 蓝方像导弹类 且 红方文本命中 redMatchKeywords_missile → + missileScore * weight
param.put("missileKeywords", "导弹,火箭弹,巡航");
param.put("redMatchKeywords_missile", "防空,导弹,导弹发射");
param.put("missileScore", 1);
return param;
}
rule "装备匹配"
salience 100
when
$fact : DroolsFact(task.side != "")
$fact : DroolsFact(task != null)
then
//如何引入Java静态方法
// 以本文件 buildParam 为真源覆盖同名键,再执行 Java 侧匹配逻辑
globalParams.putAll(buildParam());
equipmentRule($fact, globalParams);
end