修复提交成绩报错

This commit is contained in:
咕谷酱
2025-08-22 14:02:12 +08:00
parent b300ce9b09
commit 3350081e80
2 changed files with 96 additions and 172 deletions

View File

@@ -1,167 +0,0 @@
# 多人游戏观战和实时排行榜改进说明
## 主要改进
### 1. 游戏状态缓冲区 (GameplayStateBuffer)
- **实时分数缓冲**: 为每个房间的每个玩家维护最多50帧的分数数据
- **实时排行榜**: 自动计算和维护实时排行榜数据
- **游戏状态快照**: 为新加入的观众创建完整的游戏状态快照
- **观战者状态缓存**: 跟踪观战者状态以优化同步
### 2. 观战同步管理器 (SpectatorSyncManager)
- **跨Hub通信**: 通过Redis在MultiplayerHub和SpectatorHub之间同步状态
- **事件通知**: 游戏开始/结束、用户状态变化等事件的实时通知
- **异步消息处理**: 订阅和处理观战相关事件
### 3. 增强的MultiplayerHub功能
#### 新增方法:
- `UpdateScore(client, score_data)`: 接收实时分数更新
- `GetLeaderboard(client)`: 获取当前排行榜
- `RequestSpectatorSync(client)`: 观战者请求状态同步
#### 改进的方法:
- `JoinRoomWithPassword`: 增强新用户加入时的状态同步
- `ChangeState`: 添加观战状态处理和分数缓冲区管理
- `start_gameplay`: 启动实时排行榜广播和创建游戏快照
- `change_room_state`: 处理游戏结束时的清理工作
### 4. 实时排行榜系统
- **自动广播**: 每秒更新一次实时排行榜
- **智能启停**: 根据游戏状态自动启动/停止广播任务
- **最终排行榜**: 游戏结束时发送最终排行榜
## 客户端集成示例
### JavaScript客户端示例
```javascript
// 连接到MultiplayerHub
const connection = new signalR.HubConnectionBuilder()
.withUrl("/multiplayer")
.build();
// 监听实时排行榜更新
connection.on("LeaderboardUpdate", (leaderboard) => {
updateLeaderboardUI(leaderboard);
});
// 监听游戏状态同步(观战者)
connection.on("GameplayStateSync", (snapshot) => {
syncSpectatorUI(snapshot);
});
// 监听最终排行榜
connection.on("FinalLeaderboard", (finalLeaderboard) => {
showFinalResults(finalLeaderboard);
});
// 发送分数更新(玩家)
async function updateScore(scoreData) {
try {
await connection.invoke("UpdateScore", scoreData);
} catch (err) {
console.error("Error updating score:", err);
}
}
// 请求观战同步(观战者)
async function requestSpectatorSync() {
try {
await connection.invoke("RequestSpectatorSync");
} catch (err) {
console.error("Error requesting sync:", err);
}
}
// 获取当前排行榜
async function getCurrentLeaderboard() {
try {
return await connection.invoke("GetLeaderboard");
} catch (err) {
console.error("Error getting leaderboard:", err);
return [];
}
}
```
### Python客户端示例
```python
import signalrcore
# 创建连接
connection = signalrcore.HubConnectionBuilder() \
.with_url("ws://localhost:8000/multiplayer") \
.build()
# 监听排行榜更新
def on_leaderboard_update(leaderboard):
print("Leaderboard update:", leaderboard)
# 更新UI显示排行榜
connection.on("LeaderboardUpdate", on_leaderboard_update)
# 监听游戏状态同步
def on_gameplay_state_sync(snapshot):
print("Gameplay state sync:", snapshot)
# 同步观战界面
connection.on("GameplayStateSync", on_gameplay_state_sync)
# 发送分数更新
async def send_score_update(score, combo, accuracy):
await connection.send("UpdateScore", {
"score": score,
"combo": combo,
"accuracy": accuracy,
"completed": False
})
# 启动连接
connection.start()
```
## 配置要求
### Redis配置
确保Redis服务器运行并配置正确的连接参数
```python
# 在app/dependencies/database.py中
REDIS_CONFIG = {
'host': 'localhost',
'port': 6379,
'db': 0,
'decode_responses': True
}
```
### 数据库表结构
确保`multiplayer_event`表包含以下字段:
- `event_detail`: JSON字段用于存储事件详细信息
## 性能优化建议
1. **缓冲区大小调整**: 根据实际需求调整分数帧缓冲区大小默认50帧
2. **广播频率调整**: 可以根据网络条件调整排行榜广播频率默认1秒
3. **内存清理**: 定期清理过期的游戏状态快照和观战者状态
4. **连接池优化**: 配置Redis连接池以处理高并发请求
## 故障排除
### 常见问题
1. **排行榜不更新**: 检查Redis连接和广播任务状态
2. **观战者状态不同步**: 确认SpectatorSyncManager已正确初始化
3. **分数数据丢失**: 检查缓冲区大小和清理逻辑
### 日志监控
关键日志点:
- `[MultiplayerHub] Synced gameplay state for user X`
- `[MultiplayerHub] Broadcasted leaderboard update to room X`
- `Error updating score for user X`
- `Error in leaderboard broadcast loop`
### 调试模式
在开发环境中启用详细日志:
```python
import logging
logging.getLogger("app.signalr.hub.multiplayer").setLevel(logging.DEBUG)
```

