# 炸弹传递小游戏 - 产品需求文档 (PRD) ## 1. 产品概述 ### 1.1 产品定位 一款基于微信小程序的多人实时互动小游戏。玩家通过邀请好友进入房间,在倒计时内通过摇晃手机传递"炸弹",炸弹爆炸时持有者即为输家。玩法简单刺激,适合朋友间聚会娱乐。 ### 1.2 核心价值 - **社交互动**:通过微信邀请好友,增强社交黏性 - **紧张刺激**:倒计时 + 随机爆炸机制带来紧张感 - **操作简单**:摇一摇即可参与,零学习成本 - **即时反馈**:所有人实时看到炸弹传递与爆炸结果 ### 1.3 目标用户 - 微信用户群体(朋友聚会、线上社交) - 喜欢轻量级派对游戏的玩家 - 2~8 人小团体 --- ## 2. 游戏规则 ### 2.1 基本规则 | 项目 | 说明 | |------|------| | 玩家人数 | 2 ~ 8 人 | | 游戏时长 | 每轮倒计时 **30 秒**(可配置) | | 传递方式 | 持有炸弹的玩家 **摇晃手机达到指定次数** 后,炸弹传递给下一位玩家 | | 摇晃次数 | 每次传递随机生成 **3 ~ 8 次**(可配置),玩家需完成对应摇晃次数 | | 爆炸机制 | 倒计时结束时,持有炸弹的玩家被炸中 | | 传递顺序 | 按房间内玩家座位顺序(顺时针)传递 | | 胜负判定 | 炸弹爆炸时持有者为本轮输家,其余玩家获胜 | ### 2.2 特殊规则 | 规则 | 说明 | |------|------| | 掉线处理 | 玩家掉线 **10 秒** 内未重连,视为自动弃权,炸弹跳过该玩家传给下一位 | | 最少人数 | 房间内至少 **2 人** 才能开始游戏 | | 房主特权 | 房主可以 **开始游戏**、**踢出玩家**、**调整设置** | | 多轮机制 | 一轮结束后,房主可发起下一轮,输家作为下一轮炸弹初始持有者 | ### 2.3 计分规则(可选扩展) | 事件 | 积分 | |------|------| | 存活(未被炸中) | +10 分 | | 被炸中 | +0 分 | | 连续存活 3 轮 | 额外 +5 分 | | 多轮游戏结束后积分最高者为总冠军 | - | --- ## 3. 游戏流程 ### 3.1 房间阶段 ``` 玩家 A 打开小程序 ↓ 创建房间 → 获得房间号 / 邀请链接 ↓ 分享邀请链接给微信好友 ↓ 好友点击链接 → 进入房间等候区 ↓ 等候区实时显示:已加入玩家列表、头像、准备状态 ↓ 房主点击「开始游戏」(需 ≥ 2 人) ↓ 进入游戏阶段 ``` ### 3.2 游戏阶段 ``` 服务端初始化本轮游戏: ├── 随机选定炸弹初始持有者(首轮随机,后续轮为上轮输家) ├── 生成本轮倒计时时长(默认 30s) ├── 生成首次传递需要的摇晃次数(3~8 次) └── 广播「游戏开始」消息给所有玩家 ↓ 所有玩家屏幕显示: ├── 倒计时(全局同步) ├── 玩家列表(圆形排列) ├── 炸弹当前位置(高亮 + 动画) └── 持有者显示:需要摇晃的次数 ↓ [炸弹持有者操作] ├── 摇晃手机 → 客户端检测摇晃 → 上报摇晃事件 ├── 服务端计数 → 达到目标次数 ├── 服务端判定传递 → 炸弹转移给下一位 ├── 广播「炸弹传递」消息(包含新持有者、新摇晃目标次数) └── 循环直到倒计时结束 ↓ [倒计时归零] ├── 服务端判定当前持有者为输家 ├── 广播「炸弹爆炸」消息(包含输家信息) ├── 所有人屏幕播放爆炸动画 + 显示 "XXX 被炸飞了!" └── 3 秒后进入结算界面 ↓ [结算界面] ├── 显示本轮结果(输家、存活者) ├── 显示累计积分排行(如开启计分) ├── 房主可点击「再来一轮」 └── 玩家可点击「退出房间」 ``` ### 3.3 异常流程 ``` [玩家掉线] ├── 服务端检测 WebSocket 断开 ├── 标记玩家为「掉线」状态 ├── 等待 10 秒重连 ├── 重连成功 → 恢复游戏状态 └── 重连失败 → 踢出房间,炸弹跳过该玩家 [房主掉线] ├── 等待 10 秒重连 ├── 重连失败 → 自动转让房主给下一位玩家 └── 如果房间内只剩 1 人 → 游戏结束,解散房间 [游戏中玩家不足] ├── 游戏进行中玩家数 < 2 └── 游戏自动结束,剩余玩家获胜 ``` --- ## 4. 页面设计 ### 4.1 首页(游戏大厅) | 元素 | 说明 | |------|------| | 用户头像 + 昵称 | 微信授权信息 | | 「创建房间」按钮 | 创建新游戏房间 | | 「加入房间」按钮 | 输入房间号加入 | | 游戏规则说明 | 简要规则介绍 | | 历史战绩 | 总局数、胜率、最长连胜(扩展) | ### 4.2 房间等候页 | 元素 | 说明 | |------|------| | 房间号 | 6 位数字,可复制 | | 邀请按钮 | 微信分享邀请链接 | | 玩家列表 | 头像 + 昵称 + 准备状态,圆形排列 | | 房间设置 | 倒计时时长、摇晃次数范围(房主可调) | | 开始游戏按钮 | 仅房主可见,≥ 2 人时可点击 | | 退出房间按钮 | 所有人可见 | ### 4.3 游戏进行页 | 元素 | 说明 | |------|------| | 倒计时 | 顶部居中,大字体,< 5 秒时变红闪烁 | | 玩家环形排列 | 头像圆形排列,当前持有者高亮 + 炸弹图标 | | 炸弹动画 | 炸弹在玩家之间传递的动画效果 | | 摇晃提示 | 持有者屏幕显示「摇一摇!还需 X 次」 | | 摇晃进度 | 进度条或数字展示已摇次数 / 目标次数 | | 振动反馈 | 每次有效摇晃触发手机振动 | ### 4.4 爆炸结算页 | 元素 | 说明 | |------|------| | 爆炸动画 | 全屏爆炸特效 | | 输家展示 | 大头像 + "XXX 被炸飞了!" | | 本轮结果 | 所有玩家结果列表(存活 / 被炸) | | 积分排行 | 多轮游戏时的累计积分 | | 「再来一轮」 | 房主按钮 | | 「退出房间」 | 所有人可点 | | 「分享战绩」 | 生成分享卡片 | --- ## 5. 技术架构 ### 5.1 通信方案 游戏需要 **实时双向通信**,采用 **WebSocket** 作为核心通信协议: ``` ┌──────────────┐ WebSocket ┌──────────────────┐ │ 微信小程序 │ ◄──────────────► │ Go 后端服务 │ │ (前端客户端) │ │ (Gin + gorilla) │ └──────────────┘ └────────┬─────────┘ │ ┌────────┴─────────┐ │ Redis │ │ (房间状态/Pub-Sub)│ └────────┬─────────┘ │ ┌────────┴─────────┐ │ MySQL │ │ (用户/战绩持久化) │ └──────────────────┘ ``` **为什么选择 WebSocket:** - 游戏需要服务端主动推送(倒计时同步、炸弹传递、爆炸通知) - HTTP 轮询延迟高、开销大,不适合实时游戏 - 微信小程序原生支持 `wx.connectSocket` WebSocket API ### 5.2 系统架构 ``` ┌─────────────────────────────────────────────────────────────┐ │ Go 后端服务 │ │ │ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │ │ │ REST API │ │ WebSocket │ │ Game Engine │ │ │ │ (Gin) │ │ Hub │ │ (房间/游戏逻辑) │ │ │ │ │ │ │ │ │ │ │ │ - 创建房间 │ │ - 连接管理 │ │ - 房间状态机 │ │ │ │ - 加入房间 │ │ - 消息路由 │ │ - 倒计时管理 │ │ │ │ - 用户信息 │ │ - 心跳检测 │ │ - 摇晃计数 │ │ │ │ - 战绩查询 │ │ - 断线重连 │ │ - 炸弹传递逻辑 │ │ │ └─────────────┘ └─────────────┘ │ - 爆炸判定 │ │ │ └─────────────────────┘ │ │ ┌─────────────────────────────────────────────────────────┐ │ │ │ 数据层 │ │ │ │ Redis: 房间实时状态、玩家会话、Pub/Sub 消息广播 │ │ │ │ MySQL: 用户信息、游戏记录、战绩统计 │ │ │ └─────────────────────────────────────────────────────────┘ │ └─────────────────────────────────────────────────────────────┘ ``` ### 5.3 房间状态机 ``` 创建房间 │ ▼ ┌───────────────┐ │ WAITING │ ←── 玩家加入 / 离开 │ (等待中) │ └───────┬───────┘ │ 房主点击开始(≥ 2 人) ▼ ┌───────────────┐ │ COUNTDOWN │ │ (倒计时准备) │ 3, 2, 1... └───────┬───────┘ │ ▼ ┌───────────────┐ │ PLAYING │ ←── 摇晃 / 传递 / 倒计时 │ (游戏中) │ └───────┬───────┘ │ 倒计时归零 / 玩家不足 ▼ ┌───────────────┐ │ EXPLODED │ │ (已爆炸) │ 展示结果 └───────┬───────┘ │ 3 秒后 ▼ ┌───────────────┐ 房主再来一轮 │ FINISHED │ ──────────────────► WAITING │ (已结束) │ └───────┬───────┘ │ 所有人退出 ▼ ┌───────────────┐ │ DISSOLVED │ │ (已解散) │ └───────────────┘ ``` --- ## 6. 数据模型 ### 6.1 数据库表结构 #### bomb_game_rooms(游戏房间) | 字段 | 类型 | 说明 | |------|------|------| | id | BIGINT, PK | 主键 | | room_code | VARCHAR(6), UNIQUE | 房间号(6 位数字) | | owner_id | BIGINT, FK → users.id | 房主用户 ID | | status | ENUM | 房间状态:waiting / playing / finished / dissolved | | max_players | TINYINT | 最大玩家数,默认 8 | | round_duration | INT | 每轮倒计时秒数,默认 30 | | shake_min | INT | 最少摇晃次数,默认 3 | | shake_max | INT | 最多摇晃次数,默认 8 | | current_round | INT | 当前轮数 | | mini_program_id | BIGINT, FK | 所属小程序 ID | | created_at | DATETIME | 创建时间 | | updated_at | DATETIME | 更新时间 | #### bomb_game_players(房间玩家) | 字段 | 类型 | 说明 | |------|------|------| | id | BIGINT, PK | 主键 | | room_id | BIGINT, FK → bomb_game_rooms.id | 房间 ID | | user_id | BIGINT, FK → users.id | 用户 ID | | seat_index | TINYINT | 座位序号(0 开始,决定传递顺序) | | is_alive | BOOL | 本轮是否存活 | | is_online | BOOL | 是否在线 | | score | INT | 累计积分 | | joined_at | DATETIME | 加入时间 | #### bomb_game_rounds(游戏轮次记录) | 字段 | 类型 | 说明 | |------|------|------| | id | BIGINT, PK | 主键 | | room_id | BIGINT, FK → bomb_game_rooms.id | 房间 ID | | round_number | INT | 第几轮 | | loser_id | BIGINT, FK → users.id | 输家用户 ID | | bomb_start_player_id | BIGINT | 炸弹初始持有者 | | total_passes | INT | 本轮总传递次数 | | duration | INT | 实际持续秒数 | | started_at | DATETIME | 开始时间 | | ended_at | DATETIME | 结束时间 | #### bomb_game_stats(玩家战绩统计) | 字段 | 类型 | 说明 | |------|------|------| | id | BIGINT, PK | 主键 | | user_id | BIGINT, FK → users.id | 用户 ID | | mini_program_id | BIGINT, FK | 所属小程序 ID | | total_games | INT | 总局数 | | total_wins | INT | 获胜局数 | | total_losses | INT | 失败局数 | | win_streak | INT | 当前连胜 | | max_win_streak | INT | 最长连胜 | | total_score | INT | 累计积分 | | updated_at | DATETIME | 最后更新时间 | ### 6.2 Redis 数据结构(实时游戏状态) 游戏进行中的状态存储在 Redis 中,保证实时性能: ``` # 房间实时状态 (Hash) bomb:room:{room_code} = { "status": "playing", "current_holder": "user_id_123", // 当前炸弹持有者 "next_player": "user_id_456", // 下一个接收者 "shake_target": 5, // 当前需要摇晃的次数 "shake_current": 2, // 已摇晃次数 "round": 1, // 当前轮数 "timer_end": 1706000030, // 倒计时结束时间戳 "exploded": false // 是否已爆炸 } # 房间玩家有序列表 (Sorted Set, score = seat_index) bomb:room:{room_code}:players = { "user_id_1": 0, "user_id_2": 1, "user_id_3": 2, ... } # 玩家连接映射 (Hash) - 用于断线重连 bomb:player:{user_id} = { "room_code": "123456", "conn_id": "ws_conn_xxx" } # 房间 TTL: 房间创建后 2 小时未活动自动过期清理 ``` --- ## 7. API 设计 ### 7.1 REST API(房间管理) 基础路径:`/api/v1/bomb-game` #### 创建房间 ``` POST /api/v1/bomb-game/rooms Authorization: Bearer Request Body: { "max_players": 8, // 可选,默认 8 "round_duration": 30, // 可选,默认 30 秒 "shake_min": 3, // 可选,默认 3 "shake_max": 8 // 可选,默认 8 } Response 200: { "code": 0, "data": { "room_code": "582916", "room_id": 1, "owner_id": 123, "max_players": 8, "round_duration": 30, "share_ticket": "xxx" // 用于微信分享 } } ``` #### 加入房间 ``` POST /api/v1/bomb-game/rooms/:room_code/join Authorization: Bearer Response 200: { "code": 0, "data": { "room_code": "582916", "seat_index": 2, "players": [ { "user_id": 123, "nickname": "张三", "avatar_url": "https://...", "seat_index": 0, "is_owner": true }, ... ] } } Error 400 (房间已满): { "code": 40001, "message": "房间已满" } Error 400 (游戏已开始): { "code": 40002, "message": "游戏已开始,无法加入" } ``` #### 获取房间信息 ``` GET /api/v1/bomb-game/rooms/:room_code Authorization: Bearer Response 200: { "code": 0, "data": { "room_code": "582916", "status": "waiting", "owner_id": 123, "max_players": 8, "round_duration": 30, "current_round": 0, "players": [ ... ] } } ``` #### 离开房间 ``` POST /api/v1/bomb-game/rooms/:room_code/leave Authorization: Bearer Response 200: { "code": 0, "message": "已离开房间" } ``` #### 获取个人战绩 ``` GET /api/v1/bomb-game/stats Authorization: Bearer Response 200: { "code": 0, "data": { "total_games": 42, "total_wins": 30, "total_losses": 12, "win_rate": 71.4, "win_streak": 3, "max_win_streak": 7, "total_score": 380 } } ``` ### 7.2 WebSocket 连接 #### 连接建立 ``` ws:///api/v1/bomb-game/ws?room_code=582916&token= ``` 连接建立后,服务端进行身份验证并将玩家加入房间的 WebSocket 广播组。 #### 心跳机制 ``` 客户端每 5 秒发送: { "type": "ping" } 服务端响应: { "type": "pong" } 超过 15 秒未收到心跳 → 标记为掉线 ``` --- ## 8. WebSocket 消息协议 所有消息均为 JSON 格式,基本结构: ```json { "type": "message_type", "data": { ... }, "timestamp": 1706000000 } ``` ### 8.1 客户端 → 服务端 消息 | type | 说明 | data 字段 | |------|------|-----------| | `ping` | 心跳 | 无 | | `shake` | 上报一次摇晃 | `{}` | | `start_game` | 房主开始游戏 | `{}` | | `next_round` | 房主发起下一轮 | `{}` | | `kick_player` | 房主踢人 | `{ "user_id": 456 }` | | `emoji` | 发送表情互动 | `{ "emoji_id": "laugh" }` | ### 8.2 服务端 → 客户端 消息 | type | 说明 | data 字段 | |------|------|-----------| | `pong` | 心跳响应 | 无 | | `room_update` | 房间信息更新(玩家加入/离开) | `{ "players": [...], "owner_id": 123 }` | | `game_countdown` | 游戏即将开始倒计时 | `{ "countdown": 3 }` | | `game_start` | 游戏正式开始 | `{ "round": 1, "bomb_holder": "user_id", "shake_target": 5, "timer_end": 1706000030 }` | | `shake_update` | 摇晃进度更新 | `{ "user_id": "xxx", "current": 3, "target": 5 }` | | `bomb_pass` | 炸弹传递 | `{ "from": "user_id_1", "to": "user_id_2", "shake_target": 4 }` | | `bomb_explode` | 炸弹爆炸 | `{ "loser_id": "user_id", "loser_nickname": "张三" }` | | `round_result` | 轮次结算 | `{ "round": 1, "loser": {...}, "scores": [...] }` | | `player_offline` | 玩家掉线 | `{ "user_id": "xxx" }` | | `player_reconnect` | 玩家重连 | `{ "user_id": "xxx" }` | | `game_state_sync` | 完整状态同步(重连用) | `{ "room": {...}, "game": {...} }` | | `emoji_broadcast` | 表情广播 | `{ "user_id": "xxx", "emoji_id": "laugh" }` | | `error` | 错误消息 | `{ "code": 50001, "message": "..." }` | | `owner_changed` | 房主变更 | `{ "new_owner_id": 456 }` | | `room_dissolved` | 房间解散 | `{ "reason": "房主退出" }` | ### 8.3 消息流转时序 #### 正常游戏流程时序: ``` 玩家A(房主) 服务端 玩家B 玩家C │ │ │ │ │ start_game │ │ │ │─────────────►│ │ │ │ │ game_countdown(3) │ │◄─────────────│─────────────►│─────────────►│ │ │ game_countdown(2) │ │◄─────────────│─────────────►│─────────────►│ │ │ game_countdown(1) │ │◄─────────────│─────────────►│─────────────►│ │ │ │ │ │ │ game_start(holder=B, shake=5)│ │◄─────────────│─────────────►│─────────────►│ │ │ │ │ │ │ shake │ │ │ │◄─────────────│ │ │ │ shake_update(current=1) │ │◄─────────────│─────────────►│─────────────►│ │ │ ... │ │ │ │ shake(第5次) │ │ │◄─────────────│ │ │ │ │ │ │ │ bomb_pass(B→C, shake=4) │ │◄─────────────│─────────────►│─────────────►│ │ │ │ │ │ │ [倒计时归零] │ │ │ │ │ │ │ bomb_explode(loser=C) │ │◄─────────────│─────────────►│─────────────►│ │ │ │ │ │ │ round_result(...) │ │◄─────────────│─────────────►│─────────────►│ ``` --- ## 9. 防作弊设计 ### 9.1 服务端权威原则 | 策略 | 说明 | |------|------| | 摇晃验证 | 客户端上报摇晃事件,**服务端计数**,不信任客户端计数 | | 频率限制 | 单次摇晃事件最小间隔 **200ms**,防止脚本快速刷摇晃 | | 倒计时服务端控制 | 客户端倒计时仅为展示,**爆炸判定由服务端触发** | | 传递判定 | 炸弹传递逻辑在服务端执行,客户端无法伪造传递 | ### 9.2 其他安全措施 | 措施 | 说明 | |------|------| | WebSocket 鉴权 | 连接时验证 JWT token | | 房间权限 | 只有房间内玩家可收发该房间消息 | | 操作校验 | 只有当前持有者可上报摇晃事件 | | 房主操作校验 | 开始游戏、踢人等操作验证房主身份 | --- ## 10. 性能与扩展性考量 ### 10.1 并发处理 | 方面 | 方案 | |------|------| | 房间隔离 | 每个房间独立 goroutine 处理游戏逻辑,避免跨房间锁竞争 | | 消息广播 | 使用 channel 实现房间内广播,每个房间维护独立的客户端列表 | | 定时器 | 使用 `time.AfterFunc` 管理倒计时,避免全局 ticker 开销 | ### 10.2 扩展性 | 方面 | 方案 | |------|------| | 多实例部署 | 通过 Redis Pub/Sub 实现跨实例房间消息广播 | | 房间分配 | 使用 Redis 存储房间 ↔ 实例映射,支持负载均衡 | | 状态恢复 | 服务重启后从 Redis 恢复房间状态 | ### 10.3 资源清理 | 触发条件 | 清理动作 | |----------|----------| | 房间 2 小时无活动 | Redis key 自动过期,MySQL 标记 dissolved | | 所有玩家退出 | 立即清理 Redis 数据 | | 服务端定时任务 | 每 30 分钟扫描僵尸房间并清理 | --- ## 11. 项目文件结构(建议) ``` internal/ └── bomb_game/ ├── handler/ │ ├── room_handler.go # REST API: 创建/加入/离开房间、查询战绩 │ └── ws_handler.go # WebSocket: 连接建立、消息路由 ├── service/ │ ├── room_service.go # 房间管理业务逻辑 │ ├── game_service.go # 游戏核心逻辑(状态机、倒计时、爆炸判定) │ └── stats_service.go # 战绩统计 ├── model/ │ ├── room.go # Room, Player 数据库模型 │ ├── round.go # Round 数据库模型 │ └── stats.go # Stats 数据库模型 ├── hub/ │ ├── hub.go # WebSocket Hub: 连接管理、消息广播 │ ├── client.go # WebSocket Client: 单个连接封装 │ └── message.go # 消息类型定义 └── engine/ ├── game_engine.go # 游戏引擎: 房间级游戏循环 └── timer.go # 倒计时管理器 ``` 路由注册文件: ``` internal/routes/ └── bomb_game_routes.go # 路由注册 ``` --- ## 12. 待扩展功能 - [ ] 观战模式:允许旁观者进入房间观看 - [ ] 道具系统:缩短/延长倒计时、跳过本次传递、反转传递方向 - [ ] 排行榜:全局积分排行、好友排行 - [ ] 房间密码:私密房间需要密码才能加入 - [ ] 语音互动:游戏中实时语音(微信实时通话组件) - [ ] 惩罚机制:输家需完成随机挑战(真心话/大冒险) - [ ] 自定义皮肤:炸弹皮肤、爆炸特效自定义 - [ ] 匹配模式:随机匹配陌生人游戏