Compare commits
19 Commits
task/ai_ch
...
task/add_c
| Author | SHA1 | Date | |
|---|---|---|---|
| 3f3c21c3d6 | |||
| b07b044035 | |||
| 9e16774d2f | |||
| 17da2e6111 | |||
| 1f10799562 | |||
| cef684f64b | |||
| 7962852685 | |||
| cc97374e98 | |||
| a62d5d66b7 | |||
| 19cde88acf | |||
| 6f05ca98f1 | |||
| 5a8a4ac026 | |||
| 12aac846cc | |||
| 4bddd4339f | |||
| 927c16e1fc | |||
| 8ffd261bb0 | |||
| 3d4f754a0a | |||
| 0a60a7fc4b | |||
| 82dc616495 |
@@ -107,6 +107,39 @@ WPSBotGame/
|
||||
- 阻碍因素:无
|
||||
- 状态:成功
|
||||
|
||||
## 2025-10-30_00:00:00
|
||||
- 已修改:
|
||||
- games/adventure.py(新增放弃指令分支,新增 `_abandon_adventure`,更新帮助)
|
||||
- games/base.py(在全局帮助中添加放弃用法)
|
||||
- 更改:
|
||||
1. 新增冒险放弃功能:
|
||||
- 支持指令:`.adventure abandon`、`.adventure 放弃`
|
||||
- 结算规则:最低倍率 × 已冒险分钟(向下取整,至少1分钟)
|
||||
- 发放奖励后删除状态
|
||||
2. 帮助信息更新:
|
||||
- 本地帮助与全局帮助均加入放弃用法说明
|
||||
- 原因:允许用户在冒险过程中主动放弃并按最低倍率获得奖励
|
||||
- 阻碍因素:无
|
||||
- 状态:成功
|
||||
|
||||
## 2025-10-31_10:27:59
|
||||
- 已修改:
|
||||
- games/alchemy.py(修复冒险任务完成后自动删除状态导致奖励丢失的bug)
|
||||
- 更改:
|
||||
1. **Bug修复**:修复冒险任务完成后奖励丢失问题
|
||||
- **问题**:在 `_perform_alchemy` 中,当检测到冒险任务已完成时,代码会自动删除冒险状态(`self.db.delete_game_state`),但没有发放奖励,导致用户奖励丢失
|
||||
- **修复**:移除自动删除逻辑,改为提示用户先使用 `.adventure` 回收奖励
|
||||
- **修改前**:冒险完成后自动删除状态,允许炼金(导致奖励丢失)
|
||||
- **修改后**:冒险完成后提示用户先回收奖励,不允许炼金,确保奖励只能通过 `.adventure` 命令回收
|
||||
2. **行为变更**:
|
||||
- 冒险进行中:提示剩余时间,不允许炼金(保持不变)
|
||||
- 冒险已完成:提示先回收奖励,不允许炼金(修复后)
|
||||
- 用户使用 `.adventure`:发放奖励并删除状态(保持不变)
|
||||
- 状态已删除:可以正常炼金(保持不变)
|
||||
- 原因:修复冒险任务完成后自动删除状态导致奖励丢失的严重bug,确保用户必须先主动回收奖励才能继续其他操作
|
||||
- 阻碍因素:无
|
||||
- 状态:成功
|
||||
|
||||
# 详细实施记录
|
||||
|
||||
## 文件修改清单
|
||||
@@ -135,7 +168,7 @@ WPSBotGame/
|
||||
- 使用 `get_game_state(0, user_id, 'adventure')` 查询状态
|
||||
- 如果存在状态:
|
||||
* 计算剩余时间
|
||||
* 如果已完成:自动删除状态,允许继续
|
||||
* 如果已完成:提示用户先使用 `.adventure` 回收奖励,不允许炼金(2025-10-31修复:避免奖励丢失,移除自动删除逻辑)
|
||||
* 如果未完成:返回错误消息,显示剩余时间(X分Y秒)
|
||||
- 异常处理:捕获状态数据异常,自动清理损坏状态
|
||||
|
||||
@@ -191,7 +224,7 @@ state_data = {
|
||||
### 游戏互斥机制
|
||||
- 炼金前检查:查询冒险状态
|
||||
- 如果冒险进行中:返回错误,显示剩余时间
|
||||
- 如果冒险已完成:自动清理状态,允许炼金
|
||||
- 如果冒险已完成:提示用户先使用 `.adventure` 回收奖励,不允许炼金(修复后:确保奖励不会丢失)
|
||||
- 状态异常:自动清理,允许继续操作
|
||||
|
||||
# 最终审查
|
||||
@@ -205,6 +238,7 @@ state_data = {
|
||||
- ✅ 游戏互斥:冒险期间阻止炼金操作
|
||||
- ✅ 指令注册:`.adventure` 和 `.冒险` 指令正常工作
|
||||
- ✅ 帮助信息:显示在全局帮助中
|
||||
- ✅ 冒险放弃:`.adventure abandon` / `.adventure 放弃` 按最低倍率结算已冒险分钟并清理状态
|
||||
|
||||
## 代码质量
|
||||
- ✅ 所有语法检查通过
|
||||
|
||||
@@ -364,3 +364,65 @@ llama-index-llms-ollama>=0.1.0
|
||||
|
||||
## 实施与计划匹配度
|
||||
实施与计划完全匹配 ✅
|
||||
|
||||
|
||||
## 补充分析:Markdown 渲染与发送通道(2025-10-30)
|
||||
|
||||
### 现状观察
|
||||
|
||||
- `routers/callback.py` 中仅当 `response_text.startswith('#')` 时才通过 `send_markdown()` 发送,否则使用 `send_text()`。这意味着即使 AI 返回了合法的 Markdown,但不以 `#` 开头(例如代码块、列表、表格、普通段落等),也会被按纯文本通道发送,导致下游(WPS 侧)不进行 Markdown 渲染。
|
||||
- `games/ai_chat.py` 的 `_generate_response()` 直接返回 `str(response)`,未对内容类型进行标注或判定,上层仅依赖首字符为 `#` 的启发式判断来选择发送通道。
|
||||
- `utils/message.py` 已具备 `send_markdown()` 与 `send_text()` 两种发送方式,对应 `{"msgtype":"markdown"}` 与 `{"msgtype":"text"}` 消息结构;当前缺少自动识别 Markdown 的逻辑。
|
||||
|
||||
### 影响
|
||||
|
||||
- 当 AI 返回包含 Markdown 元素但非标题(不以 `#` 开头)的内容时,用户端看到的是未渲染的原始 Markdown 文本,表现为“格式不能成功排版”。
|
||||
|
||||
### 待确认问题(不含解决方案,需产品/实现口径)
|
||||
|
||||
1. 目标平台(WPS 机器人)对 Markdown 的要求是否仅需 `msgtype=markdown` 即可渲染?是否存在必须以标题开头的限制?
|
||||
2. 期望策略:
|
||||
- 是否希望“.ai 的所有回复”统一走 Markdown 通道?
|
||||
- 还是需要基于 Markdown 特征进行判定(如代码块、列表、链接、表格、行内格式等)?
|
||||
3. 兼容性:若统一改为 Markdown 通道,是否会影响既有纯文本展示(例如换行、转义、表情)?
|
||||
4. 其他指令模块是否也可能返回 Markdown?若有,是否一并纳入同一策略?
|
||||
|
||||
### 相关代码参照点(路径)
|
||||
|
||||
- `routers/callback.py`:回复通道选择逻辑(基于 `startswith('#')`)
|
||||
- `games/ai_chat.py`:AI 回复内容生成与返回(直接返回字符串)
|
||||
- `utils/message.py`:`send_markdown()` 与 `send_text()` 的消息结构
|
||||
|
||||
|
||||
### 决策结论与范围(2025-10-30)
|
||||
|
||||
- 分支策略:不创建新分支,继续在当前任务上下文内推进。
|
||||
- 发送策略:`.ai` 产生的回复统一按 Markdown 发送。
|
||||
- 影响范围:仅限 AI 对话功能(`.ai`/`ai_chat`),不扩展到其他指令模块。
|
||||
|
||||
|
||||
# 任务进度(补充)
|
||||
|
||||
## [2025-10-30_??:??:??] 标注 Markdown 渲染问题(记录现状与待确认项)
|
||||
- 已修改:
|
||||
- `.tasks/2025-10-29_3_ai_chat.md`:补充“Markdown 渲染与发送通道”分析与待确认清单(仅问题陈述,无解决方案)。
|
||||
- 更改:
|
||||
- 明确当前仅以标题开头触发 Markdown 发送的启发式导致部分 Markdown 未被渲染。
|
||||
- 原因:
|
||||
- 用户反馈“AI 返回内容支持 Markdown,但当前直接当作文本返回导致无法正确排版”。
|
||||
- 阻碍因素:
|
||||
- 目标平台的 Markdown 渲染细节与统一策略选择待确认。
|
||||
- 状态:
|
||||
- 未确认(等待策略口径与平台渲染规范确认)。
|
||||
|
||||
## [2025-10-30_11:40:31] 执行:AI 回复统一按 Markdown 发送(仅限 AI)
|
||||
- 已修改:
|
||||
- `routers/callback.py`:在 `callback_receive()` 的发送阶段,当 `game_type == 'ai_chat'` 且存在 `response_text` 时,无条件调用 `send_markdown(response_text)`;若发送异常,记录日志并回退到 `send_text(response_text)`;其他指令模块继续沿用 `startswith('#')` 的启发式逻辑。
|
||||
- 更改:
|
||||
- 使 `.ai` 产生的回复在 WPS 端稳定触发 Markdown 渲染,不再依赖以 `#` 开头。
|
||||
- 原因:
|
||||
- 对齐“统一按 Markdown 发送(仅限 AI)”的决策,解决 Markdown 文本被当作纯文本发送导致的排版问题。
|
||||
- 阻碍因素:
|
||||
- 暂无。
|
||||
- 状态:
|
||||
- 成功。
|
||||
506
.tasks/2025-10-30_1_add_casino_games.md
Normal file
506
.tasks/2025-10-30_1_add_casino_games.md
Normal file
@@ -0,0 +1,506 @@
|
||||
# 背景
|
||||
文件名:2025-10-30_1_add_casino_games.md
|
||||
创建于:2025-10-30_15:16:56
|
||||
创建者:admin
|
||||
主分支:main
|
||||
任务分支:task/add_casino_games_2025-01-14_1
|
||||
Yolo模式:Off
|
||||
|
||||
# 任务描述
|
||||
在项目中新增赌场常见游戏,支持多个玩家下注等功能,使用积分系统模拟真实的赌场环境。
|
||||
|
||||
要求:
|
||||
- 指令格式:`.赌场 <游戏类型> <参数>`
|
||||
- 采用模块化设计,便于扩展多种赌场游戏
|
||||
- 支持多人同时下注
|
||||
- 集成现有积分系统
|
||||
- 记录下注和收益数据
|
||||
|
||||
# 项目概览
|
||||
基于WPS协作开放平台的自定义机器人游戏系统。使用FastAPI + SQLite架构,已有完善的积分系统和多款游戏(五子棋、成语接龙等)。需要通过模块化设计添加赌场游戏功能。
|
||||
|
||||
# 分析
|
||||
|
||||
## 现有系统分析
|
||||
1. **积分系统**:已实现 `add_points()` 和 `consume_points()` 方法
|
||||
2. **游戏基类**:`BaseGame` 提供统一的接口
|
||||
3. **路由系统**:通过 `CommandParser` 解析指令,在 `callback.py` 中路由
|
||||
4. **数据库**:SQLite,已有用户表、游戏状态表、统计表
|
||||
|
||||
## 需要新增的内容
|
||||
1. **数据库表**:
|
||||
- `casino_bets`:记录所有下注
|
||||
- `casino_results`:记录游戏结果和结算
|
||||
- `casino_games`:记录游戏房间(可选)
|
||||
|
||||
2. **游戏模块**:
|
||||
- `games/casino.py`:主赌场模块
|
||||
- 第一期支持:大小游戏
|
||||
- 第二期计划:轮盘、二十一点等
|
||||
|
||||
3. **指令映射**:
|
||||
- `.赌场` -> casino 游戏类型
|
||||
- 子指令:`轮盘`、`大小`、`21点` 等
|
||||
|
||||
## 设计要点
|
||||
1. **模块化设计**:每种赌场游戏作为独立类
|
||||
2. **下注流程**:创建房间 -> 玩家下注 -> 结算 -> 分发奖励
|
||||
3. **安全性**:下注前检查积分,结算时原子性操作
|
||||
4. **多玩家支持**:以 chat_id 为单位创建游戏房间
|
||||
|
||||
# 提议的解决方案
|
||||
|
||||
## 指令设计
|
||||
采用 `.赌场 <游戏类型> <操作> <参数>` 的模块化结构:
|
||||
|
||||
### 大小游戏
|
||||
- **庄家开启游戏**:`.赌场 大小 open <最小下注> <最大下注> <赔率>`
|
||||
- 示例:`.赌场 大小 open 10 100 2.0` (最小10分,最大100分,赔率2.0倍)
|
||||
- **玩家下注**:`.赌场 大小 bet <大小/小> <下注金额>`
|
||||
- 示例:`.赌场 大小 bet 大 50` (下注50分压大)
|
||||
- 示例:`.赌场 大小 bet 小 30` (下注30分压小)
|
||||
- **查看状态**:`.赌场 大小 status`
|
||||
- **庄家结算**:`.赌场 大小 settle <结果>`
|
||||
- 示例:`.赌场 大小 settle 大` (开大)
|
||||
- 示例:`.赌场 大小 settle 小` (开小)
|
||||
|
||||
### 轮盘游戏(二期实现)
|
||||
- 暂不实现,等大小游戏完善后再扩展
|
||||
|
||||
### 21点游戏(二期实现)
|
||||
- 暂不实现,等大小游戏完善后再扩展
|
||||
|
||||
## 游戏流程
|
||||
1. 庄家开启游戏(指定下注限额和赔率参数)
|
||||
2. 玩家下注(可多人同时参与)
|
||||
3. 庄家确认结算(手动触发结果)
|
||||
4. 系统自动分发奖励
|
||||
|
||||
## 数据库设计
|
||||
|
||||
### 新增表:casino_bets(下注记录表)
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS casino_bets (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
chat_id INTEGER NOT NULL,
|
||||
game_type TEXT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
bet_type TEXT NOT NULL, -- '大' 或 '小'
|
||||
amount INTEGER NOT NULL,
|
||||
multiplier REAL NOT NULL, -- 赔率
|
||||
status TEXT DEFAULT 'pending', -- pending/settled/cancelled
|
||||
result TEXT, -- 游戏结果
|
||||
win_amount INTEGER, -- 赢得金额
|
||||
created_at INTEGER NOT NULL,
|
||||
settled_at INTEGER,
|
||||
FOREIGN KEY (user_id) REFERENCES users(user_id)
|
||||
)
|
||||
```
|
||||
|
||||
### 新增表:casino_sessions(游戏会话表)
|
||||
```sql
|
||||
CREATE TABLE IF NOT EXISTS casino_sessions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
chat_id INTEGER NOT NULL,
|
||||
game_type TEXT NOT NULL,
|
||||
banker_id INTEGER NOT NULL, -- 庄家ID
|
||||
min_bet INTEGER NOT NULL,
|
||||
max_bet INTEGER NOT NULL,
|
||||
multiplier REAL NOT NULL,
|
||||
house_fee REAL DEFAULT 0.05, -- 抽水率,默认5%
|
||||
status TEXT DEFAULT 'open', -- open/settling/closed
|
||||
created_at INTEGER NOT NULL,
|
||||
settled_at INTEGER,
|
||||
UNIQUE(chat_id, game_type, status)
|
||||
)
|
||||
```
|
||||
|
||||
### 索引
|
||||
- `casino_bets(chat_id, game_type, status)` - 快速查询待结算下注
|
||||
- `casino_sessions(chat_id, game_type, status)` - 快速查询活跃游戏
|
||||
|
||||
## 方案对比
|
||||
|
||||
### 方案A:纯数据库表方案(推荐)
|
||||
**优点**:
|
||||
- 数据结构清晰,便于查询统计
|
||||
- 支持历史记录追踪
|
||||
- 并发安全,利用数据库事务
|
||||
- 易于扩展复杂查询
|
||||
|
||||
**缺点**:
|
||||
- 需要维护额外的表结构
|
||||
- 稍微复杂一些
|
||||
|
||||
**决策**:采用此方案
|
||||
|
||||
### 方案B:game_states + JSON方案
|
||||
**优点**:
|
||||
- 复用现有系统
|
||||
- 实现简单
|
||||
|
||||
**缺点**:
|
||||
- 难以进行复杂统计查询
|
||||
- JSON解析性能较差
|
||||
- 数据格式不够规范化
|
||||
|
||||
## 核心实现细节
|
||||
|
||||
### 1. 游戏流程控制
|
||||
- **开启游戏**:检查是否已有活跃游戏,同一chat_id只能有一个进行中的游戏
|
||||
- **下注限制**:检查session状态、下注金额范围、玩家积分
|
||||
- **结算控制**:只有庄家可以结算,结算后自动关闭session
|
||||
|
||||
### 2. 下注流程
|
||||
1. 检查是否有活跃的session
|
||||
2. 检查下注金额是否符合min/max限制
|
||||
3. 检查用户积分是否充足
|
||||
4. 扣除下注金额(consume_points)
|
||||
5. 记录下注到casino_bets表
|
||||
|
||||
### 3. 结算流程
|
||||
1. 验证是否为庄家操作
|
||||
2. 查询所有pending状态的下注
|
||||
3. 计算每个玩家的输赢
|
||||
4. 使用数据库事务确保原子性:
|
||||
- 更新bets状态
|
||||
- 发放/扣除积分
|
||||
- 更新session状态
|
||||
5. 返回结算报告
|
||||
|
||||
### 4. 抽水机制
|
||||
- **抽水率**:5%(可配置,存储在session.house_fee中)
|
||||
- **抽水时机**:从玩家的赢得金额中扣除
|
||||
- **抽水归属**:归系统所有(不返还给庄家)
|
||||
- **计算方式**:
|
||||
- 玩家赢得 = 下注金额 × 赔率
|
||||
- 实际发放 = 赢得金额 × (1 - 抽水率)
|
||||
- 抽水金额 = 赢得金额 × 抽水率
|
||||
|
||||
### 5. 错误处理
|
||||
- 下注时积分不足:给出明确提示
|
||||
- 重复下注:允许(可下多注)
|
||||
- 非法下注金额:给出范围提示
|
||||
- 非庄家尝试结算:拒绝
|
||||
|
||||
## 安全性
|
||||
- 下注前检查积分
|
||||
- 结算时使用数据库事务保证原子性
|
||||
- 抽水机制保护庄家(虽然抽水归系统)
|
||||
- 验证庄家身份
|
||||
- 防止重复结算
|
||||
|
||||
# 详细实施计划
|
||||
|
||||
## 文件1: core/database.py
|
||||
|
||||
### 修改函数: init_tables()
|
||||
在现有表创建之后(约第130行),添加赌场相关表的创建:
|
||||
|
||||
位置:在 `user_points` 表创建之后(约第130行)添加
|
||||
|
||||
```python
|
||||
# 赌场下注记录表
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS casino_bets (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
chat_id INTEGER NOT NULL,
|
||||
game_type TEXT NOT NULL,
|
||||
user_id INTEGER NOT NULL,
|
||||
bet_type TEXT NOT NULL,
|
||||
amount INTEGER NOT NULL,
|
||||
multiplier REAL NOT NULL,
|
||||
status TEXT DEFAULT 'pending',
|
||||
result TEXT,
|
||||
win_amount INTEGER,
|
||||
created_at INTEGER NOT NULL,
|
||||
settled_at INTEGER,
|
||||
FOREIGN KEY (user_id) REFERENCES users (user_id)
|
||||
)
|
||||
""")
|
||||
|
||||
# 赌场游戏会话表
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS casino_sessions (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
chat_id INTEGER NOT NULL,
|
||||
game_type TEXT NOT NULL,
|
||||
banker_id INTEGER NOT NULL,
|
||||
min_bet INTEGER NOT NULL,
|
||||
max_bet INTEGER NOT NULL,
|
||||
multiplier REAL NOT NULL,
|
||||
house_fee REAL DEFAULT 0.05,
|
||||
status TEXT DEFAULT 'open',
|
||||
created_at INTEGER NOT NULL,
|
||||
settled_at INTEGER,
|
||||
UNIQUE(chat_id, game_type, status)
|
||||
)
|
||||
""")
|
||||
|
||||
# 创建索引
|
||||
cursor.execute("""
|
||||
CREATE INDEX IF NOT EXISTS idx_casino_bets
|
||||
ON casino_bets(chat_id, game_type, status)
|
||||
""")
|
||||
|
||||
cursor.execute("""
|
||||
CREATE INDEX IF NOT EXISTS idx_casino_sessions
|
||||
ON casino_sessions(chat_id, game_type, status)
|
||||
""")
|
||||
```
|
||||
|
||||
### 新增函数: create_casino_session()
|
||||
函数签名:
|
||||
```python
|
||||
def create_casino_session(self, chat_id: int, game_type: str, banker_id: int,
|
||||
min_bet: int, max_bet: int, multiplier: float,
|
||||
house_fee: float = 0.05) -> int:
|
||||
```
|
||||
|
||||
功能:创建新的赌场游戏会话,返回session_id
|
||||
|
||||
### 新增函数: get_active_casino_session()
|
||||
函数签名:
|
||||
```python
|
||||
def get_active_casino_session(self, chat_id: int, game_type: str) -> Optional[Dict]:
|
||||
```
|
||||
|
||||
功能:获取活跃的游戏会话
|
||||
|
||||
### 新增函数: create_casino_bet()
|
||||
函数签名:
|
||||
```python
|
||||
def create_casino_bet(self, chat_id: int, game_type: str, user_id: int,
|
||||
bet_type: str, amount: int, multiplier: float) -> int:
|
||||
```
|
||||
|
||||
功能:创建下注记录,返回bet_id
|
||||
|
||||
### 新增函数: get_pending_bets()
|
||||
函数签名:
|
||||
```python
|
||||
def get_pending_bets(self, chat_id: int, game_type: str) -> List[Dict]:
|
||||
```
|
||||
|
||||
功能:获取待结算的下注列表
|
||||
|
||||
### 新增函数: settle_casino_bets()
|
||||
函数签名:
|
||||
```python
|
||||
def settle_casino_bets(self, chat_id: int, game_type: str, result: str,
|
||||
banker_id: int) -> Dict:
|
||||
```
|
||||
|
||||
功能:结算所有下注,返回结算详情字典(winners, losers, total_win等)
|
||||
|
||||
### 新增函数: close_casino_session()
|
||||
函数签名:
|
||||
```python
|
||||
def close_casino_session(self, chat_id: int, game_type: str):
|
||||
```
|
||||
|
||||
功能:关闭游戏会话
|
||||
|
||||
## 文件2: games/casino.py(新建)
|
||||
|
||||
### 类: CasinoGame
|
||||
继承自 `BaseGame`
|
||||
|
||||
### 方法: __init__()
|
||||
初始化数据库连接
|
||||
|
||||
### 方法: async handle(command, chat_id, user_id) -> str
|
||||
主处理函数,解析指令并调用相应的处理方法
|
||||
|
||||
解析逻辑:
|
||||
- 提取命令参数,格式:`.赌场 <游戏类型> <操作> <参数>`
|
||||
- 识别游戏类型(第一期只支持"大小")
|
||||
- 分发到相应的处理方法
|
||||
|
||||
### 方法: async _handle_bigsmall(command, args, chat_id, user_id) -> str
|
||||
处理大小游戏的各种操作
|
||||
|
||||
支持的操作:
|
||||
- open: 开启游戏
|
||||
- bet: 下注
|
||||
- status: 查看状态
|
||||
- settle: 结算
|
||||
|
||||
### 方法: async _open_bigsmall(args, chat_id, user_id) -> str
|
||||
庄家开启大小游戏
|
||||
|
||||
参数解析:`<最小下注> <最大下注> <赔率>`
|
||||
参数验证和限制
|
||||
|
||||
### 方法: async _bet_bigsmall(args, chat_id, user_id) -> str
|
||||
玩家下注
|
||||
|
||||
参数解析:`<大小/小> <下注金额>`
|
||||
检查session、金额范围、用户积分
|
||||
|
||||
### 方法: async _status_bigsmall(chat_id, game_type) -> str
|
||||
查看当前游戏状态
|
||||
|
||||
### 方法: async _settle_bigsmall(args, chat_id, user_id) -> str
|
||||
庄家结算游戏
|
||||
|
||||
参数解析:`<大/小>`
|
||||
验证庄家身份,结算所有下注
|
||||
|
||||
### 方法: get_help() -> str
|
||||
返回帮助信息
|
||||
|
||||
## 文件3: utils/parser.py
|
||||
|
||||
### 修改: COMMAND_MAP
|
||||
添加赌场指令映射:
|
||||
|
||||
```python
|
||||
# 赌场系统
|
||||
'.赌场': 'casino',
|
||||
'.casino': 'casino',
|
||||
```
|
||||
|
||||
## 文件4: routers/callback.py
|
||||
|
||||
### 修改: async handle_command()
|
||||
在AI对话系统之后(约第209行)添加:
|
||||
|
||||
```python
|
||||
# 赌场系统
|
||||
if game_type == 'casino':
|
||||
from games.casino import CasinoGame
|
||||
game = CasinoGame()
|
||||
return await game.handle(command, chat_id, user_id)
|
||||
```
|
||||
|
||||
## 文件5: games/base.py
|
||||
|
||||
### 修改: get_help_message()
|
||||
在积分赠送系统之后添加赌场游戏帮助:
|
||||
|
||||
```python
|
||||
### 🎰 赌场系统
|
||||
- `.赌场 大小 open <最小> <最大> <赔率>` - 庄家开启大小游戏
|
||||
- `.赌场 大小 bet <大/小> <金额>` - 下注
|
||||
- `.赌场 大小 status` - 查看状态
|
||||
- `.赌场 大小 settle <大/小>` - 庄家结算
|
||||
```
|
||||
|
||||
## 实施清单
|
||||
|
||||
1. 修改 `core/database.py` 的 `init_tables()` 方法,添加赌场表创建和索引
|
||||
2. 在 `core/database.py` 中添加 `create_casino_session()` 方法
|
||||
3. 在 `core/database.py` 中添加 `get_active_casino_session()` 方法
|
||||
4. 在 `core/database.py` 中添加 `create_casino_bet()` 方法
|
||||
5. 在 `core/database.py` 中添加 `get_pending_bets()` 方法
|
||||
6. 在 `core/database.py` 中添加 `settle_casino_bets()` 方法
|
||||
7. 在 `core/database.py` 中添加 `close_casino_session()` 方法
|
||||
8. 创建文件 `games/casino.py`,定义 `CasinoGame` 类
|
||||
9. 在 `games/casino.py` 中实现 `__init__()` 方法
|
||||
10. 在 `games/casino.py` 中实现 `async handle()` 方法
|
||||
11. 在 `games/casino.py` 中实现 `async _handle_bigsmall()` 方法
|
||||
12. 在 `games/casino.py` 中实现 `async _open_bigsmall()` 方法
|
||||
13. 在 `games/casino.py` 中实现 `async _bet_bigsmall()` 方法
|
||||
14. 在 `games/casino.py` 中实现 `async _status_bigsmall()` 方法
|
||||
15. 在 `games/casino.py` 中实现 `async _settle_bigsmall()` 方法
|
||||
16. 在 `games/casino.py` 中实现 `get_help()` 方法
|
||||
17. 修改 `utils/parser.py`,在 COMMAND_MAP 中添加赌场指令映射
|
||||
18. 修改 `routers/callback.py`,在 `handle_command()` 中添加赌场路由
|
||||
19. 修改 `games/base.py`,在 `get_help_message()` 中添加赌场帮助信息
|
||||
20. 测试所有功能点,确保无错误
|
||||
|
||||
# 当前执行步骤:"2. 详细技术规划完成,等待进入实现阶段"
|
||||
|
||||
# 任务进度
|
||||
[2025-10-30_15:16:56]
|
||||
- 已修改:创建任务文件 `.tasks/2025-10-30_1_add_casino_games.md`
|
||||
- 更改:创建任务分支 `task/add_casino_games_2025-01-14_1` 和任务文件
|
||||
- 原因:按照RIPER-5协议建立工作基础
|
||||
- 阻碍因素:无
|
||||
- 状态:成功
|
||||
|
||||
[2025-10-30_16:30:00](预估时间)
|
||||
- 已修改:完成详细技术规划
|
||||
- 更改:设计数据库表结构、游戏流程、抽水机制等细节
|
||||
- 原因:为实施阶段提供详细技术规范
|
||||
- 阻碍因素:无
|
||||
- 状态:成功
|
||||
|
||||
[2025-10-30_16:07:57]
|
||||
- 已修改:core/database.py, games/casino.py, utils/parser.py, routers/callback.py, games/base.py
|
||||
- 更改:完成所有实施步骤1-19
|
||||
- 添加赌场表创建和索引
|
||||
- 实现6个数据库方法(create_casino_session, get_active_casino_session, create_casino_bet, get_pending_bets, settle_casino_bets, close_casino_session)
|
||||
- 创建完整的CasinoGame类,实现大小游戏所有功能
|
||||
- 注册指令映射和路由
|
||||
- 添加帮助信息
|
||||
- 原因:按照详细实施计划完成全部功能开发
|
||||
- 阻碍因素:无
|
||||
- 状态:成功
|
||||
|
||||
[2025-10-30_17:20:00](预估时间)
|
||||
- 已修改:games/casino.py
|
||||
- 更改:修改结算逻辑,从庄家指定结果改为系统随机生成
|
||||
- 移除庄家输入种子/结果的参数
|
||||
- 使用random.random()生成随机结果(50%大/50%小)
|
||||
- 更新帮助信息,settle命令不再需要参数
|
||||
- 原因:用户反馈庄家不应该能够操控游戏结果,庄家也是玩家
|
||||
- 阻碍因素:无
|
||||
- 状态:成功
|
||||
|
||||
[2025-10-30_17:26:19]
|
||||
- 已修改:games/casino.py, games/base.py
|
||||
- 更改:添加庄家放弃游戏功能
|
||||
- 新增_cancel_bigsmall()方法处理放弃逻辑
|
||||
- 放弃时返还所有玩家下注
|
||||
- 关闭会话并标记下注为cancelled
|
||||
- 添加cancel命令支持(cancel/放弃/关闭)
|
||||
- 更新帮助信息和base.py中的帮助
|
||||
- 原因:用户要求庄家可以放弃本轮游戏
|
||||
- 阻碍因素:无
|
||||
- 状态:成功
|
||||
|
||||
[2025-10-31_11:35:18]
|
||||
- 已修改:core/database.py
|
||||
- 更改:扩展数据库支持轮盘和21点游戏
|
||||
- 添加列存在性检查辅助方法(_column_exists, _add_column_if_not_exists)
|
||||
- 扩展casino_sessions表:添加current_phase和blackjack_multiplier字段(兼容性检查)
|
||||
- 扩展casino_bets表:添加bet_category、bet_number、bet_value、hand_status字段(兼容性检查)
|
||||
- 创建casino_blackjack_hands表:存储21点游戏手牌数据
|
||||
- 修改create_casino_session():支持单场限制检查(get_any_active_casino_session)和新字段
|
||||
- 扩展create_casino_bet():支持轮盘和21点专用字段参数
|
||||
- 添加21点手牌管理方法:create_blackjack_hand、get_blackjack_hand、update_blackjack_hand、get_all_blackjack_hands
|
||||
- 原因:为轮盘和21点游戏提供数据库支持,确保字段分离和向后兼容
|
||||
- 阻碍因素:无
|
||||
- 状态:成功
|
||||
|
||||
[2025-10-31_15:15:08]
|
||||
- 已修改:core/database.py
|
||||
- 更改:修复大小游戏结算时的UNIQUE约束冲突问题
|
||||
- 移除casino_sessions表的UNIQUE(chat_id, game_type, status)约束
|
||||
- 原因:status='closed'时需要允许多条历史记录,UNIQUE约束阻止了结算时更新status
|
||||
- 添加兼容性迁移逻辑:检测旧版本表结构,自动重建表以移除UNIQUE约束
|
||||
- 迁移时复制所有历史数据,处理外键关系(临时禁用/启用外键检查)
|
||||
- 单场限制通过应用层逻辑(get_any_active_casino_session)保证
|
||||
- 原因:用户测试大小游戏结算时遇到"UNIQUE constraint failed"错误
|
||||
- 阻碍因素:无
|
||||
- 状态:成功
|
||||
|
||||
[2025-10-31_15:15:08]
|
||||
- 已修改:core/database.py
|
||||
- 更改:修复21点游戏结算逻辑问题
|
||||
- 修正losers统计逻辑:将条件从`not player_is_busted and player_points != banker_points`改为`player_points != banker_points`
|
||||
- 原因:原条件排除了爆牌玩家,导致爆牌玩家未被统计到losers列表
|
||||
- 修正数据库更新逻辑:明确区分三种情况
|
||||
- 赢家:发放奖励并更新数据库
|
||||
- 平局(player_points == banker_points):已返还下注,更新数据库
|
||||
- 输家(else分支,包括爆牌和点数小于庄家):更新数据库
|
||||
- 改进结果字符串显示:包含玩家和庄家的状态信息(爆牌、黑杰克等)
|
||||
- 例如:"庄家19点 vs 玩家爆牌" 或 "庄家19点 vs 玩家20点(黑杰克)"
|
||||
- 原因:用户测试21点游戏时发现3人游戏中只有1个赢家被结算,1个爆牌玩家和1个平局玩家未被结算
|
||||
- 阻碍因素:无
|
||||
- 状态:成功
|
||||
|
||||
# 最终审查
|
||||
待完成
|
||||
986
core/database.py
986
core/database.py
File diff suppressed because it is too large
Load Diff
@@ -56,6 +56,10 @@ class AdventureGame(BaseGame):
|
||||
if args in ['help', '帮助', 'info']:
|
||||
return self._get_adventure_help()
|
||||
|
||||
# 放弃当前冒险(按最低倍率结算已冒险时间)
|
||||
if args in ['abandon', '放弃']:
|
||||
return await self._abandon_adventure(chat_id, user_id)
|
||||
|
||||
# 默认:冒险耗时1分钟
|
||||
else:
|
||||
# 解析消耗时间
|
||||
@@ -170,6 +174,69 @@ class AdventureGame(BaseGame):
|
||||
|
||||
return text
|
||||
|
||||
async def _abandon_adventure(self, chat_id: int, user_id: int) -> str:
|
||||
"""放弃当前冒险,按最低倍率结算已冒险时间
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID(使用0作为用户级标识)
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
放弃结果消息
|
||||
"""
|
||||
try:
|
||||
# 查询冒险状态
|
||||
state = self.db.get_game_state(0, user_id, 'adventure')
|
||||
if not state:
|
||||
return "❌ 当前没有进行中的冒险,可使用 `.adventure` 开始新的冒险。"
|
||||
|
||||
state_data = state.get('state_data', {})
|
||||
start_time = state_data.get('start_time')
|
||||
cost_time = state_data.get('cost_time')
|
||||
if start_time is None or cost_time is None:
|
||||
# 状态异常,清理并提示
|
||||
self.db.delete_game_state(0, user_id, 'adventure')
|
||||
return "⚠️ 冒险状态异常已清理,请使用 `.adventure` 重新开始。"
|
||||
|
||||
current_time = int(time.time())
|
||||
elapsed_seconds = max(0, current_time - int(start_time))
|
||||
elapsed_minutes = elapsed_seconds // 60
|
||||
if elapsed_minutes < 1:
|
||||
elapsed_minutes = 1
|
||||
|
||||
# 计算最低倍率
|
||||
try:
|
||||
min_multiplier = min(m for _, m, _ in self.prize_pool)
|
||||
except Exception:
|
||||
# 兜底:若奖池异常,按0.5处理
|
||||
min_multiplier = 0.5
|
||||
|
||||
reward_points = int(min_multiplier * elapsed_minutes)
|
||||
if reward_points < 0:
|
||||
reward_points = 0
|
||||
|
||||
# 发放奖励并清理状态
|
||||
if reward_points > 0:
|
||||
self.db.add_points(user_id, reward_points, "adventure", "冒险放弃奖励")
|
||||
self.db.delete_game_state(0, user_id, 'adventure')
|
||||
|
||||
# 查询当前积分
|
||||
updated_points = self.db.get_user_points(user_id)
|
||||
|
||||
# 输出
|
||||
text = f"## ⚡️ 冒险放弃\n\n"
|
||||
text += f"**已计入时间**: {elapsed_minutes} 分钟\n\n"
|
||||
text += f"**最低倍率**: {min_multiplier} 倍\n\n"
|
||||
text += f"**获得积分**: {reward_points} 分\n\n"
|
||||
text += f"**当前积分**: {updated_points['points']} 分\n\n"
|
||||
text += "---\n\n"
|
||||
text += "💡 提示:可随时使用 `.adventure` 再次踏上冒险之旅!"
|
||||
return text
|
||||
except Exception as e:
|
||||
logger.error(f"放弃冒险时出错: {e}", exc_info=True)
|
||||
# 失败时不影响原状态,返回提示
|
||||
return f"❌ 放弃冒险失败:{str(e)}"
|
||||
|
||||
def _draw_prize(self, prize_pool: list) -> dict:
|
||||
"""从奖品池中抽取奖品
|
||||
|
||||
@@ -209,6 +276,8 @@ class AdventureGame(BaseGame):
|
||||
text += f"- `.adventure time` - 消耗time分钟进行冒险, 最少一分钟\n"
|
||||
|
||||
text += f"### 其他功能\n"
|
||||
text += f"- `.adventure abandon` - 放弃当前冒险,按最低倍率结算已冒险时间\n"
|
||||
text += f"- `.adventure 放弃` - 放弃当前冒险,按最低倍率结算已冒险时间\n"
|
||||
text += f"- `.adventure help` - 查看帮助\n\n"
|
||||
|
||||
return text
|
||||
|
||||
@@ -92,9 +92,9 @@ class AlchemyGame(BaseGame):
|
||||
end_time = start_time + cost_time * 60
|
||||
remaining_seconds = end_time - current_time
|
||||
|
||||
# 如果冒险已完成,自动清理状态,允许炼金
|
||||
# 如果冒险已完成,提示用户先回收奖励,不允许炼金
|
||||
if remaining_seconds <= 0:
|
||||
self.db.delete_game_state(0, user_id, 'adventure')
|
||||
return f"❌ 你有待回收的冒险奖励!\n\n💡 请先使用 `.adventure` 回收冒险奖励后再进行炼金。"
|
||||
else:
|
||||
# 冒险未完成,返回错误提示
|
||||
remaining_minutes = remaining_seconds // 60
|
||||
|
||||
@@ -100,6 +100,8 @@ def get_help_message() -> str:
|
||||
- `.adventure` - 消耗1分钟进行冒险
|
||||
- `.冒险` - 消耗1分钟进行冒险
|
||||
- `.adventure 5` - 消耗5分钟进行冒险
|
||||
- `.adventure abandon` - 放弃当前冒险,按最低倍率结算已冒险时间
|
||||
- `.adventure 放弃` - 放弃当前冒险,按最低倍率结算已冒险时间
|
||||
- `.adventure help` - 查看冒险帮助
|
||||
|
||||
### 🎁 积分赠送系统
|
||||
@@ -111,6 +113,31 @@ def get_help_message() -> str:
|
||||
- `.ai <问题>` - 向AI提问(支持多用户对话,等待10秒后回答)
|
||||
- `.aiconfig host=xxx port=xxx model=xxx` - 配置Ollama服务地址和模型
|
||||
|
||||
### 🎰 赌场系统
|
||||
**大小游戏**:
|
||||
- `.赌场 大小 open <最小> <最大> <赔率>` - 庄家开启大小游戏
|
||||
- `.赌场 大小 bet <大/小> <金额>` - 下注
|
||||
- `.赌场 大小 status` - 查看状态
|
||||
- `.赌场 大小 settle` - 庄家结算(系统随机)
|
||||
- `.赌场 大小 cancel` - 庄家放弃游戏(返还下注)
|
||||
|
||||
**轮盘游戏**:
|
||||
- `.赌场 轮盘 open <最小> <最大>` - 庄家开启轮盘游戏
|
||||
- `.赌场 轮盘 bet <类型> <选项> <金额>` - 下注(数字/颜色/奇偶/大小/区间)
|
||||
- `.赌场 轮盘 status` - 查看状态
|
||||
- `.赌场 轮盘 settle` - 庄家结算(系统随机0-36)
|
||||
- `.赌场 轮盘 cancel` - 庄家放弃游戏(返还下注)
|
||||
|
||||
**21点游戏**:
|
||||
- `.赌场 21点 open <最小> <最大> [黑杰克倍数]` - 庄家开启21点游戏
|
||||
- `.赌场 21点 bet <金额>` - 下注
|
||||
- `.赌场 21点 deal` - 庄家发牌
|
||||
- `.赌场 21点 hit` - 玩家要牌
|
||||
- `.赌场 21点 stand` - 玩家停牌
|
||||
- `.赌场 21点 status` - 查看状态
|
||||
- `.赌场 21点 settle` - 庄家结算
|
||||
- `.赌场 21点 cancel` - 庄家放弃游戏(返还下注)
|
||||
|
||||
### 其他
|
||||
- `.help` - 显示帮助
|
||||
- `.stats` - 查看个人统计
|
||||
|
||||
1473
games/casino.py
Normal file
1473
games/casino.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -82,12 +82,10 @@ class GiftGame(BaseGame):
|
||||
receiver_id = user['user_id']
|
||||
|
||||
# 获取接收者名称用于显示
|
||||
receiver_user = self.db.get_or_create_user(receiver_id)
|
||||
receiver_name = receiver_user.get('username', f"用户{receiver_id}")
|
||||
receiver_name = self.db.get_user_display_name(receiver_id)
|
||||
|
||||
# 获取发送者名称用于显示
|
||||
sender_user = self.db.get_or_create_user(sender_id)
|
||||
sender_name = sender_user.get('username', f"用户{sender_id}")
|
||||
sender_name = self.db.get_user_display_name(sender_id)
|
||||
|
||||
# 验证参数
|
||||
if points <= 0:
|
||||
@@ -184,7 +182,7 @@ class GiftGame(BaseGame):
|
||||
|
||||
for record in records:
|
||||
timestamp = datetime.fromtimestamp(record['created_at']).strftime('%m-%d %H:%M')
|
||||
receiver_name = record.get('receiver_name', f"用户{record['receiver_id']}")
|
||||
receiver_name = self.db.get_user_display_name(record['receiver_id'])
|
||||
points = record['points']
|
||||
message = record.get('message', '')
|
||||
|
||||
@@ -214,7 +212,7 @@ class GiftGame(BaseGame):
|
||||
|
||||
for record in records:
|
||||
timestamp = datetime.fromtimestamp(record['created_at']).strftime('%m-%d %H:%M')
|
||||
sender_name = record.get('sender_name', f"用户{record['sender_id']}")
|
||||
sender_name = self.db.get_user_display_name(record['sender_id'])
|
||||
points = record['points']
|
||||
message = record.get('message', '')
|
||||
|
||||
|
||||
@@ -379,10 +379,12 @@ class IdiomGame(BaseGame):
|
||||
text += f"**当前链长**:{state_data['chain_length']}\n\n"
|
||||
|
||||
user_count = state_data['participants'][str(user_id)]
|
||||
text += f"@用户{user_id} 成功次数:{user_count}\n\n"
|
||||
user_display_name = self.db.get_user_display_name(user_id)
|
||||
text += f"@{user_display_name} 成功次数:{user_count}\n\n"
|
||||
|
||||
if mentioned_user_id:
|
||||
text += f"已指定 @用户{mentioned_user_id} 接龙\n\n"
|
||||
mentioned_display_name = self.db.get_user_display_name(mentioned_user_id)
|
||||
text += f"已指定 @{mentioned_display_name} 接龙\n\n"
|
||||
else:
|
||||
text += "任何人都可以接龙\n\n"
|
||||
|
||||
@@ -416,7 +418,8 @@ class IdiomGame(BaseGame):
|
||||
state_data['next_user_id'] = next_user_id
|
||||
self.db.save_game_state(chat_id, 0, 'idiom', state_data)
|
||||
|
||||
return f"✅ 已指定 @用户{next_user_id} 接龙"
|
||||
next_user_display_name = self.db.get_user_display_name(next_user_id)
|
||||
return f"✅ 已指定 @{next_user_display_name} 接龙"
|
||||
|
||||
def _reject_idiom(self, chat_id: int, user_id: int, idiom: str) -> str:
|
||||
"""裁判拒绝词语
|
||||
@@ -509,7 +512,8 @@ class IdiomGame(BaseGame):
|
||||
|
||||
# 下一位
|
||||
if state_data.get('next_user_id'):
|
||||
text += f"**下一位**:@用户{state_data['next_user_id']}\n\n"
|
||||
next_user_display_name = self.db.get_user_display_name(state_data['next_user_id'])
|
||||
text += f"**下一位**:@{next_user_display_name}\n\n"
|
||||
else:
|
||||
text += f"**下一位**:任何人都可以接龙\n\n"
|
||||
|
||||
@@ -522,7 +526,8 @@ class IdiomGame(BaseGame):
|
||||
reverse=True
|
||||
)
|
||||
for idx, (uid, count) in enumerate(sorted_participants[:5], 1):
|
||||
text += f"{idx}. @用户{uid} - {count}次\n"
|
||||
user_display_name = self.db.get_user_display_name(int(uid))
|
||||
text += f"{idx}. @{user_display_name} - {count}次\n"
|
||||
text += "\n"
|
||||
|
||||
# 最近成语
|
||||
@@ -588,7 +593,8 @@ class IdiomGame(BaseGame):
|
||||
reverse=True
|
||||
)
|
||||
for idx, (uid, count) in enumerate(sorted_participants, 1):
|
||||
text += f"{idx}. @用户{uid} - {count}次\n"
|
||||
user_display_name = self.db.get_user_display_name(int(uid))
|
||||
text += f"{idx}. @{user_display_name} - {count}次\n"
|
||||
# 更新统计
|
||||
try:
|
||||
for _ in range(count):
|
||||
|
||||
@@ -126,7 +126,7 @@ class PointsGame(BaseGame):
|
||||
for i, user in enumerate(leaderboard):
|
||||
rank = i + 1
|
||||
medal = medals[i] if i < len(medals) else "🏅"
|
||||
username = user.get('username', f"用户{user['user_id']}")
|
||||
username = self.db.get_user_display_name(user['user_id'])
|
||||
points = user.get('points', 0)
|
||||
|
||||
text += f"{medal} **第 {rank} 名** {username}\n"
|
||||
|
||||
@@ -76,14 +76,20 @@ async def callback_receive(request: Request):
|
||||
# 发送回复
|
||||
if response_text:
|
||||
sender = get_message_sender()
|
||||
|
||||
# 根据内容选择消息类型
|
||||
if response_text.startswith('#'):
|
||||
# Markdown格式
|
||||
await sender.send_markdown(response_text)
|
||||
|
||||
# AI 对话:统一按 Markdown 发送(按任务决策)
|
||||
if game_type == 'ai_chat':
|
||||
try:
|
||||
await sender.send_markdown(response_text)
|
||||
except Exception as send_md_err:
|
||||
logger.error(f"发送Markdown消息失败,改用文本发送: {send_md_err}")
|
||||
await sender.send_text(response_text)
|
||||
else:
|
||||
# 普通文本
|
||||
await sender.send_text(response_text)
|
||||
# 其他模块保持原有启发式:以 # 开头视为 Markdown,否则文本
|
||||
if response_text.startswith('#'):
|
||||
await sender.send_markdown(response_text)
|
||||
else:
|
||||
await sender.send_text(response_text)
|
||||
|
||||
return JSONResponse({"result": "ok"})
|
||||
|
||||
@@ -187,12 +193,27 @@ async def handle_command(game_type: str, command: str,
|
||||
game = GiftGame()
|
||||
return await game.handle(command, chat_id, user_id)
|
||||
|
||||
# 复述功能
|
||||
if game_type == 'say':
|
||||
# 提取参数并原样返回
|
||||
_, args = CommandParser.extract_command_args(command)
|
||||
args = args.strip()
|
||||
if not args:
|
||||
return "用法:.say 你想让我说的话\n别名:.说 / .复述"
|
||||
return args
|
||||
|
||||
# AI对话系统
|
||||
if game_type == 'ai_chat':
|
||||
from games.ai_chat import AIChatGame
|
||||
game = AIChatGame()
|
||||
return await game.handle(command, chat_id, user_id)
|
||||
|
||||
# 赌场系统
|
||||
if game_type == 'casino':
|
||||
from games.casino import CasinoGame
|
||||
game = CasinoGame()
|
||||
return await game.handle(command, chat_id, user_id)
|
||||
|
||||
# 未知游戏类型
|
||||
logger.warning(f"未知游戏类型: {game_type}")
|
||||
return "❌ 未知的游戏类型"
|
||||
|
||||
@@ -68,6 +68,11 @@ class CommandParser:
|
||||
'.ai': 'ai_chat',
|
||||
'.aiconfig': 'ai_chat',
|
||||
|
||||
# 复述
|
||||
'.say': 'say',
|
||||
'.说': 'say',
|
||||
'.复述': 'say',
|
||||
|
||||
# 帮助
|
||||
'.help': 'help',
|
||||
'.帮助': 'help',
|
||||
@@ -75,6 +80,10 @@ class CommandParser:
|
||||
# 统计
|
||||
'.stats': 'stats',
|
||||
'.统计': 'stats',
|
||||
|
||||
# 赌场系统
|
||||
'.赌场': 'casino',
|
||||
'.casino': 'casino',
|
||||
}
|
||||
|
||||
# 机器人名称模式(用于从@消息中提取)
|
||||
@@ -98,11 +107,20 @@ class CommandParser:
|
||||
if at_match:
|
||||
content = at_match.group(1).strip()
|
||||
|
||||
# 检查是否以指令开头
|
||||
for cmd_prefix, game_type in cls.COMMAND_MAP.items():
|
||||
if content == cmd_prefix:
|
||||
# 返回游戏类型和完整指令
|
||||
return game_type, content
|
||||
# 拦截全角空格与全角标点(不允许)
|
||||
# 范围包含:全角空格\u3000、全角标点\uFF01-\uFF60、兼容区\uFFE0-\uFFEE
|
||||
if re.search(r"[\u3000\uFF01-\uFF60\uFFE0-\uFFEE]", content):
|
||||
logger.debug(f"包含全角字符,忽略: {content}")
|
||||
return None
|
||||
|
||||
# 大小写不敏感匹配(仅用于匹配,不改变返回的原始内容)
|
||||
content_lower = content.lower()
|
||||
|
||||
# 使用最长前缀优先,避免 .r 误匹配 .roll 等更长前缀
|
||||
command_keys_sorted = sorted(cls.COMMAND_MAP.keys(), key=len, reverse=True)
|
||||
for cmd_prefix in command_keys_sorted:
|
||||
if content_lower.startswith(cmd_prefix.lower()):
|
||||
return cls.COMMAND_MAP[cmd_prefix], content
|
||||
|
||||
# 特殊处理:.ai 和 .aiconfig 指令支持参数
|
||||
if content.startswith('.ai '):
|
||||
|
||||
Reference in New Issue
Block a user