三连胜提交
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
import { RainbowCell, RainbowCut, RAINBOW_SHAPES, rainbowPriority } from "./RainbowRules";
|
||||
|
||||
export interface RocketCut { cell: RainbowCell; fragments: RainbowCut[]; }
|
||||
|
||||
function key(cells: RainbowCell[]): string {
|
||||
const x = Math.min(...cells.map(p => p.x)), y = Math.min(...cells.map(p => p.y));
|
||||
return cells.map(p => (p.x - x) + "," + (p.y - y)).sort().join(";");
|
||||
}
|
||||
|
||||
/** 每次仅删除一格;其余四邻接连通片各自匹配已有形状。 */
|
||||
export function rocketCuts(cells: RainbowCell[], edgeOnly = false): RocketCut[] {
|
||||
return cells.map(cell => {
|
||||
const pending = cells.filter(p => p !== cell).slice();
|
||||
const fragments: RainbowCut[] = [];
|
||||
while (pending.length) {
|
||||
const group = [pending.pop()];
|
||||
for (let i = 0; i < group.length; i++) {
|
||||
for (let j = pending.length - 1; j >= 0; j--) {
|
||||
if (Math.abs(pending[j].x - group[i].x) + Math.abs(pending[j].y - group[i].y) === 1) {
|
||||
group.push(pending.splice(j, 1)[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
const shape = RAINBOW_SHAPES.findIndex(points => points.length === group.length && key(points) === key(group));
|
||||
if (shape < 0) throw new Error("火箭残块缺少对应形状: " + key(group));
|
||||
fragments.push({ cell, shape, offset: {
|
||||
x: Math.min(...group.map(p => p.x)) - Math.min(...RAINBOW_SHAPES[shape].map(p => p.x)),
|
||||
y: Math.min(...group.map(p => p.y)) - Math.min(...RAINBOW_SHAPES[shape].map(p => p.y))
|
||||
} });
|
||||
}
|
||||
return { cell, fragments };
|
||||
}).filter(cut => !edgeOnly || cut.fragments.length === 1);
|
||||
}
|
||||
|
||||
export const ROCKET_CUTS = RAINBOW_SHAPES.map(shape => rocketCuts(shape));
|
||||
|
||||
export function rocketEdgeOnly(block: any, protectedColors: Set<number>): boolean {
|
||||
const info = block.block_Info;
|
||||
return protectedColors.has(block.color) || (block.type !== 0 && block.type !== 16)
|
||||
|| !!block.flowerType || info.lock != null || info.colorArray != null || info.colorChange != null
|
||||
|| !!info.horizontal || !!info.vertical || info.floorMove === true
|
||||
|| (block.teamBlocks || []).length > 1;
|
||||
}
|
||||
|
||||
export function rocketCandidates(map: any): any[] {
|
||||
const blocks = map.blocks.filter(node => cc.isValid(node, true) && node.active)
|
||||
.map(node => node.getComponent("Block")).filter(Boolean);
|
||||
const buttons = new Set<number>((map.buttonBlock || []).map(node => node.getComponent("MapBlock").color));
|
||||
const protectedColors = new Set<number>();
|
||||
(map.wallArray || []).forEach(node => {
|
||||
const wall = node.getChildByName("wall").getComponent("Wall");
|
||||
(wall.colorStartArray || []).forEach(color => protectedColors.add(color));
|
||||
if (wall.special === 2) protectedColors.add(wall.color);
|
||||
});
|
||||
const glued = new Set<cc.Node>();
|
||||
blocks.forEach(block => {
|
||||
if (block.type === 9 || block.type === 19) {
|
||||
glued.add(block.node);
|
||||
if (block.block_Info.node) glued.add(block.block_Info.node);
|
||||
}
|
||||
if ((block.adhesiveNode || []).some(node => cc.isValid(node, true) && node.active
|
||||
&& node.getComponent("Adhesive")?.target)) glued.add(block.node);
|
||||
});
|
||||
return blocks.filter(block => !block.spawnLocked && !block.over && !block.rainbowEliminating
|
||||
&& !block.oldRainbow && block.color !== 100 && block.color !== 11
|
||||
&& [10, 11, 22].indexOf(block.type) < 0 && !!ROCKET_CUTS[block.block_Info.block])
|
||||
.map(block => {
|
||||
const upper = block.type === 1 && block.block_Info.node?.getComponent("Block");
|
||||
const layers = upper ? [block, upper] : [block];
|
||||
if (layers.some(layer => glued.has(layer.node) || buttons.has(layer.color))) return null;
|
||||
const edgeOnly = layers.some(layer => rocketEdgeOnly(layer, protectedColors));
|
||||
const cuts = ROCKET_CUTS[block.block_Info.block].filter(cut => !edgeOnly || cut.fragments.length === 1);
|
||||
return { block, cuts, edgeOnly, origin: block.rocketOrigin || block.node.uuid,
|
||||
priority: block.allBlocks.length === 1 ? 2 : layers.some(layer => rainbowPriority(layer) > 0) ? 1 : 0 };
|
||||
}).filter(candidate => candidate && candidate.cuts.length);
|
||||
}
|
||||
|
||||
export function chooseRocketTarget(candidates: any[], hit: Set<string>, remaining: number,
|
||||
random: () => number = Math.random): any {
|
||||
if (!candidates.length) return null;
|
||||
const priority = Math.min(...candidates.map(candidate => candidate.priority));
|
||||
let pool = candidates.filter(candidate => candidate.priority === priority);
|
||||
const fresh = pool.filter(candidate => !hit.has(candidate.origin));
|
||||
if (fresh.length) pool = fresh;
|
||||
const target = pool[Math.floor(random() * pool.length)];
|
||||
// 1932 等候选不足的开局,重复从边缘裁切,保留主体供后续火箭命中。
|
||||
const edges = target.cuts.filter(cut => cut.fragments.length === 1);
|
||||
const cuts = candidates.length < remaining && edges.length ? edges : target.cuts;
|
||||
return { target, cut: cuts[Math.floor(random() * cuts.length)] };
|
||||
}
|
||||
|
||||
export interface RocketShot { x: number; y: number; origin: string; cut: RocketCut; }
|
||||
|
||||
/** 只在候选快照上模拟逐发裁切;同一原块的实际命中须保持规划顺序。 */
|
||||
export function planRocketShots(map: any, count = 3, random: () => number = Math.random): RocketShot[] {
|
||||
let pool = rocketCandidates(map).map(candidate => ({ ...candidate,
|
||||
x: candidate.block.posX, y: candidate.block.posY }));
|
||||
const shots: RocketShot[] = [], hit = new Set<string>();
|
||||
for (let remaining = count; remaining > 0; remaining--) {
|
||||
const choice = chooseRocketTarget(pool, hit, remaining, random);
|
||||
if (!choice) break;
|
||||
const { target, cut } = choice;
|
||||
shots.push({ x: target.x + cut.cell.x, y: target.y + cut.cell.y, origin: target.origin, cut });
|
||||
hit.add(target.origin);
|
||||
pool = pool.filter(candidate => candidate !== target);
|
||||
for (const fragment of cut.fragments) {
|
||||
const cuts = ROCKET_CUTS[fragment.shape].filter(next => !target.edgeOnly || next.fragments.length === 1);
|
||||
if (cuts.length) pool.push({ ...target, cuts,
|
||||
x: target.x + fragment.offset.x, y: target.y + fragment.offset.y,
|
||||
priority: RAINBOW_SHAPES[fragment.shape].length === 1 ? 2 : target.priority });
|
||||
}
|
||||
}
|
||||
return shots;
|
||||
}
|
||||
Reference in New Issue
Block a user