View File

@@ -108,17 +108,72 @@ class GameplayStateBuffer:
async def create_gameplay_snapshot(self, room_id: int, room_data: Dict):
"""创建游戏状态快照用于新加入的观众"""
# 序列化复杂对象
serialized_room_data = self._serialize_room_data(room_data)
snapshot = {
'room_id': room_id,
'state': room_data.get('state'),
'current_item': room_data.get('current_item'),
'users': room_data.get('users', []),
'state': serialized_room_data.get('state'),
'current_item': serialized_room_data.get('current_item'),
'users': serialized_room_data.get('users', []),
'leaderboard': self.get_leaderboard(room_id),
'created_at': datetime.now(UTC)
'created_at': datetime.now(UTC).isoformat()
}
self.gameplay_snapshots[room_id] = snapshot
return snapshot
def _serialize_room_data(self, room_data: Dict) -> Dict:
"""序列化房间数据"""
result = {}
for key, value in room_data.items():
if hasattr(value, 'value') and hasattr(value, 'name'):
# 枚举类型
result[key] = {'name': value.name, 'value': value.value}
elif hasattr(value, '__dict__'):
# 复杂对象
if hasattr(value, 'model_dump'):
result[key] = value.model_dump()
elif hasattr(value, 'dict'):
result[key] = value.dict()
else:
# 手动序列化
obj_dict = {}
for attr_name, attr_value in value.__dict__.items():
if not attr_name.startswith('_'):
obj_dict[attr_name] = self._serialize_value(attr_value)
result[key] = obj_dict
elif isinstance(value, (list, tuple)):
result[key] = [self._serialize_value(item) for item in value]
else:
result[key] = self._serialize_value(value)
return result
def _serialize_value(self, value):
"""序列化单个值"""
if hasattr(value, 'value') and hasattr(value, 'name'):
# 枚举类型
return {'name': value.name, 'value': value.value}
elif hasattr(value, '__dict__'):
# 复杂对象
if hasattr(value, 'model_dump'):
return value.model_dump()
elif hasattr(value, 'dict'):
return value.dict()
else:
obj_dict = {}
for attr_name, attr_value in value.__dict__.items():
if not attr_name.startswith('_'):
obj_dict[attr_name] = self._serialize_value(attr_value)
return obj_dict
elif isinstance(value, (list, tuple)):
return [self._serialize_value(item) for item in value]
elif isinstance(value, dict):
return {k: self._serialize_value(v) for k, v in value.items()}
elif isinstance(value, (str, int, float, bool, type(None))):
return value
else:
return str(value)
def get_gameplay_snapshot(self, room_id: int) -> Optional[Dict]:
"""获取游戏状态快照"""
return self.gameplay_snapshots.get(room_id)
@@ -155,12 +210,48 @@ class SpectatorSyncManager:
self.redis = redis_client
self.channel_prefix = "multiplayer_spectator"
def _serialize_for_json(self, obj):
"""递归序列化对象为JSON兼容格式"""
if hasattr(obj, '__dict__'):
# 如果对象有__dict__属性将其转换为字典
if hasattr(obj, 'model_dump'):
# 对于Pydantic模型
return obj.model_dump()
elif hasattr(obj, 'dict'):
# 对于较旧的Pydantic模型
return obj.dict()
else:
# 对于普通对象
result = {}
for key, value in obj.__dict__.items():
if not key.startswith('_'): # 跳过私有属性
result[key] = self._serialize_for_json(value)
return result
elif isinstance(obj, dict):
return {key: self._serialize_for_json(value) for key, value in obj.items()}
elif isinstance(obj, (list, tuple)):
return [self._serialize_for_json(item) for item in obj]
elif isinstance(obj, datetime):
# 处理datetime对象
return obj.isoformat()
elif hasattr(obj, 'value') and hasattr(obj, 'name'):
# 对于枚举类型
return {'name': obj.name, 'value': obj.value}
elif isinstance(obj, (str, int, float, bool, type(None))):
return obj
else:
# 对于其他类型,尝试转换为字符串
return str(obj)
async def notify_spectator_hubs(self, room_id: int, event_type: str, data: Dict):
"""通知观战Hub游戏状态变化"""
# 序列化复杂对象为JSON兼容格式
serialized_data = self._serialize_for_json(data)
message = {
'room_id': room_id,
'event_type': event_type,
'data': data,
'data': serialized_data,
'timestamp': datetime.now(UTC).isoformat()
}