export const RAINBOW_COLOR = 100; export const RAINBOW_CAPACITY = 60; // 每次整块消除增加的能量秒数,按消除时的实际格数配置。 export const RAINBOW_BLOCK_ENERGY: { [cells: number]: number } = { 1: 3, 2: 4.5, 3: 6, 4: 7.5, 5: 9, }; // 临时放慢飞行以便测试;飞行结束后才裁切方块,正式时长为 0.3 秒。 export const RAINBOW_CAST_SECONDS = 1; export interface RainbowCell { x: number; y: number; } export interface RainbowCut { cell: RainbowCell; shape: number; offset: RainbowCell; } // 与 Block.initBlocks 的格子坐标一致;只平移匹配,不旋转剩余方块。 export const RAINBOW_SHAPES: RainbowCell[][] = [ [[0, 0]], [[0, 0], [-1, 0]], [[0, 0], [0, 1]], [[0, 0], [-1, 0], [-2, 0]], [[0, 0], [0, 1], [0, 2]], [[0, 0], [-1, 0], [-1, 1], [0, 1]], [[0, 0], [0, 1], [0, 2], [-1, 2]], [[0, 0], [0, 1], [-1, 1], [-2, 1]], [[0, 0], [-1, 0], [-1, 1], [-1, 2]], [[0, 0], [-1, 0], [-2, 0], [0, 1]], [[0, 0], [1, 2], [0, 1], [0, 2]], [[0, 0], [2, 1], [1, 1], [0, 1]], [[0, 0], [0, 1], [0, 2], [-1, 0]], [[0, 0], [-1, 0], [-2, 0], [-2, 1]], [[0, 0], [0, 1], [-1, 1], [1, 1]], [[0, 0], [-1, 0], [-2, 0], [-1, 1]], [[0, 0], [1, 1], [0, 1], [0, 2]], [[0, 0], [0, 1], [0, 2], [-1, 1]], [[0, 0], [0, 1], [0, 2], [1, 1], [-1, 1]], [[0, 0], [0, 1], [-1, 0]], [[0, 0], [-1, 0], [-1, 1]], [[0, 0], [0, 1], [1, 1]], [[0, 0], [0, 1], [-1, 1]], ].map(shape => shape.map(p => ({ x: p[0], y: p[1] }))); function normalized(cells: RainbowCell[]): { key: string; x: number; y: number } { const x = Math.min(...cells.map(p => p.x)); const y = Math.min(...cells.map(p => p.y)); return { x, y, key: cells.map(p => (p.x - x) + "," + (p.y - y)).sort().join(";") }; } export function rainbowCuts(cells: RainbowCell[]): RainbowCut[] { if (!cells || cells.length <= 1) return []; const cuts: RainbowCut[] = []; cells.forEach((cell, index) => { const remaining = cells.filter((_, i) => i !== index); const reached = [remaining[0]]; for (let i = 0; i < reached.length; i++) { remaining.forEach(p => { if (reached.indexOf(p) < 0 && Math.abs(p.x - reached[i].x) + Math.abs(p.y - reached[i].y) === 1) reached.push(p); }); } if (reached.length !== remaining.length) return; const target = normalized(remaining); const shape = RAINBOW_SHAPES.findIndex(points => points.length === remaining.length && normalized(points).key === target.key); if (shape < 0) return; const origin = normalized(RAINBOW_SHAPES[shape]); cuts.push({ cell: { x: cell.x, y: cell.y }, shape, offset: { x: target.x - origin.x, y: target.y - origin.y } }); }); return cuts; } // 形状固定,避免在移动端每一帧重复做连通性与形状匹配。 export const RAINBOW_CUTS = RAINBOW_SHAPES.map(rainbowCuts); /** 复合属性取最低优先级。调用方先排除不可消除、生产中及单格方块。 */ export function rainbowPriority(block: any): number { const info = block.block_Info || {}; let priority = 0; if ([2, 5, 13, 14, 21].indexOf(block.type) >= 0 || block.flowerType === 1) priority = 1; if ([7, 8, 20].indexOf(block.type) >= 0 || info.colorArray != null || info.horizontal || info.vertical || info.colorChange != null) priority = Math.max(priority, 2); if (block.type === 4 || block.type === 16) priority = Math.max(priority, 3); if (block.type === 3 || block.type === 12 || info.lock != null) priority = Math.max(priority, 4); if (block.type === 6 || block.type === 17) priority = Math.max(priority, 5); if (info.floor != null || block.flowerType === 2) priority = Math.max(priority, 6); if (block.type === 1 || block.type === 10) priority = Math.max(priority, 7); if (block.type === 9) priority = Math.max(priority, 8); if (info.floorMove === true && block.teamBlocks && block.teamBlocks.length > 1) priority = 9; return priority; } export function chooseRainbowTarget( candidates: T[], random: () => number = Math.random ): { target: T; cut: RainbowCut } | null { if (!candidates.length) return null; const priority = Math.min(...candidates.map(candidate => candidate.priority)); const pool = candidates.filter(candidate => candidate.priority === priority); const target = pool[Math.floor(random() * pool.length)]; return { target, cut: target.cuts[Math.floor(random() * target.cuts.length)] }; } /** 进关就准备所有可能裁出的形状,施放期间不下载图片。 */ export function rainbowTexturePaths(blocks: any[]): string[] { const paths = new Set(); for (const block of blocks) { if (!block || !RAINBOW_SHAPES[block.block] || block.color === 11 || block.type === 22) continue; const shapes = new Set([block.block]); const pending = [block.block]; while (pending.length) { for (const cut of RAINBOW_CUTS[pending.pop()]) { if (!shapes.has(cut.shape)) { shapes.add(cut.shape); pending.push(cut.shape); } } } const colors = new Set([block.color]); if (block.type === 1) colors.add(block.stacking); if (block.colorArray != null) String(block.colorArray).split("").forEach(c => colors.add(Number(c) + 1)); shapes.forEach(shape => { colors.forEach(color => paths.add((color === 11 ? 0 : color) + "color" + shape)); if (block.type === 16) paths.add("question" + shape); if (block.lock != null) { paths.add("11color" + shape); paths.add("12color" + shape); } }); } return Array.from(paths).sort(); } export class RainbowEnergy { value = RAINBOW_CAPACITY; stopped = false; unavailable = false; setAvailability(available: boolean, pendingProduction: boolean): void { if (this.stopped) return; this.unavailable = !available; if (!available && !pendingProduction) this.stopped = true; } tick(dt: number, counting: boolean): void { if (counting) this.add(dt); } eliminated(cells: number): void { this.add(RAINBOW_BLOCK_ENERGY[cells] || 0); } private add(value: number): void { if (!this.stopped && !this.unavailable) this.value = Math.min(RAINBOW_CAPACITY, this.value + value); } consume(): boolean { if (this.stopped || this.unavailable || this.value < RAINBOW_CAPACITY) return false; this.value = 0; return true; } }