修改为发起/接受对局以避开at无法获取id的问题

This commit is contained in:
2025-10-28 17:49:16 +08:00
parent f217cd958b
commit 7d28e2e2aa
4 changed files with 149 additions and 71 deletions

View File

@@ -228,7 +228,29 @@ if game_type == 'gomoku':
- 改进错误提示,显示实际接收到的参数内容
- 将 callback.py 中的消息内容日志级别从 DEBUG 改为 INFO便于追踪
- 原因进一步诊断用户ID识别失败的问题添加调试信息帮助定位问题
- 阻碍因素:需要用户测试并提供日志输出来确定实际的消息格式
- 阻碍因素:WPS callback不提供被@用户的ID信息
- 状态:不成功
## [2025-10-28 17:55:00]
- 已修改:
- games/gomoku.py - 重构游戏发起机制,从@用户改为挑战-接受模式
- games/base.py - 更新全局帮助信息
- routers/callback.py - 添加完整callback数据日志
- 更改:
- **核心架构变更**:从"@用户发起对战"改为"挑战-接受"机制
- 新增 `_create_challenge()` 方法 - 用户发起挑战
- 新增 `_accept_challenge()` 方法 - 其他用户接受挑战
- 新增 `_cancel_challenge()` 方法 - 取消自己的挑战
- 删除 `_parse_opponent()` 方法(不再需要)
- 删除 `_start_game()` 方法(由新方法替代)
- 更新游戏池数据结构,添加 `challenges` 列表
- 更新所有帮助信息和错误提示
- 指令变更:
- `.gomoku challenge` / `.gomoku start` - 发起挑战
- `.gomoku accept` / `.gomoku join` - 接受挑战
- `.gomoku cancel` - 取消挑战
- 原因WPS callback消息内容中@用户只是文本形式(如"@揭英飙"不包含user_id无法实现@用户发起对战
- 阻碍因素:无
- 状态:未确认
# 最终审查

View File

@@ -74,7 +74,8 @@ def get_help_message() -> str:
- `.idiom blacklist` - 查看黑名单
### ⚫ 五子棋
- `.gomoku @对手` - 发起
- `.gomoku challenge` - 发起
- `.gomoku accept` - 接受挑战
- `.gomoku A1` - 落子
- `.gomoku show` - 显示棋盘
- `.gomoku resign` - 认输

View File

@@ -54,6 +54,18 @@ class GomokuGame(BaseGame):
if action in ['help', '帮助']:
return self.get_help()
# 发起挑战
if action in ['challenge', 'start', '挑战', '开始']:
return self._create_challenge(chat_id, user_id)
# 接受挑战
if action in ['accept', 'join', '接受', '加入']:
return self._accept_challenge(chat_id, user_id)
# 取消挑战
if action in ['cancel', '取消']:
return self._cancel_challenge(chat_id, user_id)
# 列出所有对战
if action in ['list', '列表', '查看']:
return self._list_games(chat_id)
@@ -75,48 +87,14 @@ class GomokuGame(BaseGame):
if coord is not None:
return self._make_move(chat_id, user_id, action)
# 尝试解析为@对手(开始游戏)
opponent_id = self._parse_opponent(args)
logger.info(f"五子棋指令解析 - opponent_id: {opponent_id}")
if opponent_id is not None:
return self._start_game(chat_id, user_id, opponent_id)
# 未识别的指令
logger.warning(f"五子棋未识别的指令 - args: {args}")
return f"❌ 未识别的指令:{args}\n\n💡 提示:\n- 要开始游戏,请使用 `.gomoku` 然后@对手\n- 落子,请使用 `.gomoku A1`(坐标格式)\n- 查看帮助,请使用 `.gomoku help`"
return f"❌ 未识别的指令:{args}\n\n💡 提示:\n- 发起挑战:`.gomoku challenge`\n- 接受挑战:`.gomoku accept`\n- 落子`.gomoku A1`\n- 查看帮助`.gomoku help`"
except Exception as e:
logger.error(f"处理五子棋指令错误: {e}", exc_info=True)
return f"❌ 处理指令出错: {str(e)}"
def _parse_opponent(self, args: str) -> Optional[int]:
"""解析@对手的用户ID
Args:
args: 参数字符串
Returns:
用户ID或None
"""
# WPS格式<at user_id="123456"></at> 或其他变体
# 尝试多种格式
patterns = [
r'<at\s+user_id="(\d+)"[^>]*>', # <at user_id="123456"> 或 <at user_id="123456"></at>
r'<at\s+user_id=\'(\d+)\'[^>]*>', # <at user_id='123456'>
r'user_id="(\d+)"', # 直接查找 user_id="xxx"
r'user_id=\'(\d+)\'', # 直接查找 user_id='xxx'
]
for pattern in patterns:
match = re.search(pattern, args)
if match:
try:
return int(match.group(1))
except ValueError:
pass
return None
def _get_game_pool(self, chat_id: int) -> Dict[str, Any]:
"""获取游戏池
@@ -163,56 +141,93 @@ class GomokuGame(BaseGame):
return None
def _start_game(self, chat_id: int, user_id: int, opponent_id: int) -> str:
"""开始新游戏
def _create_challenge(self, chat_id: int, user_id: int) -> str:
"""创建挑战
Args:
chat_id: 会话ID
user_id: 发起者ID
opponent_id: 对手ID
Returns:
提示消息
"""
# 检查是否与自己对战
if user_id == opponent_id:
return "❌ 不能和自己下棋哦!"
# 获取游戏池
pool = self._get_game_pool(chat_id)
games = pool.get("games", [])
challenges = pool.get("challenges", [])
# 检查用户是否已经在对战中
user_game = self._find_user_game(chat_id, user_id)
if user_game:
return "⚠️ 你已经在对战中!\n\n输入 `.gomoku show` 查看棋盘"
# 检查用户是否已经发起了挑战
for challenge in challenges:
if challenge["challenger_id"] == user_id:
return "⚠️ 你已经发起了一个挑战!\n\n等待其他人接受,或输入 `.gomoku cancel` 取消挑战"
# 创建挑战
current_time = int(time.time())
challenge = {
"challenger_id": user_id,
"created_at": current_time
}
challenges.append(challenge)
pool["challenges"] = challenges
self._save_game_pool(chat_id, pool)
text = f"## 🎯 五子棋挑战\n\n"
text += f"<at user_id=\"{user_id}\"></at> 发起了五子棋挑战!\n\n"
text += f"💡 想要应战吗?输入 `.gomoku accept` 接受挑战"
return text
def _accept_challenge(self, chat_id: int, user_id: int) -> str:
"""接受挑战
Args:
chat_id: 会话ID
user_id: 接受者ID
Returns:
提示消息
"""
# 获取游戏池
pool = self._get_game_pool(chat_id)
games = pool.get("games", [])
challenges = pool.get("challenges", [])
if not challenges:
return "⚠️ 当前没有挑战可以接受\n\n输入 `.gomoku challenge` 发起挑战"
# 检查用户是否已经在对战中
user_game = self._find_user_game(chat_id, user_id)
if user_game:
return "⚠️ 你已经在对战中!\n\n输入 `.gomoku show` 查看棋盘"
# 获取最新的挑战
challenge = challenges[-1]
challenger_id = challenge["challenger_id"]
# 不能接受自己的挑战
if challenger_id == user_id:
return "❌ 不能接受自己的挑战!"
# 检查是否已达到最大并发数
active_games = [g for g in games if g["status"] == "playing"]
if len(active_games) >= self.max_concurrent_games:
return f"⚠️ 当前聊天已有 {len(active_games)} 局对战,已达到最大并发数限制"
# 检查这两个用户是否已经在对战
for game in active_games:
players = {game["player_black"], game["player_white"]}
if user_id in players and opponent_id in players:
return "⚠️ 你们已经有一局正在进行的对战了!\n\n输入 `.gomoku show` 查看棋盘"
# 检查用户是否已经在其他对战中
user_game = self._find_user_game(chat_id, user_id)
if user_game:
opponent = user_game["player_white"] if user_game["player_black"] == user_id else user_game["player_black"]
return f"⚠️ 你已经在与 <at user_id=\"{opponent}\"></at> 对战中!\n\n输入 `.gomoku show` 查看棋盘"
opponent_game = self._find_user_game(chat_id, opponent_id)
if opponent_game:
other = opponent_game["player_white"] if opponent_game["player_black"] == opponent_id else opponent_game["player_black"]
return f"⚠️ 对手已经在与 <at user_id=\"{other}\"></at> 对战中!"
# 创建新游戏
# 创建游戏
current_time = int(time.time())
game_id = f"p{user_id}_p{opponent_id}_{current_time}"
game_id = f"p{challenger_id}_p{user_id}_{current_time}"
new_game = {
"game_id": game_id,
"player_black": user_id, # 发起者执黑(先手)
"player_white": opponent_id, # 对手执白(后手)
"current_player": user_id, # 黑方先手
"player_black": challenger_id, # 挑战者执黑(先手)
"player_white": user_id, # 接受者执白(后手)
"current_player": challenger_id, # 黑方先手
"board": logic.create_empty_board(),
"status": "playing",
"winner": None,
@@ -223,13 +238,18 @@ class GomokuGame(BaseGame):
}
games.append(new_game)
# 移除已接受的挑战
challenges.remove(challenge)
pool["games"] = games
pool["challenges"] = challenges
self._save_game_pool(chat_id, pool)
text = f"## ⚫ 五子棋对战开始!\n\n"
text += f"**黑方(先手)**<at user_id=\"{user_id}\"></at> ⚫\n\n"
text += f"**白方(后手)**<at user_id=\"{opponent_id}\"></at> ⚪\n\n"
text += f"**轮到**<at user_id=\"{user_id}\"></at> ⚫\n\n"
text += f"**黑方(先手)**<at user_id=\"{challenger_id}\"></at> ⚫\n\n"
text += f"**白方(后手)**<at user_id=\"{user_id}\"></at> ⚪\n\n"
text += f"**轮到**<at user_id=\"{challenger_id}\"></at> ⚫\n\n"
text += "💡 提示:\n"
text += "- 黑方有禁手规则(三三、四四、长连禁手)\n"
text += "- 输入 `.gomoku A1` 在A1位置落子\n"
@@ -237,6 +257,37 @@ class GomokuGame(BaseGame):
return text
def _cancel_challenge(self, chat_id: int, user_id: int) -> str:
"""取消挑战
Args:
chat_id: 会话ID
user_id: 用户ID
Returns:
提示消息
"""
# 获取游戏池
pool = self._get_game_pool(chat_id)
challenges = pool.get("challenges", [])
# 查找用户的挑战
user_challenge = None
for challenge in challenges:
if challenge["challenger_id"] == user_id:
user_challenge = challenge
break
if not user_challenge:
return "⚠️ 你没有发起挑战"
# 移除挑战
challenges.remove(user_challenge)
pool["challenges"] = challenges
self._save_game_pool(chat_id, pool)
return "✅ 已取消挑战"
def _make_move(self, chat_id: int, user_id: int, coord: str) -> str:
"""落子
@@ -477,12 +528,14 @@ class GomokuGame(BaseGame):
return """## ⚫ 五子棋
### 基础用法
- `.gomoku @对手` - 向对手发起
- `.gomoku challenge` - 发起
- `.gomoku accept` - 接受挑战
- `.gomoku A1` - 在A1位置落子
- `.gomoku show` - 显示当前棋盘
- `.gomoku resign` - 认输
### 其他指令
- `.gomoku cancel` - 取消自己的挑战
- `.gomoku list` - 列出所有进行中的对战
- `.gomoku stats` - 查看个人战绩
@@ -502,7 +555,8 @@ class GomokuGame(BaseGame):
### 示例
```
.gomoku @123456 # 向用户123456发起
.gomoku challenge # 发起
.gomoku accept # 接受挑战
.gomoku H8 # 在中心位置落子
.gomoku show # 查看棋盘
.gomoku resign # 认输

View File

@@ -29,6 +29,7 @@ async def callback_receive(request: Request):
data = await request.json()
logger.info(f"收到消息: chatid={data.get('chatid')}, creator={data.get('creator')}")
logger.info(f"消息内容: {data.get('content')}")
logger.info(f"完整callback数据: {data}")
# 验证请求
try: