refactor: 重构抽奖逻辑以支持可验证凭据 feat(redis): 集成Redis客户端并添加配置支持 fix: 修复订单取消时的优惠券和库存处理逻辑 docs: 添加对对碰游戏前端对接指南和示例JSON test: 添加对对碰游戏模拟测试和验证逻辑
123 lines
4.6 KiB
Markdown
123 lines
4.6 KiB
Markdown
# 对对碰(Matching Game)前端对接指南
|
||
|
||
## 1. 核心流程概述
|
||
|
||
本版本采用 **"预计算 + 客户端渲染"** 模式,以确保流畅体验并防止网络延迟影响游戏节奏。
|
||
|
||
### 交互时序
|
||
1. **开始游戏 (API)**: 用户点击开始 -> 调用 `PreOrder` 接口 -> 服务器扣费并返回**完整游戏剧本**。
|
||
2. **动画播放 (Frontend)**: 前端根据返回的 `timeline` 数据,按顺序播放每一轮的 消除、填补、重洗 动画。
|
||
3. **游戏结算 (API)**: 动画播放完毕(或用户点击跳过) -> 调用 `Check` 接口 -> 服务器确认结束并发放奖励。
|
||
|
||
---
|
||
|
||
## 2. 接口调用详解
|
||
|
||
### 2.1 第一步:下单并获取数据 (Start)
|
||
|
||
* **接口**: `POST /api/app/matching/preorder`
|
||
* **参数**: `{ "issue_id": 123 }`
|
||
* **核心响应**:
|
||
```json
|
||
{
|
||
"game_id": "MG_123456", // 保存此ID用于结算
|
||
"initial_board": [ ... ], // 初始9宫格数据 (用于渲染第一帧)
|
||
"timeline": [ ... ], // 关键!动画剧本数组
|
||
"total_pairs": 5 // 预计总消除对数 (可用于显示"目标")
|
||
}
|
||
```
|
||
|
||
### 2.2 第二步:前端渲染循环 (Render)
|
||
|
||
前端拿到 `timeline` 后,不需要再请求服务器,直接在本地执行以下逻辑:
|
||
|
||
```javascript
|
||
// 伪代码示例
|
||
async function playFullGame(data) {
|
||
// 1. 渲染初始棋盘
|
||
renderBoard(data.initial_board);
|
||
|
||
// 2. 遍历时间轴,逐回合播放
|
||
for (const round of data.timeline) {
|
||
|
||
// --- 阶段 A: 消除动画 ---
|
||
if (round.pairs.length > 0) {
|
||
// 高亮要消除的卡牌
|
||
highlightCards(round.pairs);
|
||
await wait(500); // 停顿
|
||
|
||
// 播放消除特效(根据 slot_indices 找到对应格子)
|
||
for (const pair of round.pairs) {
|
||
// pair.slot_indices 数组包含了所有要消除的格子下标 (0-8)
|
||
playEliminateEffect(pair.slot_indices);
|
||
}
|
||
await wait(500); // 等待特效结束
|
||
|
||
// 从界面移除卡牌 DOM
|
||
removeCardsFromBoard(round.pairs);
|
||
}
|
||
|
||
// --- 阶段 B: 填补动画 ---
|
||
if (round.drawn_cards.length > 0) {
|
||
// 播放发牌/飞入动画
|
||
for (const draw of round.drawn_cards) {
|
||
// draw.slot_index 是目标格子
|
||
// draw.card 是新卡牌数据
|
||
flyCardToSlot(draw.card, draw.slot_index);
|
||
}
|
||
await wait(500);
|
||
}
|
||
|
||
// --- 阶段 C: 死局重洗 (如果有) ---
|
||
if (round.reshuffled) {
|
||
showToast("死局重洗中...");
|
||
playShuffleAnimation(); // 播放所有卡牌重新排列的动画
|
||
// 动画结束后,根据 round.board 更新整个棋盘状态,确保位置正确
|
||
updateFullBoard(round.board);
|
||
await wait(1000);
|
||
}
|
||
|
||
// 每一轮结束后稍作停顿
|
||
await wait(300);
|
||
}
|
||
|
||
// 3. 全部播放完毕,调用结算
|
||
doCheck(data.game_id);
|
||
}
|
||
```
|
||
|
||
### 2.3 第三步:结算与领奖 (Check)
|
||
|
||
* **接口**: `POST /api/app/matching/check`
|
||
* **参数**: `{ "game_id": "MG_123456" }`
|
||
* **作用**: 告知服务器动画已播完,触发最终奖励发放(虽然服务器在PreOrder时已经计算好了,但这个调用作为完整的业务闭环)。
|
||
|
||
---
|
||
|
||
## 3. 数据结构字典
|
||
|
||
### `MatchingRoundResult` (时间轴中的每一项)
|
||
|
||
| 字段 | 类型 | 说明 | 前端处理建议 |
|
||
| :--- | :--- | :--- | :--- |
|
||
| `round` | int | 回合数 | 用于UI显示"第几轮" |
|
||
| `pairs` | array | 消除列表 | **关键**:遍历此数组,读取 `slot_indices` 来决定哪些格子要播消除动画 |
|
||
| `drawn_cards` | array | 填补列表 | **关键**:遍历此数组,读取 `slot_index` 来决定新卡牌飞入哪个格子 |
|
||
| `reshuffled` | bool | 是否重洗 | 若为 true,必须播放全屏洗牌特效,并强制刷新棋盘数据 |
|
||
| `can_continue` | bool | 是否继续 | 若为 false,播放完本轮后显示"游戏结束"弹窗 |
|
||
|
||
### `MatchingPair` (消除详情)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| :--- | :--- | :--- |
|
||
| `card_type` | string | 消除的卡牌类型 |
|
||
| `count` | int | 消除数量 (通常是2,连消可能是4,6) |
|
||
| `slot_indices` | int[] | **核心**:被消除的格子下标列表,如 `[0, 1]` |
|
||
|
||
### `DrawnCardInfo` (填补详情)
|
||
|
||
| 字段 | 类型 | 说明 |
|
||
| :--- | :--- | :--- |
|
||
| `slot_index` | int | **核心**:填入的目标格子下标 (0-8) |
|
||
| `card` | object | 新卡牌的完整数据 (id, image_url, name) |
|