chore: 调整过滤算法

This commit is contained in:
unanmed 2026-05-25 13:59:44 +08:00
parent 202decf476
commit 4b63de743a
5 changed files with 86 additions and 49 deletions

View File

@ -254,6 +254,10 @@ const labelConfig: IAutoLabelConfig = {
entry: 5
},
allowedSize: [[13, 13]],
allowLargeDoorCluster: false,
allowLargeEnemyCluster: false,
allowIdleBranch: false,
allowRepeatedGuardIdleBranch: false,
allowUselessBranch: false,
maxWallDensityStd: 1,
maxEmptyArea: 8,

View File

@ -176,34 +176,44 @@ export async function autoLabelTowers(
ignoredFloorsEntry++;
continue;
}
if (floorInfo.hasLargeDoorCluster) {
const filteredByLargeDoorCluster =
!config.allowLargeDoorCluster && floorInfo.hasLargeDoorCluster;
const filteredByLargeEnemyCluster =
!config.allowLargeEnemyCluster &&
floorInfo.hasLargeEnemyCluster;
if (filteredByLargeDoorCluster) {
ignoredFloorsContinuousDoor++;
}
if (floorInfo.hasLargeEnemyCluster) {
if (filteredByLargeEnemyCluster) {
ignoredFloorsContinuousEnemy++;
}
if (
floorInfo.hasLargeDoorCluster ||
floorInfo.hasLargeEnemyCluster
) {
if (filteredByLargeDoorCluster || filteredByLargeEnemyCluster) {
ignoredFloorsContinuous++;
continue;
}
if (
floorInfo.idleDoorBranchCount > 0 ||
floorInfo.repeatedGuardDoorBranchCount > 0
) {
const filteredByIdleDoorBranch =
!config.allowIdleBranch && floorInfo.idleDoorBranchCount > 0;
const filteredByRepeatedGuardDoorBranch =
!config.allowRepeatedGuardIdleBranch &&
floorInfo.repeatedGuardDoorBranchCount > 0;
const filteredByIdleEnemyBranch =
!config.allowIdleBranch && floorInfo.idleEnemyBranchCount > 0;
const filteredByRepeatedGuardEnemyBranch =
!config.allowRepeatedGuardIdleBranch &&
floorInfo.repeatedGuardEnemyBranchCount > 0;
if (filteredByIdleDoorBranch || filteredByRepeatedGuardDoorBranch) {
ignoredFloorsIdleDoor++;
}
if (
floorInfo.idleEnemyBranchCount > 0 ||
floorInfo.repeatedGuardEnemyBranchCount > 0
filteredByIdleEnemyBranch ||
filteredByRepeatedGuardEnemyBranch
) {
ignoredFloorsIdleEnemy++;
}
if (
floorInfo.hasIdleBranch ||
floorInfo.hasRepeatedGuardIdleBranch
(!config.allowIdleBranch && floorInfo.hasIdleBranch) ||
(!config.allowRepeatedGuardIdleBranch &&
floorInfo.hasRepeatedGuardIdleBranch)
) {
ignoredFloorsIdle++;
continue;

View File

@ -87,6 +87,10 @@ const config: IAutoLabelConfig = {
entry: 5
},
allowedSize: [[5, 5]],
allowLargeDoorCluster: false,
allowLargeEnemyCluster: false,
allowIdleBranch: false,
allowRepeatedGuardIdleBranch: false,
allowUselessBranch: false,
minEnemyRatio: 0,
maxEnemyRatio: 1,
@ -120,6 +124,23 @@ function parseTestFloor(map: number[][]) {
);
}
function parseTestFloorWithConverter(
map: number[][],
converter: IMapTileConverter
) {
return parseFloorInfo(tower, map, map, [], config, converter, 'F1');
}
class BlockedDirectionConverter extends MockTileConverter {
getCannotIn(): number {
return 0b1111;
}
getCannotOut(): number {
return 0b1111;
}
}
describe('parseFloorInfo useless branch detection', () => {
it('marks a branch with only one grid-level passable direction as useless', () => {
const floor = parseTestFloor([
@ -259,3 +280,18 @@ describe('parseFloorInfo repeated guard idle detection', () => {
expect(floor.hasRepeatedGuardIdleBranch).toBe(false);
});
});
describe('parseFloorInfo wall-only passability detection', () => {
it('ignores cannotIn and cannotOut flags in the useless branch quick check', () => {
const floor = parseTestFloorWithConverter(
[
[1, 1, 1, 1, 1, 1],
[1, 5, 2, 4, 3, 1],
[1, 1, 1, 1, 1, 1]
],
new BlockedDirectionConverter()
);
expect(floor.hasUselessBranch).toBe(false);
});
});

View File

@ -1,7 +1,6 @@
import { readFile } from 'fs/promises';
import {
BranchType,
CannotInOut,
GraphNodeType,
IAutoLabelConfig,
IFloorInfo,
@ -28,11 +27,11 @@ import { gaussainHeatmap, generateHeatmap } from './heatmap';
import { MapTopology } from './topo';
// 格子层四方向通行检查,供无用分支主算法复用。
const branchCheckDirs: [number, number, CannotInOut, CannotInOut][] = [
[-1, 0, CannotInOut.Left, CannotInOut.Right],
[1, 0, CannotInOut.Right, CannotInOut.Left],
[0, -1, CannotInOut.Top, CannotInOut.Bottom],
[0, 1, CannotInOut.Bottom, CannotInOut.Top]
const branchCheckDirs: [number, number][] = [
[-1, 0],
[1, 0],
[0, -1],
[0, 1]
];
interface IRawTowerInfo {
@ -361,43 +360,23 @@ function getNodeTile(node: MapGraphNode): number {
* Empty / Resource
*
*
* `cannotIn/cannotOut`
*
*
* @param topo
* @param from 使 y * width + x
* @param to
* @param outFlag
* @param inFlag
* @returns from -> to to -> from
* @returns
*/
function hasGridPassage(
topo: MapTopology,
from: number,
to: number,
outFlag: CannotInOut,
inFlag: CannotInOut
): boolean {
function hasGridPassage(topo: MapTopology, to: number): boolean {
const width = topo.convertedMap[0]?.length ?? 0;
const fromX = from % width;
const fromY = (from - fromX) / width;
const toX = to % width;
const toY = (to - toX) / width;
if (
topo.noPass[fromY]?.[fromX] ||
topo.noPass[toY]?.[toX] ||
topo.convertedMap[toY]?.[toX] == null
) {
if (topo.convertedMap[toY]?.[toX] == null) {
return false;
}
const canGo =
!(topo.cannotOut[fromY][fromX] & outFlag) &&
!(topo.cannotIn[toY][toX] & inFlag);
const canCome =
!(topo.cannotOut[toY][toX] & inFlag) &&
!(topo.cannotIn[fromY][fromX] & outFlag);
// 与构图逻辑保持一致:双向只要任一方向能通过,就视为这两个格子存在通路。
return canGo || canCome;
return topo.convertedMap[toY][toX] !== topo.config.wall;
}
/**
@ -418,7 +397,7 @@ function countGridPassableDirections(topo: MapTopology, tile: number): number {
const y = (tile - x) / width;
let count = 0;
for (const [dx, dy, outFlag, inFlag] of branchCheckDirs) {
for (const [dx, dy] of branchCheckDirs) {
const nx = x + dx;
const ny = y + dy;
if (nx < 0 || nx >= width || ny < 0 || ny >= height) {
@ -426,7 +405,7 @@ function countGridPassableDirections(topo: MapTopology, tile: number): number {
}
const nextTile = ny * width + nx;
if (hasGridPassage(topo, tile, nextTile, outFlag, inFlag)) {
if (hasGridPassage(topo, nextTile)) {
count++;
}
}

View File

@ -188,6 +188,14 @@ export interface IAutoLabelConfig {
readonly classes: Readonly<IMapBlockConfig>;
/** 地图允许大小 */
readonly allowedSize: [number, number][];
/** 是否允许同类门分支连通块大小超过 3 */
readonly allowLargeDoorCluster: boolean;
/** 是否允许同类怪分支连通块大小超过 3 */
readonly allowLargeEnemyCluster: boolean;
/** 是否允许拓扑图上只连接一个邻居的闲置分支 */
readonly allowIdleBranch: boolean;
/** 是否允许重复守同一连通区域的闲置分支 */
readonly allowRepeatedGuardIdleBranch: boolean;
/** 是否允许无用节点 */
readonly allowUselessBranch: boolean;
/** 最小怪物占比 */