@cc-component/cc-pathfinding 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. package/.cc-ex-component.md +12 -0
  2. package/assets/pathfinding/Interface.ts +73 -0
  3. package/assets/pathfinding/Interface.ts.meta +9 -0
  4. package/assets/pathfinding/base/AStarPathfinder.ts +264 -0
  5. package/assets/pathfinding/base/AStarPathfinder.ts.meta +9 -0
  6. package/assets/pathfinding/base/Common.ts +342 -0
  7. package/assets/pathfinding/base/Common.ts.meta +9 -0
  8. package/assets/pathfinding/base/GridMap.ts +314 -0
  9. package/assets/pathfinding/base/GridMap.ts.meta +9 -0
  10. package/assets/pathfinding/base/GridMovementController.ts +257 -0
  11. package/assets/pathfinding/base/GridMovementController.ts.meta +9 -0
  12. package/assets/pathfinding/base/IMapData.ts +13 -0
  13. package/assets/pathfinding/base/IMapData.ts.meta +9 -0
  14. package/assets/pathfinding/base/IPlayerBuff.ts +1 -0
  15. package/assets/pathfinding/base/IPlayerBuff.ts.meta +9 -0
  16. package/assets/pathfinding/base/MapBoxView.ts +386 -0
  17. package/assets/pathfinding/base/MapBoxView.ts.meta +9 -0
  18. package/assets/pathfinding/base/MapViewModel.ts +17 -0
  19. package/assets/pathfinding/base/MapViewModel.ts.meta +9 -0
  20. package/assets/pathfinding/base/Mapitem.ts +38 -0
  21. package/assets/pathfinding/base/Mapitem.ts.meta +9 -0
  22. package/assets/pathfinding/base/PathGridStateManager.ts +94 -0
  23. package/assets/pathfinding/base/PathGridStateManager.ts.meta +9 -0
  24. package/assets/pathfinding/base/PathfindingModule.ts +161 -0
  25. package/assets/pathfinding/base/PathfindingModule.ts.meta +9 -0
  26. package/assets/pathfinding/base/prefab/map_root.prefab +16290 -0
  27. package/assets/pathfinding/base/prefab/map_root.prefab.meta +13 -0
  28. package/assets/pathfinding/base/prefab.meta +9 -0
  29. package/assets/pathfinding/base.meta +9 -0
  30. package/assets/pathfinding.meta +9 -0
  31. package/assets.meta +9 -0
  32. package/index.ts +9 -0
  33. package/index.ts.meta +9 -0
  34. package/package.json +19 -0
  35. package/package.json.meta +11 -0
