import { adjacent, points, resultNumber } from './rules'; import { legalFlow } from './flow-rules'; import type { Ball } from './types'; export const balloonRadius = (n: number): number => 24 + Math.min(9, n - 1) * 3.2; export const copyBalls = (balls: Ball[]): Ball[] => balls.map(b => ({ ...b })); export interface Reaction { before: Ball[]; path: Ball[]; created: Ball; points: number; index: number } export interface Cascade { stages: Reaction[]; balls: Ball[]; score: number; popped: number; flight: Ball | null } /** 自动接上 n−2 → n−1 → 新球;新球一直留在玩家选择的链尾。 */ export function continuation(balls: Ball[], head: Ball): number[] | null { const seconds = balls.filter(b => b.n === head.n - 1 && adjacent(b, head)) .sort((a, b) => Math.hypot(a.x - head.x, a.y - head.y) - Math.hypot(b.x - head.x, b.y - head.y)); for (const second of seconds) { const first = balls.find(b => b.n === head.n - 2 && adjacent(b, second)); if (first) return [first.id, second.id, head.id]; } const same = balls.filter(b => b.id !== head.id && b.n === head.n); for (const second of same.filter(b => adjacent(b, head))) { const first = same.find(b => b.id !== second.id && adjacent(b, second)); if (first) return [first.id, second.id, head.id]; } return null; } export function cascade(source: Ball[], ids: number[], fever = false, offset = 0): Cascade | null { let balls = copyBalls(source), next: number[] | null = ids; const initial = ids.map(id => balls.find(b => b.id === id)); if (initial.some(b => !b) || !legalFlow(initial as Ball[])) return null; const stages: Reaction[] = []; let score = 0, popped = 0, flight: Ball | null = null; while (next && stages.length + offset < (fever ? 4 : 3)) { const path = next.map(id => balls.find(b => b.id === id)!); const tail = path[path.length - 1], n = resultNumber(path), index = stages.length + offset + 1; const created = { ...tail, n, r: balloonRadius(n), vx: 0, vy: 0 }; const gain = points(path) * index * (fever ? 2 : 1) + (n >= 10 ? 10000 : 0); stages.push({ before: copyBalls(balls), path: copyBalls(path), created: { ...created }, points: gain, index }); score += gain; popped += path.length; const removed = new Set(next); balls = balls.filter(b => !removed.has(b.id)); if (n >= 10) { flight = created; break; } balls.push(created); next = continuation(balls, created); } return { stages, balls, score, popped, flight }; }