/** * eight_ball_pool.aigameplay — reducer 接口模板 * * Agent 生成实际 reducer 时,必须导出以下函数。 * 此文件是接口约束模板,不是可执行代码。 * * ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ * ⚠️ 重要:渲染层(GameScene)不应直接调用 dispatch() * * dispatch() 是一体化接口,内部跑完整个物理模拟后只返回最终状态, * 中间每帧球的位置、碰撞事件、进袋事件全部丢失。 * * 渲染层需要逐帧更新球的精灵位置,因此必须使用下方的「分步函数」, * 每帧调用 applyPhysicsStep + checkPockets,渲染层根据状态更新精灵。 * * dispatch() 的用途:无头测试、AI 模拟、快速跳过动画等场景。 * ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ */ // ─── 配置类型 ───────────────────────────────────────────────────────────────── export type EightBallConfig = { /** 台球桌宽度(像素),默认 1000 */ tableWidth: number; /** 台球桌高度(像素),默认 500 */ tableHeight: number; /** 球的半径(像素),默认 14 */ ballRadius: number; /** 球袋半径(像素),默认 28 */ pocketRadius: number; /** 最大击球速度(像素/秒) */ maxShotSpeed: number; /** 摩擦系数(每帧速度乘以此值,约 0.985) */ frictionFactor: number; /** 球停止阈值(速度小于此值视为停止,像素/秒) */ stopThreshold: number; /** 物理模拟每帧步数 */ physicsStepsPerFrame: number; }; // ─── 核心类型 ───────────────────────────────────────────────────────────────── export type BallType = 'cue' | 'solid' | 'stripe' | 'eight'; export type BallState = 'on_table' | 'pocketed'; export type PlayerGroup = 'solid' | 'stripe' | 'unassigned'; export type GamePhase = 'assign' | 'clear_own' | 'sink_eight'; export type LoseReason = 'eight_ball_pocketed_early' | 'scratch_on_eight_ball' | null; export type Vector2 = { x: number; y: number }; export type Ball = { id: number; // 0=白球,1-7=实心,8=黑球,9-15=条纹 type: BallType; position: Vector2; velocity: Vector2; state: BallState; }; export type Pocket = { id: string; position: Vector2; }; // ─── 动作类型 ───────────────────────────────────────────────────────────────── export type EightBallAction = | { type: 'aim'; angle: number; power: number } // 设置瞄准参数 | { type: 'shoot' } // 确认击球 | { type: 'simulate_until_stop' } // (测试用)模拟直到所有球停止 | { type: 'simulate_step'; dt: number } // 模拟单帧 | { type: 'reset' }; // ─── 状态类型 ───────────────────────────────────────────────────────────────── export type EightBallState = { balls: Ball[]; pockets: Pocket[]; aim: { angle: number; power: number }; playerGroup: PlayerGroup; pocketedOwn: number; // 已入袋的己方球数量 phase: GamePhase; ballsMoving: boolean; shotInProgress: boolean; gameOver: boolean; win: boolean; loseReason: LoseReason; score: number; /** 上一次物理步骤产生的进袋事件 */ lastPocketEvents: PocketEvent[]; /** 上一次物理步骤产生的碰撞事件 */ lastCollisionEvents: CollisionEvent[]; }; // ─── 分步操作返回类型 ───────────────────────────────────────────────────────── /** 进袋事件 */ export type PocketEvent = { ballId: number; pocketId: string; ballType: BallType; isOwnGroup: boolean; isFoul: boolean; // scratch(白球入袋)或提前入8号球 }; /** 球碰撞事件(渲染层可据此播放碰撞音效) */ export type CollisionEvent = { ball1Id: number; ball2Id: number; impactSpeed: number; // 碰撞速度大小,用于决定音效强度 }; // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 必须导出的函数 // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // ─── 初始化 ────────────────────────────────────────────────────────────────── /** * init:根据配置初始化游戏状态,摆放标准开球阵型(三角形排列) */ export declare function init(config: EightBallConfig): EightBallState; // ─── 一体化接口(测试/AI 模拟用) ─────────────────────────────────────────── /** * dispatch:接收动作,返回新状态(纯函数,不得修改原 state) * - aim:设置瞄准参数 * - shoot:执行击球,模拟到所有球停止,返回最终状态 * - simulate_step:单帧物理模拟 * - simulate_until_stop:模拟直到所有球停止 * - reset:重置 * * ⚠️ 仅用于无头测试和 AI 模拟。渲染层请使用下方分步函数。 */ export declare function dispatch(state: EightBallState, action: EightBallAction): EightBallState; /** * getState:返回当前状态快照 */ export declare function getState(state: EightBallState): EightBallState; // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ // 分步函数(渲染层/场景层必须使用这些函数逐帧驱动) // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ /** * aimCueBall:设置瞄准参数(角度和力度),不触发击球 * 渲染层在玩家拖动瞄准线时调用,用于预览击球方向 */ export declare function aimCueBall( state: EightBallState, angle: number, // 0~360 度 power: number, // 0~100 ): EightBallState; /** * shootCueBall:根据当前 aim 参数给白球施加初始速度 * 渲染层在玩家确认击球时调用,之后进入物理帧循环 */ export declare function shootCueBall(state: EightBallState): EightBallState; /** * applyPhysicsStep:执行单帧物理模拟 * - 更新所有球的位置(position += velocity × dt) * - 应用摩擦力(velocity *= frictionFactor) * - 处理边界反弹(velocity 分量取反) * - 处理球与球碰撞(弹性碰撞,交换速度分量) * - 返回包含碰撞事件的新状态 * * 渲染层每帧调用此函数,根据 state.balls 更新精灵位置 */ export declare function applyPhysicsStep( state: EightBallState, dt: number, // 帧时间(秒),典型值 1/60 ): EightBallState; /** * checkPockets:检测所有球是否进入球袋 * - 球心距任意球袋中心 <= pocketRadius → 进袋 * - 更新 ball.state = 'pocketed' * - 白球进袋:scratch 处理 * - 8号球进袋:根据 phase 判断胜负 * - 返回含进袋事件的新状态 * * 渲染层在每次 applyPhysicsStep 后调用,检测进袋并播放进袋动画 */ export declare function checkPockets(state: EightBallState): EightBallState; /** * determineTurn:所有球停止后,判断回合结果并更新 turn_state * - 处理球组分配(首次进袋时) * - 检测胜利/失败条件 * - 更新 phase(assign → clear_own → sink_eight) * - 返回更新后的状态(含 gameOver/win 标记) * * 渲染层在检测到 ballsMoving 由 true 变为 false 时调用 */ export declare function determineTurn(state: EightBallState): EightBallState;