You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
102 lines
3.6 KiB
102 lines
3.6 KiB
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<number, number>,
|
|
coordOf: (gridIndex: number) => GridCoord | null,
|
|
defaultLevel = 1,
|
|
): ComposedUnit[] {
|
|
const results: ComposedUnit[] = [];
|
|
const used = createMembershipSet<number>();
|
|
|
|
// 勿用 [...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<number, typeof entries>();
|
|
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<number, number>,
|
|
used: MembershipSet<number>,
|
|
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;
|
|
}
|
|
}
|
|
|