火力规则:装备匹配规则实现
This commit is contained in:
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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 < minSelectedScore:不追加 fireRuleInputs;仍将当前池映射到 FireRuleOutputVO.redWeapons。</li>
|
||||
* <li>否则:池中 remove 选中项;fireRuleInputs 追加一行,drawName += outputDrawNameSuffix,taskWeapons 一条映射自选中红装;redWeapons 为剩余池。</li>
|
||||
* </ul>
|
||||
*/
|
||||
public class RuleFunction {
|
||||
public final class RuleFunction {
|
||||
|
||||
private RuleFunction() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 装备匹配
|
||||
* 单轮规则执行:对当前 DroolsFact 的一条蓝方任务,从 redWeapons 中选一件装备并写回输出。
|
||||
* <p>
|
||||
* 参数 p(globalParams)中各键含义与运算见 {@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 字典序比较结果(>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
@@ -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)对每个红方装备拼「红方文本串」redBlob:name、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 后接 outputDrawNameSuffix;taskWeapons 填选中红装映射。
|
||||
*
|
||||
* 关键词格式:英文逗号分隔,子串包含即算命中(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
|
||||
|
||||
Reference in New Issue
Block a user