- 玩法一 数字一笔:规则库、求解器、26 关、竖屏触摸 Web 原型、测试 - 玩法二 连连气球:连消畅玩版核心、Web 原型、Cocos 球槽组件与导出脚本 - 玩法三 六边一笔:规则库、求解器、10 关 + 转墙实验包、Web 原型、测试 - 产品文档、UI 设计规范、各玩法 GDD / HANDOFF - 文档移除全部外部参考作引用;删除玩法二 v0 旧稿
47 lines
2.4 KiB
TypeScript
47 lines
2.4 KiB
TypeScript
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 };
|
|
}
|