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.
AdLike2048/assets/mx/module/merge/compose/ComposeMatcher.ts

103 lines
3.6 KiB

3 weeks ago
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;
}
}