156 lines
6.6 KiB
TypeScript
156 lines
6.6 KiB
TypeScript
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<T extends { priority: number; cuts: RainbowCut[] }>(
|
|
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<string>();
|
|
for (const block of blocks) {
|
|
if (!block || !RAINBOW_SHAPES[block.block] || block.color === 11 || block.type === 22) continue;
|
|
const shapes = new Set<number>([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<number>([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;
|
|
}
|
|
}
|