Merge branch 'task/add-idiom-chain-game_2025-10-28_1'
This commit is contained in:
238
.tasks/2025-10-28_1_add-idiom-chain-game.md
Normal file
238
.tasks/2025-10-28_1_add-idiom-chain-game.md
Normal file
@@ -0,0 +1,238 @@
|
|||||||
|
# 背景
|
||||||
|
文件名:2025-10-28_1_add-idiom-chain-game.md
|
||||||
|
创建于:2025-10-28_15:43:00
|
||||||
|
创建者:admin
|
||||||
|
主分支:main
|
||||||
|
任务分支:task/add-idiom-chain-game_2025-10-28_1
|
||||||
|
Yolo模式:Off
|
||||||
|
|
||||||
|
# 任务描述
|
||||||
|
在WPS Bot Game项目中新增一个成语接龙游戏功能。
|
||||||
|
|
||||||
|
## 核心需求
|
||||||
|
1. 群内多人游戏,机器人作为裁判和出题者
|
||||||
|
2. 允许按拼音接龙(包括谐音接龙)
|
||||||
|
3. 没有时间限制
|
||||||
|
4. 不需要提示功能
|
||||||
|
5. 游戏记录保存到.stats统计中
|
||||||
|
6. 不允许重复使用成语
|
||||||
|
7. 不需要难度分级(非人机对战)
|
||||||
|
8. 需要裁判指令用于接受/拒绝玩家回答
|
||||||
|
|
||||||
|
## 游戏玩法
|
||||||
|
- 机器人出题(给出起始成语)
|
||||||
|
- 群内玩家轮流接龙
|
||||||
|
- 机器人判断接龙是否有效(拼音/谐音匹配、未重复使用)
|
||||||
|
- 裁判可以手动接受或拒绝某个回答
|
||||||
|
- 记录每个玩家的成功接龙次数
|
||||||
|
|
||||||
|
# 项目概览
|
||||||
|
|
||||||
|
## 项目结构
|
||||||
|
```
|
||||||
|
WPSBotGame/
|
||||||
|
├── app.py # FastAPI主应用
|
||||||
|
├── config.py # 配置管理
|
||||||
|
├── core/
|
||||||
|
│ ├── database.py # SQLite数据库操作
|
||||||
|
│ ├── middleware.py # 中间件
|
||||||
|
│ └── models.py # 数据模型
|
||||||
|
├── games/ # 游戏模块
|
||||||
|
│ ├── base.py # 游戏基类
|
||||||
|
│ ├── dice.py # 骰娘游戏
|
||||||
|
│ ├── rps.py # 石头剪刀布
|
||||||
|
│ ├── fortune.py # 运势占卜
|
||||||
|
│ ├── guess.py # 猜数字
|
||||||
|
│ └── quiz.py # 问答游戏
|
||||||
|
├── data/ # 数据文件
|
||||||
|
│ ├── bot.db # SQLite数据库
|
||||||
|
│ ├── quiz.json # 问答题库
|
||||||
|
│ └── fortunes.json # 运势数据
|
||||||
|
├── routers/ # 路由处理
|
||||||
|
│ ├── callback.py # WPS回调处理
|
||||||
|
│ └── health.py # 健康检查
|
||||||
|
└── utils/ # 工具模块
|
||||||
|
├── message.py # 消息发送
|
||||||
|
├── parser.py # 指令解析
|
||||||
|
└── rate_limit.py # 限流控制
|
||||||
|
```
|
||||||
|
|
||||||
|
## 技术栈
|
||||||
|
- FastAPI:Web框架
|
||||||
|
- SQLite:数据存储
|
||||||
|
- WPS协作机器人API:消息接收与发送
|
||||||
|
|
||||||
|
## 现有游戏架构
|
||||||
|
1. 所有游戏继承`BaseGame`基类
|
||||||
|
2. 必须实现`handle(command, chat_id, user_id)`方法处理指令
|
||||||
|
3. 必须实现`get_help()`方法返回帮助信息
|
||||||
|
4. 游戏状态存储在数据库`game_states`表:`(chat_id, user_id, game_type)`作为联合主键
|
||||||
|
5. 游戏统计存储在`game_stats`表:记录`wins`, `losses`, `draws`, `total_plays`
|
||||||
|
6. 指令通过`CommandParser`解析,在`callback.py`中分发到对应游戏处理器
|
||||||
|
|
||||||
|
## 数据库设计
|
||||||
|
### game_states表
|
||||||
|
- chat_id: 会话ID
|
||||||
|
- user_id: 用户ID
|
||||||
|
- game_type: 游戏类型
|
||||||
|
- state_data: JSON格式的游戏状态数据
|
||||||
|
- created_at/updated_at: 时间戳
|
||||||
|
|
||||||
|
### game_stats表
|
||||||
|
- user_id: 用户ID
|
||||||
|
- game_type: 游戏类型
|
||||||
|
- wins/losses/draws/total_plays: 统计数据
|
||||||
|
|
||||||
|
# 分析
|
||||||
|
|
||||||
|
## 关键技术挑战
|
||||||
|
|
||||||
|
### 1. 群级别vs个人级别状态管理
|
||||||
|
现有游戏(猜数字、问答)都是个人独立状态,使用`(chat_id, user_id, game_type)`作为主键。
|
||||||
|
|
||||||
|
成语接龙是群内共享游戏,需要:
|
||||||
|
- 群级别的游戏状态:当前成语、已用成语列表、接龙长度、当前轮到谁
|
||||||
|
- 个人级别的统计:每个玩家的成功接龙次数
|
||||||
|
|
||||||
|
**可能方案:**
|
||||||
|
- 使用特殊user_id(如0或-1)存储群级别游戏状态
|
||||||
|
- 或者在state_data中存储所有玩家信息
|
||||||
|
|
||||||
|
### 2. 成语词库准备
|
||||||
|
需要准备:
|
||||||
|
- 成语列表(至少500-1000个常用成语)
|
||||||
|
- 每个成语的拼音信息(用于判断接龙是否匹配)
|
||||||
|
- 数据格式:JSON文件,类似quiz.json
|
||||||
|
|
||||||
|
### 3. 拼音匹配逻辑
|
||||||
|
- 需要拼音库支持(pypinyin)
|
||||||
|
- 支持谐音匹配(声母韵母匹配)
|
||||||
|
- 处理多音字情况
|
||||||
|
|
||||||
|
### 4. 裁判指令设计
|
||||||
|
需要额外指令:
|
||||||
|
- `.idiom accept` - 接受上一个回答
|
||||||
|
- `.idiom reject` - 拒绝上一个回答
|
||||||
|
- 需要权限控制(谁可以当裁判?)
|
||||||
|
|
||||||
|
### 5. 游戏流程设计
|
||||||
|
```
|
||||||
|
1. 开始游戏:.idiom start
|
||||||
|
- 机器人随机给出起始成语
|
||||||
|
- 创建群级别游戏状态
|
||||||
|
|
||||||
|
2. 玩家接龙:.idiom [成语]
|
||||||
|
- 检查是否在词库中
|
||||||
|
- 检查拼音是否匹配(首字拼音 == 上一个成语尾字拼音)
|
||||||
|
- 检查是否已使用过
|
||||||
|
- 自动判断或等待裁判确认
|
||||||
|
|
||||||
|
3. 裁判操作:.idiom accept/reject
|
||||||
|
- 手动接受或拒绝最近的回答
|
||||||
|
|
||||||
|
4. 查看状态:.idiom status
|
||||||
|
- 显示当前成语、已用成语数量、参与人数
|
||||||
|
|
||||||
|
5. 结束游戏:.idiom stop
|
||||||
|
- 显示统计信息
|
||||||
|
- 更新每个玩家的game_stats
|
||||||
|
```
|
||||||
|
|
||||||
|
## 现有代码分析
|
||||||
|
|
||||||
|
### CommandParser (utils/parser.py)
|
||||||
|
需要添加成语接龙指令映射:
|
||||||
|
```python
|
||||||
|
'.idiom': 'idiom',
|
||||||
|
'.成语接龙': 'idiom',
|
||||||
|
```
|
||||||
|
|
||||||
|
### callback.py (routers/callback.py)
|
||||||
|
需要在`handle_command`函数中添加idiom游戏分支:
|
||||||
|
```python
|
||||||
|
if game_type == 'idiom':
|
||||||
|
from games.idiom import IdiomGame
|
||||||
|
game = IdiomGame()
|
||||||
|
return await game.handle(command, chat_id, user_id)
|
||||||
|
```
|
||||||
|
|
||||||
|
### base.py (games/base.py)
|
||||||
|
需要更新`get_help_message()`和`get_stats_message()`,添加成语接龙信息。
|
||||||
|
|
||||||
|
### config.py
|
||||||
|
可能需要添加成语接龙相关配置:
|
||||||
|
```python
|
||||||
|
"idiom": {
|
||||||
|
"auto_judge": True, # 是否自动判断
|
||||||
|
"require_approval": False, # 是否需要裁判确认
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
# 提议的解决方案
|
||||||
|
|
||||||
|
(待INNOVATE模式填写)
|
||||||
|
|
||||||
|
# 当前执行步骤:"1. 创建任务文件"
|
||||||
|
|
||||||
|
# 任务进度
|
||||||
|
|
||||||
|
## [2025-10-28 15:45:00]
|
||||||
|
- 已修改:requirements.txt, config.py, utils/parser.py, routers/callback.py, games/base.py
|
||||||
|
- 已创建:games/idiom.py
|
||||||
|
- 更改:
|
||||||
|
1. 在requirements.txt中添加pypinyin==0.51.0依赖
|
||||||
|
2. 在config.py的GAME_CONFIG中添加idiom配置(起始成语池、历史显示数量)
|
||||||
|
3. 在utils/parser.py的COMMAND_MAP中添加.idiom、.成语接龙、.成语指令映射
|
||||||
|
4. 创建games/idiom.py实现完整的成语接龙游戏逻辑:
|
||||||
|
- IdiomGame类继承BaseGame
|
||||||
|
- 实现handle()主指令分发方法
|
||||||
|
- 实现_start_game()开始游戏
|
||||||
|
- 实现_make_chain()玩家接龙
|
||||||
|
- 实现_set_next_user()指定下一位
|
||||||
|
- 实现_reject_idiom()裁判拒绝
|
||||||
|
- 实现_show_status()显示状态
|
||||||
|
- 实现_show_blacklist()显示黑名单
|
||||||
|
- 实现_stop_game()结束游戏
|
||||||
|
- 实现_get_pinyin()获取拼音(支持多音字)
|
||||||
|
- 实现_check_pinyin_match()检查拼音匹配(忽略声调)
|
||||||
|
- 实现_parse_mentioned_user()解析@用户
|
||||||
|
- 实现_can_answer()权限检查(防连续、指定轮次)
|
||||||
|
- 实现_validate_idiom()词语验证(4字、拼音匹配、未使用、未黑名单)
|
||||||
|
- 实现get_help()帮助信息
|
||||||
|
5. 在routers/callback.py的handle_command()中添加idiom游戏分支
|
||||||
|
6. 在games/base.py的get_help_message()中添加成语接龙帮助信息
|
||||||
|
7. 在games/base.py的get_stats_message()的game_names字典中添加idiom映射
|
||||||
|
- 原因:实现成语接龙游戏功能的所有核心代码
|
||||||
|
- 阻碍因素:无
|
||||||
|
- 状态:未确认
|
||||||
|
|
||||||
|
## [2025-10-28 15:50:00]
|
||||||
|
- 已修改:games/base.py
|
||||||
|
- 更改:在get_help_message()的成语接龙部分添加黑名单相关指令说明
|
||||||
|
- 添加 `.idiom reject [词语]` - 拒绝词语加入黑名单(仅发起人)
|
||||||
|
- 添加 `.idiom blacklist` - 查看黑名单
|
||||||
|
- 原因:用户反馈.help帮助信息中看不到黑名单机制的使用说明
|
||||||
|
- 阻碍因素:无
|
||||||
|
- 状态:未确认
|
||||||
|
|
||||||
|
## [2025-10-28 15:55:00]
|
||||||
|
- 已修改:games/idiom.py
|
||||||
|
- 已创建:data/idiom_blacklist.json
|
||||||
|
- 更改:将黑名单机制从游戏状态改为全局永久存储
|
||||||
|
1. 创建data/idiom_blacklist.json作为全局黑名单数据文件
|
||||||
|
2. 在IdiomGame.__init__()中添加黑名单文件路径和懒加载变量
|
||||||
|
3. 添加_load_blacklist()方法从文件懒加载全局黑名单
|
||||||
|
4. 添加_save_blacklist()方法保存黑名单到文件
|
||||||
|
5. 修改_validate_idiom()方法检查全局黑名单而非游戏状态中的黑名单
|
||||||
|
6. 修改_start_game()方法移除state_data中的blacklist字段初始化
|
||||||
|
7. 修改_reject_idiom()方法将词语添加到全局黑名单并保存到文件
|
||||||
|
8. 修改_show_blacklist()方法显示全局黑名单,不再依赖游戏状态
|
||||||
|
9. 更新所有提示信息,明确说明是"永久禁用"
|
||||||
|
- 原因:用户要求被拒绝的词语应该永久不可用,而不是仅本局游戏不可用
|
||||||
|
- 阻碍因素:无
|
||||||
|
- 状态:未确认
|
||||||
|
|
||||||
|
# 最终审查
|
||||||
|
|
||||||
|
(待REVIEW模式完成后填写)
|
||||||
|
|
||||||
10
config.py
10
config.py
@@ -58,5 +58,15 @@ GAME_CONFIG = {
|
|||||||
"quiz": {
|
"quiz": {
|
||||||
"timeout": 60, # 答题超时时间(秒)
|
"timeout": 60, # 答题超时时间(秒)
|
||||||
},
|
},
|
||||||
|
"idiom": {
|
||||||
|
"max_history_display": 10, # 状态显示最近N个成语
|
||||||
|
"starter_idioms": [ # 起始成语池
|
||||||
|
"一马当先", "龙马精神", "马到成功", "开门见山",
|
||||||
|
"心想事成", "万事如意", "风调雨顺", "国泰民安",
|
||||||
|
"四季平安", "安居乐业", "业精于勤", "勤学苦练",
|
||||||
|
"练达老成", "成竹在胸", "胸有成竹", "竹报平安",
|
||||||
|
"平步青云", "云程发轫", "刃迎缕解", "解甲归田"
|
||||||
|
]
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
5
data/idiom_blacklist.json
Normal file
5
data/idiom_blacklist.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"blacklist": [],
|
||||||
|
"description": "成语接龙游戏全局黑名单,被拒绝的词语将永久不可使用"
|
||||||
|
}
|
||||||
|
|
||||||
@@ -64,6 +64,15 @@ def get_help_message() -> str:
|
|||||||
- `.quiz` - 随机问题
|
- `.quiz` - 随机问题
|
||||||
- `.quiz 答案` - 回答问题
|
- `.quiz 答案` - 回答问题
|
||||||
|
|
||||||
|
### 🀄 成语接龙
|
||||||
|
- `.idiom start [成语]` - 开始游戏
|
||||||
|
- `.idiom [成语]` - 接龙
|
||||||
|
- `.idiom [成语] @某人` - 接龙并指定下一位
|
||||||
|
- `.idiom stop` - 结束游戏
|
||||||
|
- `.idiom status` - 查看状态
|
||||||
|
- `.idiom reject [词语]` - 拒绝词语加入黑名单(仅发起人)
|
||||||
|
- `.idiom blacklist` - 查看黑名单
|
||||||
|
|
||||||
### 其他
|
### 其他
|
||||||
- `.help` - 显示帮助
|
- `.help` - 显示帮助
|
||||||
- `.stats` - 查看个人统计
|
- `.stats` - 查看个人统计
|
||||||
@@ -95,7 +104,8 @@ def get_stats_message(user_id: int) -> str:
|
|||||||
game_names = {
|
game_names = {
|
||||||
'rps': '✊ 石头剪刀布',
|
'rps': '✊ 石头剪刀布',
|
||||||
'guess': '🔢 猜数字',
|
'guess': '🔢 猜数字',
|
||||||
'quiz': '📝 问答游戏'
|
'quiz': '📝 问答游戏',
|
||||||
|
'idiom': '🀄 成语接龙'
|
||||||
}
|
}
|
||||||
|
|
||||||
for row in stats:
|
for row in stats:
|
||||||
|
|||||||
664
games/idiom.py
Normal file
664
games/idiom.py
Normal file
@@ -0,0 +1,664 @@
|
|||||||
|
"""成语接龙游戏"""
|
||||||
|
import json
|
||||||
|
import random
|
||||||
|
import logging
|
||||||
|
import time
|
||||||
|
import re
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Optional, Tuple, Dict, Any, List
|
||||||
|
from pypinyin import pinyin, Style
|
||||||
|
from games.base import BaseGame
|
||||||
|
from utils.parser import CommandParser
|
||||||
|
from config import GAME_CONFIG
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
class IdiomGame(BaseGame):
|
||||||
|
"""成语接龙游戏"""
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
"""初始化游戏"""
|
||||||
|
super().__init__()
|
||||||
|
self.config = GAME_CONFIG.get('idiom', {})
|
||||||
|
self.max_history_display = self.config.get('max_history_display', 10)
|
||||||
|
self.starter_idioms = self.config.get('starter_idioms', [
|
||||||
|
"一马当先", "龙马精神", "马到成功", "开门见山"
|
||||||
|
])
|
||||||
|
self._blacklist = None
|
||||||
|
self.blacklist_file = Path(__file__).parent.parent / "data" / "idiom_blacklist.json"
|
||||||
|
|
||||||
|
async def handle(self, command: str, chat_id: int, user_id: int) -> str:
|
||||||
|
"""处理成语接龙指令
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command: 指令,如 ".idiom start" 或 ".idiom 马到成功"
|
||||||
|
chat_id: 会话ID
|
||||||
|
user_id: 用户ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
回复消息
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
# 提取参数
|
||||||
|
_, args = CommandParser.extract_command_args(command)
|
||||||
|
args = args.strip()
|
||||||
|
|
||||||
|
# 没有参数,显示帮助
|
||||||
|
if not args:
|
||||||
|
return self.get_help()
|
||||||
|
|
||||||
|
# 解析参数
|
||||||
|
parts = args.split(maxsplit=1)
|
||||||
|
action = parts[0].lower()
|
||||||
|
|
||||||
|
# 开始游戏
|
||||||
|
if action in ['start', '开始']:
|
||||||
|
starter = parts[1].strip() if len(parts) > 1 else None
|
||||||
|
return self._start_game(chat_id, user_id, starter)
|
||||||
|
|
||||||
|
# 结束游戏
|
||||||
|
if action in ['stop', '结束', 'end']:
|
||||||
|
return self._stop_game(chat_id, user_id)
|
||||||
|
|
||||||
|
# 查看状态
|
||||||
|
if action in ['status', '状态']:
|
||||||
|
return self._show_status(chat_id)
|
||||||
|
|
||||||
|
# 查看黑名单
|
||||||
|
if action in ['blacklist', '黑名单']:
|
||||||
|
return self._show_blacklist(chat_id)
|
||||||
|
|
||||||
|
# 查看帮助
|
||||||
|
if action in ['help', '帮助']:
|
||||||
|
return self.get_help()
|
||||||
|
|
||||||
|
# 裁判拒绝
|
||||||
|
if action in ['reject', '拒绝']:
|
||||||
|
if len(parts) < 2:
|
||||||
|
return "❌ 请指定要拒绝的词语,如:`.idiom reject 词语`"
|
||||||
|
idiom_to_reject = parts[1].strip()
|
||||||
|
return self._reject_idiom(chat_id, user_id, idiom_to_reject)
|
||||||
|
|
||||||
|
# 指定下一位
|
||||||
|
if action in ['next', '下一位']:
|
||||||
|
if len(parts) < 2:
|
||||||
|
return "❌ 请@要指定的用户"
|
||||||
|
mentioned = self._parse_mentioned_user(args)
|
||||||
|
if not mentioned:
|
||||||
|
return "❌ 未识别到@的用户"
|
||||||
|
return self._set_next_user(chat_id, user_id, mentioned)
|
||||||
|
|
||||||
|
# 默认:接龙
|
||||||
|
# 整个args都是成语(可能包含@用户)
|
||||||
|
return self._make_chain(chat_id, user_id, args)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"处理成语接龙指令错误: {e}", exc_info=True)
|
||||||
|
return f"❌ 处理指令出错: {str(e)}"
|
||||||
|
|
||||||
|
def _load_blacklist(self) -> List[str]:
|
||||||
|
"""懒加载全局黑名单
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
黑名单列表
|
||||||
|
"""
|
||||||
|
if self._blacklist is None:
|
||||||
|
try:
|
||||||
|
if self.blacklist_file.exists():
|
||||||
|
with open(self.blacklist_file, 'r', encoding='utf-8') as f:
|
||||||
|
data = json.load(f)
|
||||||
|
self._blacklist = data.get('blacklist', [])
|
||||||
|
else:
|
||||||
|
self._blacklist = []
|
||||||
|
logger.info(f"黑名单加载完成,共 {len(self._blacklist)} 个词语")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"加载黑名单失败: {e}")
|
||||||
|
self._blacklist = []
|
||||||
|
return self._blacklist
|
||||||
|
|
||||||
|
def _save_blacklist(self):
|
||||||
|
"""保存黑名单到文件"""
|
||||||
|
try:
|
||||||
|
self.blacklist_file.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
data = {
|
||||||
|
"blacklist": self._blacklist if self._blacklist is not None else [],
|
||||||
|
"description": "成语接龙游戏全局黑名单,被拒绝的词语将永久不可使用"
|
||||||
|
}
|
||||||
|
with open(self.blacklist_file, 'w', encoding='utf-8') as f:
|
||||||
|
json.dump(data, f, ensure_ascii=False, indent=2)
|
||||||
|
logger.info(f"黑名单已保存,共 {len(data['blacklist'])} 个词语")
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"保存黑名单失败: {e}")
|
||||||
|
|
||||||
|
def _get_pinyin(self, char: str, all_readings: bool = True) -> list:
|
||||||
|
"""获取单字拼音
|
||||||
|
|
||||||
|
Args:
|
||||||
|
char: 单个汉字
|
||||||
|
all_readings: 是否返回多音字的所有读音
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
拼音列表
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
result = pinyin(char, style=Style.NORMAL, heteronym=all_readings)
|
||||||
|
return result[0] if result else []
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"获取拼音错误: {e}")
|
||||||
|
return []
|
||||||
|
|
||||||
|
def _check_pinyin_match(self, last_char: str, first_char: str) -> Tuple[bool, str, str]:
|
||||||
|
"""检查拼音匹配
|
||||||
|
|
||||||
|
Args:
|
||||||
|
last_char: 上一个成语的最后一个字
|
||||||
|
first_char: 当前成语的第一个字
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(是否匹配, 上一个拼音, 当前拼音)
|
||||||
|
"""
|
||||||
|
last_pinyins = self._get_pinyin(last_char)
|
||||||
|
first_pinyins = self._get_pinyin(first_char)
|
||||||
|
|
||||||
|
# 只要有任何一个读音匹配就算成功
|
||||||
|
for lp in last_pinyins:
|
||||||
|
for fp in first_pinyins:
|
||||||
|
if lp.lower() == fp.lower():
|
||||||
|
return True, lp, fp
|
||||||
|
|
||||||
|
# 没有匹配,返回第一个读音
|
||||||
|
return False, last_pinyins[0] if last_pinyins else '', first_pinyins[0] if first_pinyins else ''
|
||||||
|
|
||||||
|
def _parse_mentioned_user(self, content: str) -> Optional[int]:
|
||||||
|
"""解析消息中@的用户ID
|
||||||
|
|
||||||
|
Args:
|
||||||
|
content: 消息内容
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
用户ID或None
|
||||||
|
"""
|
||||||
|
# 简化实现:查找@后的数字
|
||||||
|
# 实际WPS API可能有特定格式,需要根据文档调整
|
||||||
|
match = re.search(r'@.*?(\d+)', content)
|
||||||
|
if match:
|
||||||
|
try:
|
||||||
|
return int(match.group(1))
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _can_answer(self, state_data: Dict, user_id: int) -> Tuple[bool, str]:
|
||||||
|
"""检查用户是否可以接龙
|
||||||
|
|
||||||
|
Args:
|
||||||
|
state_data: 游戏状态数据
|
||||||
|
user_id: 用户ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(是否可以, 错误消息)
|
||||||
|
"""
|
||||||
|
# 不能是上一个接龙的人
|
||||||
|
if state_data.get('last_user_id') == user_id:
|
||||||
|
return False, "❌ 不能连续接龙哦!让其他人来吧"
|
||||||
|
|
||||||
|
# 如果指定了下一位,必须是指定的人
|
||||||
|
if state_data.get('next_user_id') is not None:
|
||||||
|
if state_data['next_user_id'] != user_id:
|
||||||
|
return False, f"❌ 现在轮到指定的人接龙了"
|
||||||
|
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
def _validate_idiom(self, idiom: str, state_data: Dict) -> Tuple[bool, str]:
|
||||||
|
"""验证词语有效性
|
||||||
|
|
||||||
|
Args:
|
||||||
|
idiom: 待验证的词语
|
||||||
|
state_data: 游戏状态数据
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
(是否有效, 错误消息)
|
||||||
|
"""
|
||||||
|
# 检查长度
|
||||||
|
if len(idiom) != 4:
|
||||||
|
return False, "❌ 词语必须是4个字"
|
||||||
|
|
||||||
|
# 检查是否已使用
|
||||||
|
if idiom in state_data.get('used_idioms', []):
|
||||||
|
return False, f"❌ 「{idiom}」已经用过了"
|
||||||
|
|
||||||
|
# 检查是否在全局黑名单
|
||||||
|
blacklist = self._load_blacklist()
|
||||||
|
if idiom in blacklist:
|
||||||
|
return False, f"❌ 「{idiom}」在黑名单中(永久禁用)"
|
||||||
|
|
||||||
|
# 检查拼音匹配
|
||||||
|
current_idiom = state_data.get('current_idiom', '')
|
||||||
|
if current_idiom:
|
||||||
|
last_char = current_idiom[-1]
|
||||||
|
first_char = idiom[0]
|
||||||
|
is_match, last_py, first_py = self._check_pinyin_match(last_char, first_char)
|
||||||
|
|
||||||
|
if not is_match:
|
||||||
|
return False, f"❌ 首字「{first_char}」拼音[{first_py}]不匹配上个成语尾字「{last_char}」拼音[{last_py}]"
|
||||||
|
|
||||||
|
return True, ""
|
||||||
|
|
||||||
|
def _start_game(self, chat_id: int, user_id: int, starter_idiom: Optional[str]) -> str:
|
||||||
|
"""开始新游戏
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id: 会话ID
|
||||||
|
user_id: 用户ID
|
||||||
|
starter_idiom: 起始成语(可选)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
提示消息
|
||||||
|
"""
|
||||||
|
# 检查是否已有进行中的游戏(user_id=0表示群级别状态)
|
||||||
|
state = self.db.get_game_state(chat_id, 0, 'idiom')
|
||||||
|
if state:
|
||||||
|
return "⚠️ 已经有一个进行中的游戏了!\n\n输入 `.idiom stop` 结束当前游戏"
|
||||||
|
|
||||||
|
# 确定起始成语
|
||||||
|
if starter_idiom:
|
||||||
|
# 验证起始成语
|
||||||
|
if len(starter_idiom) != 4:
|
||||||
|
return "❌ 起始成语必须是4个字"
|
||||||
|
idiom = starter_idiom
|
||||||
|
else:
|
||||||
|
# 随机选择
|
||||||
|
idiom = random.choice(self.starter_idioms)
|
||||||
|
|
||||||
|
# 获取最后一个字的拼音
|
||||||
|
last_char = idiom[-1]
|
||||||
|
last_pinyin_list = self._get_pinyin(last_char)
|
||||||
|
last_pinyin = last_pinyin_list[0] if last_pinyin_list else ''
|
||||||
|
|
||||||
|
# 创建游戏状态
|
||||||
|
state_data = {
|
||||||
|
'creator_id': user_id,
|
||||||
|
'current_idiom': idiom,
|
||||||
|
'current_pinyin_last': last_pinyin,
|
||||||
|
'last_user_id': user_id, # 发起人可以接第一个
|
||||||
|
'next_user_id': None,
|
||||||
|
'used_idioms': [idiom],
|
||||||
|
'chain_length': 1,
|
||||||
|
'participants': {},
|
||||||
|
'history': [
|
||||||
|
{
|
||||||
|
'user_id': user_id,
|
||||||
|
'idiom': idiom,
|
||||||
|
'timestamp': int(time.time())
|
||||||
|
}
|
||||||
|
],
|
||||||
|
'status': 'playing'
|
||||||
|
}
|
||||||
|
|
||||||
|
# 保存群状态(user_id=0)
|
||||||
|
self.db.save_game_state(chat_id, 0, 'idiom', state_data)
|
||||||
|
|
||||||
|
text = f"## 🀄 成语接龙开始!\n\n"
|
||||||
|
text += f"**起始成语**:{idiom} [{last_pinyin}]\n\n"
|
||||||
|
text += f"任何人都可以接龙,输入 `.idiom [成语]` 开始吧!\n\n"
|
||||||
|
text += f"💡 提示:可以用 `.idiom [成语] @某人` 指定下一位"
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
def _make_chain(self, chat_id: int, user_id: int, args: str) -> str:
|
||||||
|
"""玩家接龙
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id: 会话ID
|
||||||
|
user_id: 用户ID
|
||||||
|
args: 参数(成语 + 可能的@用户)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
结果消息
|
||||||
|
"""
|
||||||
|
# 获取群状态
|
||||||
|
state = self.db.get_game_state(chat_id, 0, 'idiom')
|
||||||
|
if not state:
|
||||||
|
return "⚠️ 还没有开始游戏呢!\n\n输入 `.idiom start` 开始游戏"
|
||||||
|
|
||||||
|
state_data = state['state_data']
|
||||||
|
|
||||||
|
# 检查用户权限
|
||||||
|
can_answer, error_msg = self._can_answer(state_data, user_id)
|
||||||
|
if not can_answer:
|
||||||
|
return error_msg
|
||||||
|
|
||||||
|
# 解析成语和@用户
|
||||||
|
# 提取成语(去除@部分)
|
||||||
|
idiom_match = re.match(r'^([^\s@]+)', args)
|
||||||
|
if not idiom_match:
|
||||||
|
return "❌ 请输入4个字的词语"
|
||||||
|
|
||||||
|
idiom = idiom_match.group(1).strip()
|
||||||
|
|
||||||
|
# 验证词语
|
||||||
|
is_valid, error_msg = self._validate_idiom(idiom, state_data)
|
||||||
|
if not is_valid:
|
||||||
|
return error_msg
|
||||||
|
|
||||||
|
# 解析@用户
|
||||||
|
mentioned_user_id = self._parse_mentioned_user(args)
|
||||||
|
|
||||||
|
# 获取拼音
|
||||||
|
last_char = idiom[-1]
|
||||||
|
last_pinyin_list = self._get_pinyin(last_char)
|
||||||
|
last_pinyin = last_pinyin_list[0] if last_pinyin_list else ''
|
||||||
|
|
||||||
|
# 更新状态
|
||||||
|
state_data['current_idiom'] = idiom
|
||||||
|
state_data['current_pinyin_last'] = last_pinyin
|
||||||
|
state_data['last_user_id'] = user_id
|
||||||
|
state_data['next_user_id'] = mentioned_user_id
|
||||||
|
state_data['used_idioms'].append(idiom)
|
||||||
|
state_data['chain_length'] += 1
|
||||||
|
|
||||||
|
# 更新参与者统计
|
||||||
|
if str(user_id) not in state_data['participants']:
|
||||||
|
state_data['participants'][str(user_id)] = 0
|
||||||
|
state_data['participants'][str(user_id)] += 1
|
||||||
|
|
||||||
|
# 记录历史
|
||||||
|
state_data['history'].append({
|
||||||
|
'user_id': user_id,
|
||||||
|
'idiom': idiom,
|
||||||
|
'timestamp': int(time.time())
|
||||||
|
})
|
||||||
|
|
||||||
|
# 保存状态
|
||||||
|
self.db.save_game_state(chat_id, 0, 'idiom', state_data)
|
||||||
|
|
||||||
|
# 构建回复
|
||||||
|
text = f"## ✅ 接龙成功!\n\n"
|
||||||
|
text += f"**{idiom}** [{last_pinyin}]\n\n"
|
||||||
|
text += f"**当前链长**:{state_data['chain_length']}\n\n"
|
||||||
|
|
||||||
|
user_count = state_data['participants'][str(user_id)]
|
||||||
|
text += f"@用户{user_id} 成功次数:{user_count}\n\n"
|
||||||
|
|
||||||
|
if mentioned_user_id:
|
||||||
|
text += f"已指定 @用户{mentioned_user_id} 接龙\n\n"
|
||||||
|
else:
|
||||||
|
text += "任何人都可以接龙\n\n"
|
||||||
|
|
||||||
|
text += "继续加油!💪"
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
def _set_next_user(self, chat_id: int, user_id: int, next_user_id: int) -> str:
|
||||||
|
"""指定下一位接龙者
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id: 会话ID
|
||||||
|
user_id: 当前用户ID
|
||||||
|
next_user_id: 指定的下一位用户ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
提示消息
|
||||||
|
"""
|
||||||
|
# 获取群状态
|
||||||
|
state = self.db.get_game_state(chat_id, 0, 'idiom')
|
||||||
|
if not state:
|
||||||
|
return "⚠️ 还没有开始游戏呢!"
|
||||||
|
|
||||||
|
state_data = state['state_data']
|
||||||
|
|
||||||
|
# 检查是否是最后接龙的人
|
||||||
|
if state_data.get('last_user_id') != user_id:
|
||||||
|
return "❌ 只有最后接龙成功的人可以指定下一位"
|
||||||
|
|
||||||
|
# 更新状态
|
||||||
|
state_data['next_user_id'] = next_user_id
|
||||||
|
self.db.save_game_state(chat_id, 0, 'idiom', state_data)
|
||||||
|
|
||||||
|
return f"✅ 已指定 @用户{next_user_id} 接龙"
|
||||||
|
|
||||||
|
def _reject_idiom(self, chat_id: int, user_id: int, idiom: str) -> str:
|
||||||
|
"""裁判拒绝词语
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id: 会话ID
|
||||||
|
user_id: 用户ID
|
||||||
|
idiom: 要拒绝的词语
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
提示消息
|
||||||
|
"""
|
||||||
|
# 获取群状态
|
||||||
|
state = self.db.get_game_state(chat_id, 0, 'idiom')
|
||||||
|
if not state:
|
||||||
|
return "⚠️ 还没有开始游戏呢!"
|
||||||
|
|
||||||
|
state_data = state['state_data']
|
||||||
|
|
||||||
|
# 检查权限(仅发起人)
|
||||||
|
if state_data.get('creator_id') != user_id:
|
||||||
|
return "❌ 只有游戏发起人可以执行裁判操作"
|
||||||
|
|
||||||
|
# 添加到全局黑名单
|
||||||
|
blacklist = self._load_blacklist()
|
||||||
|
if idiom not in blacklist:
|
||||||
|
blacklist.append(idiom)
|
||||||
|
self._save_blacklist()
|
||||||
|
logger.info(f"词语「{idiom}」已加入全局黑名单")
|
||||||
|
|
||||||
|
# 如果是最后一个成语,回退状态
|
||||||
|
if state_data.get('current_idiom') == idiom and len(state_data['history']) > 1:
|
||||||
|
# 移除最后一条历史
|
||||||
|
removed = state_data['history'].pop()
|
||||||
|
removed_user = str(removed['user_id'])
|
||||||
|
|
||||||
|
# 减少该用户的计数
|
||||||
|
if removed_user in state_data['participants']:
|
||||||
|
state_data['participants'][removed_user] -= 1
|
||||||
|
if state_data['participants'][removed_user] <= 0:
|
||||||
|
del state_data['participants'][removed_user]
|
||||||
|
|
||||||
|
# 恢复到上一个成语
|
||||||
|
if state_data['history']:
|
||||||
|
last_entry = state_data['history'][-1]
|
||||||
|
last_idiom = last_entry['idiom']
|
||||||
|
last_char = last_idiom[-1]
|
||||||
|
last_pinyin_list = self._get_pinyin(last_char)
|
||||||
|
|
||||||
|
state_data['current_idiom'] = last_idiom
|
||||||
|
state_data['current_pinyin_last'] = last_pinyin_list[0] if last_pinyin_list else ''
|
||||||
|
state_data['last_user_id'] = last_entry['user_id']
|
||||||
|
state_data['next_user_id'] = None
|
||||||
|
state_data['chain_length'] -= 1
|
||||||
|
|
||||||
|
# 从已使用列表中移除
|
||||||
|
if idiom in state_data['used_idioms']:
|
||||||
|
state_data['used_idioms'].remove(idiom)
|
||||||
|
|
||||||
|
# 保存状态
|
||||||
|
self.db.save_game_state(chat_id, 0, 'idiom', state_data)
|
||||||
|
|
||||||
|
text = f"✅ 已将「{idiom}」加入全局黑名单(永久禁用)"
|
||||||
|
if state_data.get('current_idiom') != idiom:
|
||||||
|
text += f"\n\n当前成语:{state_data['current_idiom']}"
|
||||||
|
else:
|
||||||
|
text += "\n\n游戏状态已回退"
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
def _show_status(self, chat_id: int) -> str:
|
||||||
|
"""显示游戏状态
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id: 会话ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
状态信息
|
||||||
|
"""
|
||||||
|
# 获取群状态
|
||||||
|
state = self.db.get_game_state(chat_id, 0, 'idiom')
|
||||||
|
if not state:
|
||||||
|
return "⚠️ 还没有开始游戏呢!\n\n输入 `.idiom start` 开始游戏"
|
||||||
|
|
||||||
|
state_data = state['state_data']
|
||||||
|
|
||||||
|
text = f"## 🀄 成语接龙状态\n\n"
|
||||||
|
text += f"**当前成语**:{state_data['current_idiom']} [{state_data['current_pinyin_last']}]\n\n"
|
||||||
|
text += f"**链长**:{state_data['chain_length']}\n\n"
|
||||||
|
|
||||||
|
# 下一位
|
||||||
|
if state_data.get('next_user_id'):
|
||||||
|
text += f"**下一位**:@用户{state_data['next_user_id']}\n\n"
|
||||||
|
else:
|
||||||
|
text += f"**下一位**:任何人都可以接龙\n\n"
|
||||||
|
|
||||||
|
# 参与者排行
|
||||||
|
if state_data['participants']:
|
||||||
|
text += f"### 🏆 参与者排行\n\n"
|
||||||
|
sorted_participants = sorted(
|
||||||
|
state_data['participants'].items(),
|
||||||
|
key=lambda x: x[1],
|
||||||
|
reverse=True
|
||||||
|
)
|
||||||
|
for idx, (uid, count) in enumerate(sorted_participants[:5], 1):
|
||||||
|
text += f"{idx}. @用户{uid} - {count}次\n"
|
||||||
|
text += "\n"
|
||||||
|
|
||||||
|
# 最近成语
|
||||||
|
history = state_data.get('history', [])
|
||||||
|
if history:
|
||||||
|
display_count = min(self.max_history_display, len(history))
|
||||||
|
recent = history[-display_count:]
|
||||||
|
text += f"### 📜 最近{display_count}个成语\n\n"
|
||||||
|
text += " → ".join([h['idiom'] for h in recent])
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
def _show_blacklist(self, chat_id: int) -> str:
|
||||||
|
"""显示全局黑名单
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id: 会话ID(保留参数以保持接口一致性)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
黑名单信息
|
||||||
|
"""
|
||||||
|
# 加载全局黑名单
|
||||||
|
blacklist = self._load_blacklist()
|
||||||
|
|
||||||
|
if not blacklist:
|
||||||
|
return "📋 全局黑名单为空\n\n💡 发起人可使用 `.idiom reject [词语]` 添加不合适的词语到黑名单"
|
||||||
|
|
||||||
|
text = f"## 📋 全局黑名单词语(永久禁用)\n\n"
|
||||||
|
text += f"**共 {len(blacklist)} 个词语**\n\n"
|
||||||
|
text += "、".join(blacklist)
|
||||||
|
text += "\n\n💡 这些词语在所有游戏中都不可使用"
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
def _stop_game(self, chat_id: int, user_id: int) -> str:
|
||||||
|
"""结束游戏
|
||||||
|
|
||||||
|
Args:
|
||||||
|
chat_id: 会话ID
|
||||||
|
user_id: 用户ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
总结消息
|
||||||
|
"""
|
||||||
|
# 获取群状态
|
||||||
|
state = self.db.get_game_state(chat_id, 0, 'idiom')
|
||||||
|
if not state:
|
||||||
|
return "⚠️ 还没有开始游戏呢!"
|
||||||
|
|
||||||
|
state_data = state['state_data']
|
||||||
|
|
||||||
|
# 构建总结
|
||||||
|
text = f"## 🎮 游戏结束!\n\n"
|
||||||
|
text += f"**总链长**:{state_data['chain_length']}\n\n"
|
||||||
|
text += f"**参与人数**:{len(state_data['participants'])}\n\n"
|
||||||
|
|
||||||
|
# 排行榜
|
||||||
|
if state_data['participants']:
|
||||||
|
text += f"### 🏆 排行榜\n\n"
|
||||||
|
sorted_participants = sorted(
|
||||||
|
state_data['participants'].items(),
|
||||||
|
key=lambda x: x[1],
|
||||||
|
reverse=True
|
||||||
|
)
|
||||||
|
for idx, (uid, count) in enumerate(sorted_participants, 1):
|
||||||
|
text += f"{idx}. @用户{uid} - {count}次\n"
|
||||||
|
# 更新统计
|
||||||
|
try:
|
||||||
|
for _ in range(count):
|
||||||
|
self.db.update_game_stats(int(uid), 'idiom', win=True)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f"更新统计失败: {e}")
|
||||||
|
text += "\n"
|
||||||
|
|
||||||
|
# 完整接龙
|
||||||
|
history = state_data.get('history', [])
|
||||||
|
if history:
|
||||||
|
text += f"### 📜 完整接龙\n\n"
|
||||||
|
idioms = [h['idiom'] for h in history]
|
||||||
|
text += " → ".join(idioms)
|
||||||
|
|
||||||
|
# 删除游戏状态
|
||||||
|
self.db.delete_game_state(chat_id, 0, 'idiom')
|
||||||
|
|
||||||
|
return text
|
||||||
|
|
||||||
|
def _format_history(self, history: list, count: int) -> str:
|
||||||
|
"""格式化历史记录
|
||||||
|
|
||||||
|
Args:
|
||||||
|
history: 历史记录列表
|
||||||
|
count: 显示数量
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
格式化的字符串
|
||||||
|
"""
|
||||||
|
if not history:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
display_count = min(count, len(history))
|
||||||
|
recent = history[-display_count:]
|
||||||
|
return " → ".join([h['idiom'] for h in recent])
|
||||||
|
|
||||||
|
def get_help(self) -> str:
|
||||||
|
"""获取帮助信息"""
|
||||||
|
return """## 🀄 成语接龙
|
||||||
|
|
||||||
|
### 基础用法
|
||||||
|
- `.idiom start [成语]` - 开始游戏(可指定起始成语)
|
||||||
|
- `.idiom [成语]` - 接龙
|
||||||
|
- `.idiom [成语] @某人` - 接龙并指定下一位
|
||||||
|
- `.idiom stop` - 结束游戏(任何人可执行)
|
||||||
|
|
||||||
|
### 其他指令
|
||||||
|
- `.idiom status` - 查看游戏状态
|
||||||
|
- `.idiom blacklist` - 查看黑名单
|
||||||
|
- `.idiom reject [词语]` - 裁判拒绝词语(仅发起人)
|
||||||
|
- `.idiom next @某人` - 指定下一位(仅最后接龙者)
|
||||||
|
|
||||||
|
### 游戏规则
|
||||||
|
- 词语必须是4个字
|
||||||
|
- 首字拼音必须匹配上个成语尾字拼音(忽略声调)
|
||||||
|
- 不能重复使用成语
|
||||||
|
- 不能连续接龙
|
||||||
|
- 黑名单词语不可使用
|
||||||
|
- 任何人都可以结束游戏
|
||||||
|
|
||||||
|
### 示例
|
||||||
|
```
|
||||||
|
.idiom start 一马当先 # 开始游戏
|
||||||
|
.idiom 先声夺人 # 接龙
|
||||||
|
.idiom 人山人海 @张三 # 接龙并指定下一位
|
||||||
|
.idiom reject 某词 # 发起人拒绝某词
|
||||||
|
.idiom stop # 结束游戏
|
||||||
|
```
|
||||||
|
|
||||||
|
💡 提示:支持多音字和谐音接龙
|
||||||
|
"""
|
||||||
|
|
||||||
@@ -15,5 +15,8 @@ pydantic-settings==2.1.0
|
|||||||
# 系统监控
|
# 系统监控
|
||||||
psutil==7.1.2
|
psutil==7.1.2
|
||||||
|
|
||||||
|
# 拼音处理
|
||||||
|
pypinyin==0.51.0
|
||||||
|
|
||||||
# 注意:使用Python标准库sqlite3,不引入SQLAlchemy
|
# 注意:使用Python标准库sqlite3,不引入SQLAlchemy
|
||||||
|
|
||||||
|
|||||||
@@ -146,6 +146,12 @@ async def handle_command(game_type: str, command: str,
|
|||||||
game = QuizGame()
|
game = QuizGame()
|
||||||
return await game.handle(command, chat_id, user_id)
|
return await game.handle(command, chat_id, user_id)
|
||||||
|
|
||||||
|
# 成语接龙
|
||||||
|
if game_type == 'idiom':
|
||||||
|
from games.idiom import IdiomGame
|
||||||
|
game = IdiomGame()
|
||||||
|
return await game.handle(command, chat_id, user_id)
|
||||||
|
|
||||||
# 未知游戏类型
|
# 未知游戏类型
|
||||||
logger.warning(f"未知游戏类型: {game_type}")
|
logger.warning(f"未知游戏类型: {game_type}")
|
||||||
return "❌ 未知的游戏类型"
|
return "❌ 未知的游戏类型"
|
||||||
|
|||||||
@@ -30,6 +30,11 @@ class CommandParser:
|
|||||||
'.quiz': 'quiz',
|
'.quiz': 'quiz',
|
||||||
'.问答': 'quiz',
|
'.问答': 'quiz',
|
||||||
|
|
||||||
|
# 成语接龙
|
||||||
|
'.idiom': 'idiom',
|
||||||
|
'.成语接龙': 'idiom',
|
||||||
|
'.成语': 'idiom',
|
||||||
|
|
||||||
# 帮助
|
# 帮助
|
||||||
'.help': 'help',
|
'.help': 'help',
|
||||||
'.帮助': 'help',
|
'.帮助': 'help',
|
||||||
|
|||||||
Reference in New Issue
Block a user