feat(tools): 添加 CLI 模块管理命令和文档验证 demos

- CLI 新增 list/add/remove 命令管理项目模块
- 创建 demos 包验证模块文档正确性
- 包含 Timer/FSM/Pathfinding/Procgen/Spatial 5个模块的完整测试
This commit is contained in:
yhh
2025-12-26 22:09:01 +08:00
parent 4a16e30794
commit 881ffad3bc
11 changed files with 1460 additions and 4 deletions

View File

@@ -0,0 +1,164 @@
/**
* Pathfinding Module Demo - Tests APIs from docs/modules/pathfinding/index.md
*/
import {
createGridMap,
createAStarPathfinder,
createLineOfSightSmoother,
createCatmullRomSmoother,
manhattanDistance,
octileDistance
} from '@esengine/pathfinding';
function assert(condition: boolean, message: string): void {
if (!condition) throw new Error(`FAILED: ${message}`);
console.log(`${message}`);
}
function section(name: string): void {
console.log(`\n▶ ${name}`);
}
export async function runPathfindingDemo(): Promise<void> {
console.log('═══════════════════════════════════════');
console.log(' Pathfinding Module Demo');
console.log('═══════════════════════════════════════');
// 1. Create Grid Map
section('1. createGridMap()');
const grid = createGridMap(20, 20);
assert(grid !== null, 'Grid created');
assert(grid.width === 20, 'Width is 20');
assert(grid.height === 20, 'Height is 20');
// 2. Walkability
section('2. setWalkable() / isWalkable()');
assert(grid.isWalkable(5, 5), 'Initially walkable');
grid.setWalkable(5, 5, false);
assert(!grid.isWalkable(5, 5), 'Set to not walkable');
grid.setWalkable(5, 5, true);
assert(grid.isWalkable(5, 5), 'Restored to walkable');
// 3. Set Obstacles
section('3. Setting Obstacles');
grid.setWalkable(5, 5, false);
grid.setWalkable(5, 6, false);
grid.setWalkable(5, 7, false);
assert(!grid.isWalkable(5, 6), 'Obstacle set');
// 4. Create Pathfinder
section('4. createAStarPathfinder()');
const pathfinder = createAStarPathfinder(grid);
assert(pathfinder !== null, 'Pathfinder created');
// 5. Find Path
section('5. findPath()');
const result = pathfinder.findPath(0, 0, 15, 15);
assert(result.found, 'Path found');
assert(result.path.length > 0, `Path has ${result.path.length} points`);
assert(result.cost > 0, `Path cost: ${result.cost.toFixed(2)}`);
assert(result.nodesSearched > 0, `Searched ${result.nodesSearched} nodes`);
// 6. Path Blocked
section('6. Path Blocked');
// Create a wall
for (let y = 0; y < 20; y++) {
grid.setWalkable(10, y, false);
}
const blocked = pathfinder.findPath(0, 0, 15, 15);
assert(!blocked.found, 'No path when fully blocked');
// Clear wall
for (let y = 0; y < 20; y++) {
grid.setWalkable(10, y, true);
}
// 7. Movement Cost
section('7. setCost()');
const gridCost = createGridMap(10, 10);
gridCost.setCost(5, 5, 10); // High cost tile
const costResult = createAStarPathfinder(gridCost).findPath(0, 0, 9, 9);
assert(costResult.found, 'Path found with cost');
// 8. Heuristics
section('8. Heuristic Functions');
const d1 = manhattanDistance({ x: 0, y: 0 }, { x: 3, y: 4 });
assert(d1 === 7, `Manhattan distance: ${d1}`);
const d2 = octileDistance({ x: 0, y: 0 }, { x: 3, y: 4 });
assert(d2 > 0, `Octile distance: ${d2.toFixed(2)}`);
// 9. Grid Options
section('9. Grid Options');
const gridOpts = createGridMap(10, 10, {
allowDiagonal: false,
heuristic: manhattanDistance
});
assert(gridOpts !== null, 'Grid with options created');
// 10. Path Smoothing - Line of Sight
section('10. Line of Sight Smoother');
const gridSmooth = createGridMap(20, 20);
const pf = createAStarPathfinder(gridSmooth);
const rawPath = pf.findPath(0, 0, 10, 10);
const losSmoother = createLineOfSightSmoother();
const smoothed = losSmoother.smooth(rawPath.path, gridSmooth);
assert(smoothed.length <= rawPath.path.length, `Smoothed: ${rawPath.path.length} -> ${smoothed.length} points`);
// 11. Catmull-Rom Smoother
section('11. Catmull-Rom Smoother');
const crSmoother = createCatmullRomSmoother(5, 0.5);
const curved = crSmoother.smooth(rawPath.path, gridSmooth);
assert(curved.length >= rawPath.path.length, `Curved path has ${curved.length} points`);
// 12. loadFromArray
section('12. loadFromArray()');
const gridArr = createGridMap(5, 3);
gridArr.loadFromArray([
[0, 0, 0, 1, 0],
[0, 1, 0, 1, 0],
[0, 1, 0, 0, 0]
]);
assert(!gridArr.isWalkable(3, 0), 'Loaded obstacle at (3,0)');
assert(!gridArr.isWalkable(1, 1), 'Loaded obstacle at (1,1)');
assert(gridArr.isWalkable(0, 0), 'Loaded walkable at (0,0)');
// 13. loadFromString
section('13. loadFromString()');
const gridStr = createGridMap(5, 3);
gridStr.loadFromString(`
.....
.#.#.
.#...
`);
assert(!gridStr.isWalkable(1, 1), 'Loaded # as obstacle');
assert(gridStr.isWalkable(0, 0), 'Loaded . as walkable');
// 14. setRectWalkable
section('14. setRectWalkable()');
const gridRect = createGridMap(10, 10);
gridRect.setRectWalkable(2, 2, 4, 4, false);
assert(!gridRect.isWalkable(3, 3), 'Rect set as obstacle');
assert(gridRect.isWalkable(0, 0), 'Outside rect is walkable');
// 15. Pathfinder Options
section('15. Pathfinder Options');
const limitedResult = pathfinder.findPath(0, 0, 15, 15, {
maxNodes: 100,
heuristicWeight: 1.5
});
assert(limitedResult !== null, 'findPath with options works');
// 16. Reset Grid
section('16. reset()');
grid.reset();
assert(grid.isWalkable(5, 5), 'Grid reset - all walkable');
console.log('\n═══════════════════════════════════════');
console.log(' Pathfinding Demo: ALL TESTS PASSED ✓');
console.log('═══════════════════════════════════════\n');
}
runPathfindingDemo().catch(console.error);