import type { IComposeConfigProvider } from '../config/IComposeConfigProvider'; import type { ComposedUnit } from '../types/MergeTypes'; import { createMembershipSet, membershipAdd, type MembershipSet } from '../../../tools/MembershipSet'; export interface GridCoord { gridIndex: number; row: number; col: number; } /** * 扫描地图字块布局,按 Hero.text 横向正向匹配组合。 * 方向:仅左→右(不支持竖向)。 */ export class ComposeMatcher { constructor(private readonly _config: IComposeConfigProvider) {} findCompositions( layout: Map, coordOf: (gridIndex: number) => GridCoord | null, defaultLevel = 1, ): ComposedUnit[] { const results: ComposedUnit[] = []; const used = createMembershipSet(); // 勿用 [...map.entries()]:部分 H5 构建会编译为 [].concat(map.entries()),迭代结果异常 const entries: { gridIndex: number; weapon2Id: number; row: number; col: number }[] = []; layout.forEach((weapon2Id, gridIndex) => { const c = coordOf(gridIndex); if (c) entries.push({ gridIndex, weapon2Id, row: c.row, col: c.col }); }); if (entries.length === 0) { return results; } const byRow = new Map(); for (const e of entries) { if (!byRow.has(e.row)) byRow.set(e.row, []); byRow.get(e.row)!.push(e); } byRow.forEach((rowCells) => { rowCells.sort((a, b) => a.col - b.col); this.scanLine(rowCells, 'horizontal', layout, used, results, coordOf, defaultLevel); }); return results; } private scanLine( cells: { gridIndex: number; weapon2Id: number; row: number; col: number }[], direction: 'horizontal' | 'vertical', layout: Map, used: MembershipSet, results: ComposedUnit[], coordOf: (gridIndex: number) => GridCoord | null, defaultLevel: number, ): void { for (let i = 0; i < cells.length; i++) { if (used.has(cells[i].gridIndex)) continue; for (let len = cells.length - i; len >= 1; len--) { const slice = cells.slice(i, i + len); if (!this.isContiguous(slice, direction)) continue; const sequence = slice.map((c) => c.weapon2Id); const weapon1Id = this._config.getWeapon1ByGroupSequence(sequence); if (weapon1Id == null) continue; const indices = slice.map((c) => c.gridIndex); if (indices.some((idx) => used.has(idx))) continue; for (const idx of indices) membershipAdd(used, idx); results.push({ weapon1Id, level: defaultLevel, gridIndices: indices, direction, }); break; } } } private isContiguous( cells: { row: number; col: number }[], direction: 'horizontal' | 'vertical', ): boolean { if (cells.length <= 1) return true; for (let i = 1; i < cells.length; i++) { if (direction === 'horizontal') { if (cells[i].col !== cells[i - 1].col + 1) return false; if (cells[i].row !== cells[i - 1].row) return false; } else { if (cells[i].row !== cells[i - 1].row + 1) return false; if (cells[i].col !== cells[i - 1].col) return false; } } return true; } }