@@ -0,0 +1,342 @@
1
+
2
+
3
+ export enum HeroType {
4
+ Hero = 1,
5
+ Home = 2,
6
+ XiaoBing = 3,
7
+
8
+ }
9
+ export enum RaceType {
10
+ /**人族 */
11
+ Ren = 0,
12
+ /**兽族 */
13
+ Shou = 1,
14
+ /**精灵 */
15
+ JingLin = 2,
16
+ /**魔族 */
17
+ BOSS = 3
18
+ }
19
+ export enum AttackDistance {
20
+ attack_0 = 0,
21
+ attack_1 = 1,
22
+ attack_2 = 2,
23
+ attack_3 = 3,
24
+ attack_4 = 4,
25
+ attack_5 = 5,
26
+ attack_6 = 6,
27
+ attack_7 = 7,
28
+ attack_8 = 8,
29
+ attack_9 = 9,
30
+ attack_10 = 10
31
+ }
32
+ export enum PlayerAnimation {
33
+ NONE = "NONE",
34
+ IDLE = "idle",
35
+ WALK = "walk",
36
+ ATTACK = "attack",
37
+ DEAD = "dead",
38
+
39
+ }
40
+ export const AttackDistanceConfig = {
41
+ [100]: AttackDistance.attack_1,
42
+ [200]: AttackDistance.attack_2,
43
+ [300]: AttackDistance.attack_3,
44
+ [400]: AttackDistance.attack_4,
45
+ [500]: AttackDistance.attack_5,
46
+ }
47
+
48
+
49
+ export interface IBaseSkill {
50
+ /**技能图标 */
51
+ iconPath: string;
52
+ /**是否添加技能 */
53
+ isAdd: boolean;
54
+ /**技能名称 */
55
+ name: string
56
+ /**技能描述 */
57
+ des: string;
58
+ /**buff类型 */
59
+ buffType: BuffType;
60
+ /**技能id */
61
+ id: number;
62
+ }
63
+
64
+ export enum PlayerCamp {
65
+ None = -1,
66
+ Red = 1,
67
+ Blue = 2,
68
+ Yellow = 3,
69
+ Green = 4,
70
+ White = 5,
71
+ Black = 6,
72
+ Purple = 7,
73
+ Orange = 8,
74
+ Pink = 9,
75
+ Gray = 10,
76
+ Brown = 11,
77
+ Cyan = 12,
78
+ Magenta = 13,
79
+ Turquoise = 14,
80
+ }
81
+ export enum BuffType {
82
+ Buff_none = -1,
83
+
84
+ /**暴击 */
85
+ Buff_BaoJi = 1,
86
+ /**暴击 */
87
+ Buff_BaoJi_zd,
88
+ /** 杀死敌人增加攻击力,2.5% */
89
+ AttackAdd,
90
+ /** 杀死敌人增加攻击速度,2.5% */
91
+ AttackAddSpeed,
92
+
93
+ /**加攻速-光环 */
94
+ AttackSpeedAll,
95
+ AttackSpeedAll_zd,
96
+ /**加攻击力-光环-百分比 */
97
+ AttackValueScaleAll,
98
+ AttackValueScaleAll_zd,
99
+
100
+
101
+ /**加固定攻击力-光环 */
102
+ AttackValueAll,
103
+ AttackValueAll_zd,
104
+
105
+ /**死亡加金币 */
106
+ DeadAddCoin,
107
+ /**吸血 */
108
+ Buff_XiXie,
109
+ Buff_XiXie_zd,
110
+ /**中毒百分比 */
111
+ Buff_DuScale,
112
+ Buff_DuScale_zd,
113
+ /**中毒固定 */
114
+ Buff_Du,
115
+ Buff_Du_zd,
116
+ /**攻击百分比伤害 */
117
+ AttackScale,
118
+ AttackScale_zd,
119
+
120
+
121
+ /**伤害减免*/
122
+ DamageReduction,
123
+ DamageReduction_zd,
124
+
125
+ /**伤害减免-固定值 */
126
+ DamageReductionGD,
127
+ DamageReductionGD_zd,
128
+
129
+ /**加物理防御和魔法防御 */
130
+ // Defense,
131
+ // Defense_zd,
132
+
133
+ /**魔法防御-物理防御 */
134
+ PhysicalDdefense,
135
+ MagicDefense,
136
+ /**破甲-魔法 */
137
+ Buff_PoJia_MF,
138
+ Buff_PoJia_MF_zd,
139
+ /**破甲-物理 */
140
+ Buff_PoJia_WL,
141
+ Buff_PoJia_WL_zd,
142
+
143
+ /** 眩晕*/
144
+ Buff_XuanYun,
145
+ Buff_XuanYun_baotou,
146
+ Buff_XuanYun_zd,
147
+ /**生命-固定 */
148
+ HP,
149
+ //HP_zd,
150
+ /**生命-百分比 */
151
+ HPScale,
152
+ //HPScale_zd,
153
+
154
+ // Buff_DianRan_zd,
155
+
156
+
157
+
158
+
159
+ /**闪避率 */
160
+ Dodge,
161
+ /**命中率 */
162
+ Hit,
163
+ /**生命回复-固定 */
164
+ HPAdd,
165
+ /**生命回复-百分比 */
166
+ HPAddScale,
167
+ /**魔法回复-固定 */
168
+ MPAdd,
169
+ /**点燃 */
170
+ Buff_DianRan,
171
+ //主动技能>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
172
+ /**雷电术 */
173
+ Lightning = 200,
174
+ /**冰冻术 */
175
+ BDS,
176
+ /**流星雨 */
177
+ LXY,
178
+ /**冰咆哮-范围 */
179
+ BPX,
180
+
181
+ }
182
+
183
+
184
+ export enum AttackType {
185
+ /**物理攻击 */
186
+ Physical = 1,
187
+ /**魔法攻击 */
188
+ Magic = 2,
189
+ }
190
+ export enum AttackTypeBullet {
191
+ /**抛物线 */
192
+ Parabola = 1,
193
+ /**直线 */
194
+ Line = 2,
195
+ }
196
+
197
+ // export class TWeapon {
198
+ // id: ZBType;
199
+ // name: string;
200
+ // long: boolean;//长期永久装备
201
+ // iconPath: string;
202
+ // des_list: string[];
203
+ // des_skill_list: string[];
204
+ // buffList: BaseSkill[] = [];
205
+ // isZhuDong: boolean;//是主动技能
206
+
207
+
208
+ // constructor(weaponData?: TWeapon) {
209
+ // if (weaponData) {
210
+ // // 创建目标类型的新实例
211
+ // const instance = this
212
+ // // 深拷贝所有属性
213
+ // Object.keys(weaponData).forEach(key => {
214
+ // const value = weaponData[key as keyof TWeapon];
215
+ // if (Array.isArray(value)) {
216
+ // // 特殊处理 BaseSkill 数组
217
+ // if (key === 'buffList') {
218
+ // // 对 BaseSkill[] 进行深度克隆
219
+ // (instance as any)[key] = (value as BaseSkill[]).map(skill => {
220
+ // const newSkill = new BaseSkill();
221
+ // return Object.assign(newSkill, skill);
222
+ // });
223
+ // } else {
224
+ // // 对其他数组进行浅拷贝
225
+ // (instance as any)[key] = [...value];
226
+ // }
227
+ // } else {
228
+ // // 对基本类型和对象引用进行浅拷贝
229
+ // (instance as any)[key] = value;
230
+ // }
231
+ // });
232
+ // }
233
+ // }
234
+ // /**
235
+ // * 深克隆方法,用于将当前实例转换为指定的子类类型,同时处理嵌套对象
236
+ // * @param ctor - 目标类的构造函数
237
+ // * @returns 返回指定类型的实例,包含当前实例的所有属性值(深度复制)
238
+ // * @template T - 继承自 TWeapon 的泛型类型
239
+ // */
240
+ // deepClone<T extends TWeapon>(ctor: new () => T): T {
241
+ // // 创建目标类型的新实例
242
+ // const instance = new ctor();
243
+ // // 深拷贝所有属性
244
+ // Object.keys(this).forEach(key => {
245
+ // const value = this[key as keyof TWeapon];
246
+ // if (Array.isArray(value)) {
247
+ // // 特殊处理 BaseSkill 数组
248
+ // if (key === 'buffList') {
249
+ // // 对 BaseSkill[] 进行深度克隆
250
+ // (instance as any)[key] = (value as any[]).map(skill => {
251
+ // const newSkill = new BaseSkill();
252
+ // return Object.assign(newSkill, skill);
253
+ // });
254
+ // } else {
255
+ // // 对其他数组进行浅拷贝
256
+ // (instance as any)[key] = [...value];
257
+ // }
258
+ // } else {
259
+ // // 对基本类型和对象引用进行浅拷贝
260
+ // (instance as any)[key] = value;
261
+ // }
262
+ // });
263
+
264
+ // return instance;
265
+ // }
266
+ // }
267
+
268
+ // export class TWeapon extends TWeapon {
269
+ // /**是否携带武器 */
270
+ // is_carry = false;
271
+ // /**
272
+ // * 构造函数,可选择性地使用 Partial<TWeapon> 对象初始化属性
273
+ // * @param weaponData - 包含部分或全部 TWeapon 属性的对象,用于初始化实例
274
+ // */
275
+ // constructor(weaponData?: TWeapon) {
276
+ // super();
277
+ // if (weaponData) {
278
+ // // 使用 Object.assign 将传入对象的属性复制到当前实例
279
+ // Object.assign(this, weaponData);
280
+ // }
281
+ // }
282
+ // }
283
+
284
+ export class BaseSkill implements IBaseSkill {
285
+ /**技能id */
286
+ id: number;
287
+ /**技能名称 */
288
+ name: string;
289
+ /**技能图标*/
290
+ iconPath: string;
291
+ /**技能类型*/
292
+ buffType: BuffType;
293
+ /**是被动技能还是主动技能,默认主动技能*/
294
+ isZhuDong: boolean = true;
295
+ /**技能描述 */
296
+ des: string;
297
+ /**技能时间 */
298
+ time: number = 0
299
+ /**技能值 */
300
+ attackValue: number = 0
301
+ /**概率触发 */
302
+ attackValueRatio: number = 0
303
+ /**是否群体技能 */
304
+ isAll: boolean = false
305
+ //#region 动态属性
306
+ /*释放技能*/
307
+ isPlaySkill: boolean = false
308
+ /**是否添加了这个技能 */
309
+ isAdd: boolean;
310
+
311
+ constructor(data?: BaseSkill) {
312
+ Object.assign(this, data)
313
+
314
+ }
315
+
316
+ }
317
+
318
+ /**英雄类型 */
319
+ export enum PlayerType {
320
+ /**士兵 */
321
+ shibing = 0,
322
+ /**火枪手 */
323
+ huo_qiang = 1,
324
+ /**火枪手 */
325
+ qs = 2,
326
+ /**飞机 */
327
+ fei_ji = 3,
328
+ /**圣骑士 */
329
+ sqs = 4,
330
+ /**山丘之王 */
331
+ sqzw = 5,
332
+ /**牧师 */
333
+ mu_shi = 6,
334
+ /**光明女巫 */
335
+ nw = 7,
336
+ /**天空骑士 */
337
+ tkqs = 8,
338
+ /**大魔法师 */
339
+ dmfs = 9,
340
+ /**火魔法师 */
341
+ hmfs = 10
342
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "ver": "4.0.24",
3
+ "importer": "typescript",
4
+ "imported": true,
5
+ "uuid": "149ef51b-697e-469e-8ba6-1feb40e9e2b6",
6
+ "files": [],
7
+ "subMetas": {},
8
+ "userData": {}
9
+ }
@@ -0,0 +1,314 @@
1
+ /**
2
+ * 网格数据层(纯逻辑,不依赖任何 cc 视图,可独立单元测试)
3
+ *
4
+ * 职责:
5
+ * 1. 边界 / 占用管理
6
+ * 2. 方向规则(八方向 / 六方向蜂窝,蜂窝按行奇偶取对角)
7
+ * 3. 距离计算(蜂窝步数 / 切比雪夫 / 曼哈顿)
8
+ * 4. 范围查询(目标周围可站点等)
9
+ * 5. 包围判断(四方向原逻辑 / 蜂窝逻辑)
10
+ */
11
+
12
+ /** 格子坐标 */
13
+ export type Cell = { row: number; col: number };
14
+
15
+ /** 格子的唯一 key:"row,col" */
16
+ export function cellKey(row: number, col: number): string {
17
+ return `${row},${col}`;
18
+ }
19
+
20
+ export class GridMap {
21
+
22
+ readonly rowCount: number;
23
+ readonly colCount: number;
24
+ /** 六方向模式:按蜂窝规则(左上/左/左下/右上/右/右下)移动、寻路、判包围 */
25
+ readonly useSixDirection: boolean;
26
+
27
+ /** 被占用格子的 key 集合 */
28
+ private readonly occupied: Set<string> = new Set();
29
+
30
+ constructor(rowCount: number, colCount: number, useSixDirection: boolean) {
31
+ this.rowCount = rowCount;
32
+ this.colCount = colCount;
33
+ this.useSixDirection = useSixDirection;
34
+ }
35
+
36
+ // ==================== 1. 边界 / 占用 ====================
37
+
38
+ /** 检查格子是否在地图范围内 */
39
+ isWithinBounds(row: number, col: number): boolean {
40
+ return row >= 0 && row < this.rowCount && col >= 0 && col < this.colCount;
41
+ }
42
+
43
+ isOccupied(row: number, col: number): boolean {
44
+ return this.occupied.has(cellKey(row, col));
45
+ }
46
+
47
+ occupy(row: number, col: number): void {
48
+ this.occupied.add(cellKey(row, col));
49
+ }
50
+
51
+ free(row: number, col: number): void {
52
+ this.occupied.delete(cellKey(row, col));
53
+ }
54
+
55
+ freeAll(): void {
56
+ this.occupied.clear();
57
+ }
58
+
59
+ /** 被占用格子的 key 集合(兼容旧接口 GetOccupyNodeNot) */
60
+ getOccupiedSet(): Set<string> {
61
+ return this.occupied;
62
+ }
63
+
64
+ // ==================== 2. 方向 / 距离 ====================
65
+
66
+ /**
67
+ * 六方向(蜂窝)模式下两格之间的步数距离
68
+ * 奇数行右移半格的交错布局(odd-r):offset 转 cube 坐标
69
+ */
70
+ getHexDistance(row1: number, col1: number, row2: number, col2: number): number {
71
+ const x1 = col1 - (row1 - (row1 & 1)) / 2;
72
+ const z1 = row1;
73
+ const y1 = -x1 - z1;
74
+ const x2 = col2 - (row2 - (row2 & 1)) / 2;
75
+ const z2 = row2;
76
+ const y2 = -x2 - z2;
77
+ return Math.max(Math.abs(x1 - x2), Math.abs(y1 - y2), Math.abs(z1 - z2));
78
+ }
79
+
80
+ /** 切比雪夫距离(原逻辑的 getManhattanDistance,八方向攻击距离判定用) */
81
+ getChebyshevDistance(a: Cell, b: Cell): number {
82
+ const row = Math.abs(a.row - b.row);
83
+ const col = Math.abs(a.col - b.col);
84
+ return row > col ? row : col;
85
+ }
86
+
87
+ /** 曼哈顿距离(攻击位排序用) */
88
+ getManhattanDistance(a: Cell, b: Cell): number {
89
+ return Math.abs(a.row - b.row) + Math.abs(a.col - b.col);
90
+ }
91
+
92
+ /** 当前移动模式下不会高估的“步数距离”下界(A* 启发函数用,保证最优路径) */
93
+ getStepDistance(a: Cell, b: Cell): number {
94
+ if (this.useSixDirection) {
95
+ return this.getHexDistance(a.row, a.col, b.row, b.col);
96
+ }
97
+ return this.getChebyshevDistance(a, b);
98
+ }
99
+
100
+ /**
101
+ * 获取从 row 行出发的移动方向偏移
102
+ * 六方向模式:左上、左、左下、右上、右、右下(对角按行奇偶取)
103
+ * 八方向模式:上下左右 + 四个对角
104
+ */
105
+ getDirections(row: number): [number, number][] {
106
+ if (this.useSixDirection) {
107
+ const isEvenRow = row % 2 === 0;
108
+ return [
109
+ isEvenRow ? [-1, -1] : [-1, 0], // 左上
110
+ [0, -1], // 左
111
+ isEvenRow ? [1, -1] : [1, 0], // 左下
112
+ isEvenRow ? [-1, 0] : [-1, 1], // 右上
113
+ [0, 1], // 右
114
+ isEvenRow ? [1, 0] : [1, 1], // 右下
115
+ ];
116
+ }
117
+ return [
118
+ [-1, 0], // 上
119
+ [1, 0], // 下
120
+ [0, -1], // 左
121
+ [0, 1], // 右
122
+ [-1, -1], // 左上
123
+ [-1, 1], // 右上
124
+ [1, -1], // 左下
125
+ [1, 1], // 右下
126
+ ];
127
+ }
128
+
129
+ /** 判断两个格子是否相邻(按当前模式的移动规则) */
130
+ isNeighborCell(a: Cell, b: Cell): boolean {
131
+ return this.getDirections(a.row).some(([dr, dc]) =>
132
+ a.row + dr === b.row && a.col + dc === b.col
133
+ );
134
+ }
135
+
136
+ // ==================== 3. 范围查询 ====================
137
+
138
+ /**
139
+ * 查找目标周围 maxDistance 范围内、在地图内且未被占用的点。
140
+ *
141
+ * 注意:这里不能用 isClosedLoop 做提前返回——目标的“被包围”语义包含
142
+ * “目标自身被占用”,而那正是需要找攻击位的场景(终点被占用/敌人站在终点上)。
143
+ * 只按 边界 + 占用 过滤即可,范围内没有可站点时自然返回空数组,
144
+ * 由上层 findPathToNearby 继续扩大半径。
145
+ */
146
+ findAvailablePointsInRange(targetRow: number, targetCol: number, maxDistance: number): Cell[] {
147
+ const availablePoints: Cell[] = [];
148
+ const visited = new Set<string>();
149
+ // 遍历目标点周围 maxDistance 范围内的所有格子
150
+ for (let dRow = -maxDistance; dRow <= maxDistance; dRow++) {
151
+ for (let dCol = -maxDistance; dCol <= maxDistance; dCol++) {
152
+ const newRow = targetRow + dRow;
153
+ const newCol = targetCol + dCol;
154
+ // 跳过目标点本身
155
+ if (newRow === targetRow && newCol === targetCol) {
156
+ continue;
157
+ }
158
+ // 六方向模式:只保留蜂窝距离 ≤ maxDistance 的格子(去掉正方形的四个角)
159
+ if (this.useSixDirection
160
+ && this.getHexDistance(targetRow, targetCol, newRow, newCol) > maxDistance) {
161
+ continue;
162
+ }
163
+ if (this.isWithinBounds(newRow, newCol) && !this.isOccupied(newRow, newCol)) {
164
+ const key = cellKey(newRow, newCol);
165
+ if (!visited.has(key)) {
166
+ visited.add(key);
167
+ availablePoints.push({ row: newRow, col: newCol });
168
+ }
169
+ }
170
+ }
171
+ }
172
+ return availablePoints;
173
+ }
174
+
175
+ /** 目标周围 maxDistance 范围内的所有点(不判断占用,仅边界内) */
176
+ checkAvailableSurrounding(targetRow: number, targetCol: number, maxDistance: number): Cell[] {
177
+ const points: Cell[] = [];
178
+ for (let dRow = -maxDistance; dRow <= maxDistance; dRow++) {
179
+ for (let dCol = -maxDistance; dCol <= maxDistance; dCol++) {
180
+ const newRow = targetRow + dRow;
181
+ const newCol = targetCol + dCol;
182
+ // 跳过目标点本身
183
+ if (newRow === targetRow && newCol === targetCol) {
184
+ continue;
185
+ }
186
+ if (this.isWithinBounds(newRow, newCol)) {
187
+ points.push({ row: newRow, col: newCol });
188
+ }
189
+ }
190
+ }
191
+ return points;
192
+ }
193
+
194
+ // ==================== 4. 包围判断 ====================
195
+
196
+ /** 判断目标点是否被包围 */
197
+ isClosedLoop(targetRow: number, targetCol: number, maxDistance: number): boolean {
198
+ if (this.useSixDirection) {
199
+ return this.isClosedLoopSix(targetRow, targetCol, maxDistance);
200
+ }
201
+ return this.isClosedLoopFour(targetRow, targetCol, maxDistance);
202
+ }
203
+
204
+ /** 原有四方向逻辑:四个面全部被堵住才算被包围 */
205
+ private isClosedLoopFour(targetRow: number, targetCol: number, maxDistance: number): boolean {
206
+ const value = maxDistance;
207
+
208
+ const shang = [
209
+ { row: -value, col: -value }, // 左上
210
+ { row: -value, col: 0 }, // 上
211
+ { row: -value, col: value }, // 右上
212
+ ];
213
+ const zuo = [
214
+ { row: -value, col: -value }, // 左上
215
+ { row: 0, col: -value }, // 左
216
+ { row: value, col: -value }, // 左下
217
+ ];
218
+ const xia = [
219
+ { row: value, col: -value }, // 左下
220
+ { row: value, col: 0 }, // 下
221
+ { row: value, col: value }, // 右下
222
+ ];
223
+ const you = [
224
+ { row: -value, col: value }, // 右上
225
+ { row: 0, col: value }, // 右
226
+ { row: value, col: value }, // 右下
227
+ ];
228
+
229
+ let is_shang = false;
230
+ let is_zuo = false;
231
+ let is_xia = false;
232
+ let is_you = false;
233
+
234
+ let shang_model = this.isMan(targetRow, targetCol, shang, 0, 0);
235
+ is_shang = shang_model.is_man
236
+ if (!shang_model.is_man) {
237
+ shang_model = this.isMan(targetRow, targetCol, shang, -1, 0);
238
+ is_shang = shang_model.is_man_bounds
239
+ }
240
+
241
+ let zuo_model = this.isMan(targetRow, targetCol, zuo, 0, 0);
242
+ is_zuo = zuo_model.is_man
243
+ if (!zuo_model.is_man) {
244
+ zuo_model = this.isMan(targetRow, targetCol, zuo, 0, -1);
245
+ is_zuo = zuo_model.is_man_bounds
246
+ }
247
+
248
+ let xia_model = this.isMan(targetRow, targetCol, xia, 0, 0);
249
+ is_xia = xia_model.is_man
250
+ if (!xia_model.is_man) {
251
+ xia_model = this.isMan(targetRow, targetCol, xia, 1, 0);
252
+ is_xia = xia_model.is_man_bounds
253
+ }
254
+
255
+ let you_model = this.isMan(targetRow, targetCol, you, 0, 0);
256
+ is_you = you_model.is_man
257
+ if (!you_model.is_man) {
258
+ you_model = this.isMan(targetRow, targetCol, you, 0, 1);
259
+ is_you = you_model.is_man_bounds
260
+ }
261
+
262
+ // 四个面全部被堵住才算被包围
263
+ return is_shang && is_zuo && is_xia && is_you;
264
+ }
265
+
266
+ /**
267
+ * 六方向(蜂窝)模式的包围判断:
268
+ * 目标点周围蜂窝距离 maxDistance 以内(不含自身)的所有格子,
269
+ * 只要有一个既在地图内又没被占用,就没被包围;
270
+ * 全部被堵住(出界或被占用都算堵住)才算被包围。
271
+ */
272
+ private isClosedLoopSix(targetRow: number, targetCol: number, maxDistance: number): boolean {
273
+ // 目标点本身出界 / 被占用,也算“被围住”
274
+ if (!this.isWithinBounds(targetRow, targetCol) || this.isOccupied(targetRow, targetCol)) {
275
+ return true;
276
+ }
277
+ // 蜂窝范围一定落在这个正方形包围盒里,遍历后按蜂窝距离过滤即可
278
+ for (let row = targetRow - maxDistance; row <= targetRow + maxDistance; row++) {
279
+ for (let col = targetCol - maxDistance; col <= targetCol + maxDistance; col++) {
280
+ if (row === targetRow && col === targetCol) {
281
+ continue;
282
+ }
283
+ if (this.getHexDistance(targetRow, targetCol, row, col) > maxDistance) {
284
+ continue;
285
+ }
286
+ // 出界或被占用都算“堵住”;发现一个能站的空格就没被包围
287
+ if (this.isWithinBounds(row, col) && !this.isOccupied(row, col)) {
288
+ return false;
289
+ }
290
+ }
291
+ }
292
+ return true;
293
+ }
294
+
295
+ /** 检查一组格子是否全部被占据(is_man)或全部出界(is_man_bounds),四方向包围判断用 */
296
+ private isMan(targetRow: number, targetCol: number, list: { row: number, col: number }[], row: number, col: number) {
297
+ let count = 0
298
+ let count_bounds = 0
299
+
300
+ for (const dir of list) {
301
+ const newRow = targetRow + dir.row + row;
302
+ const newCol = targetCol + dir.col + col;
303
+ // 出界算被占据
304
+ if (!this.isWithinBounds(newRow, newCol)) {
305
+ count++
306
+ count_bounds++
307
+ }
308
+ if (this.isOccupied(newRow, newCol)) {
309
+ count++
310
+ }
311
+ }
312
+ return { is_man_bounds: count_bounds === list.length, is_man: count === list.length }
313
+ }
314
+ }
@@ -0,0 +1,9 @@
1
+ {
2
+ "ver": "4.0.24",
3
+ "importer": "typescript",
4
+ "imported": true,
5
+ "uuid": "5d3a8f21-7c4e-4b6a-9f2d-8e1c0a4b7d92",
6
+ "files": [],
7
+ "subMetas": {},
8
+ "userData": {}
9
+ }