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 entry: 5
}, },
allowedSize: [[13, 13]], allowedSize: [[13, 13]],
allowLargeDoorCluster: false,
allowLargeEnemyCluster: false,
allowIdleBranch: false,
allowRepeatedGuardIdleBranch: false,
allowUselessBranch: false, allowUselessBranch: false,
maxWallDensityStd: 1, maxWallDensityStd: 1,
maxEmptyArea: 8, maxEmptyArea: 8,

View File

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

View File

@ -87,6 +87,10 @@ const config: IAutoLabelConfig = {
entry: 5 entry: 5
}, },
allowedSize: [[5, 5]], allowedSize: [[5, 5]],
allowLargeDoorCluster: false,
allowLargeEnemyCluster: false,
allowIdleBranch: false,
allowRepeatedGuardIdleBranch: false,
allowUselessBranch: false, allowUselessBranch: false,
minEnemyRatio: 0, minEnemyRatio: 0,
maxEnemyRatio: 1, 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', () => { describe('parseFloorInfo useless branch detection', () => {
it('marks a branch with only one grid-level passable direction as useless', () => { it('marks a branch with only one grid-level passable direction as useless', () => {
const floor = parseTestFloor([ const floor = parseTestFloor([
@ -259,3 +280,18 @@ describe('parseFloorInfo repeated guard idle detection', () => {
expect(floor.hasRepeatedGuardIdleBranch).toBe(false); 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 { readFile } from 'fs/promises';
import { import {
BranchType, BranchType,
CannotInOut,
GraphNodeType, GraphNodeType,
IAutoLabelConfig, IAutoLabelConfig,
IFloorInfo, IFloorInfo,
@ -28,11 +27,11 @@ import { gaussainHeatmap, generateHeatmap } from './heatmap';
import { MapTopology } from './topo'; import { MapTopology } from './topo';
// 格子层四方向通行检查,供无用分支主算法复用。 // 格子层四方向通行检查,供无用分支主算法复用。
const branchCheckDirs: [number, number, CannotInOut, CannotInOut][] = [ const branchCheckDirs: [number, number][] = [
[-1, 0, CannotInOut.Left, CannotInOut.Right], [-1, 0],
[1, 0, CannotInOut.Right, CannotInOut.Left], [1, 0],
[0, -1, CannotInOut.Top, CannotInOut.Bottom], [0, -1],
[0, 1, CannotInOut.Bottom, CannotInOut.Top] [0, 1]
]; ];
interface IRawTowerInfo { interface IRawTowerInfo {
@ -361,43 +360,23 @@ function getNodeTile(node: MapGraphNode): number {
* Empty / Resource * Empty / Resource
* *
* *
* `cannotIn/cannotOut`
*
*
* @param topo * @param topo
* @param from 使 y * width + x
* @param to * @param to
* @param outFlag * @returns
* @param inFlag
* @returns from -> to to -> from
*/ */
function hasGridPassage( function hasGridPassage(topo: MapTopology, to: number): boolean {
topo: MapTopology,
from: number,
to: number,
outFlag: CannotInOut,
inFlag: CannotInOut
): boolean {
const width = topo.convertedMap[0]?.length ?? 0; const width = topo.convertedMap[0]?.length ?? 0;
const fromX = from % width;
const fromY = (from - fromX) / width;
const toX = to % width; const toX = to % width;
const toY = (to - toX) / width; const toY = (to - toX) / width;
if ( if (topo.convertedMap[toY]?.[toX] == null) {
topo.noPass[fromY]?.[fromX] ||
topo.noPass[toY]?.[toX] ||
topo.convertedMap[toY]?.[toX] == null
) {
return false; return false;
} }
const canGo = return topo.convertedMap[toY][toX] !== topo.config.wall;
!(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;
} }
/** /**
@ -418,7 +397,7 @@ function countGridPassableDirections(topo: MapTopology, tile: number): number {
const y = (tile - x) / width; const y = (tile - x) / width;
let count = 0; let count = 0;
for (const [dx, dy, outFlag, inFlag] of branchCheckDirs) { for (const [dx, dy] of branchCheckDirs) {
const nx = x + dx; const nx = x + dx;
const ny = y + dy; const ny = y + dy;
if (nx < 0 || nx >= width || ny < 0 || ny >= height) { 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; const nextTile = ny * width + nx;
if (hasGridPassage(topo, tile, nextTile, outFlag, inFlag)) { if (hasGridPassage(topo, nextTile)) {
count++; count++;
} }
} }

View File

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