新增赌场系统
This commit is contained in:
443
.tasks/2025-10-30_1_add_casino_games.md
Normal file
443
.tasks/2025-10-30_1_add_casino_games.md
Normal file
@@ -0,0 +1,443 @@
|
||||
# 背景
|
||||
文件名: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类,实现大小游戏所有功能
|
||||
- 注册指令映射和路由
|
||||
- 添加帮助信息
|
||||
- 原因:按照详细实施计划完成全部功能开发
|
||||
- 阻碍因素:无
|
||||
- 状态:成功
|
||||
|
||||
# 最终审查
|
||||
待完成
|
||||
267
core/database.py
267
core/database.py
@@ -129,6 +129,54 @@ class Database:
|
||||
)
|
||||
""")
|
||||
|
||||
# 赌场下注记录表
|
||||
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)
|
||||
""")
|
||||
|
||||
logger.info("数据库表初始化完成")
|
||||
|
||||
# ===== 用户相关操作 =====
|
||||
@@ -587,6 +635,225 @@ class Database:
|
||||
rows = cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
# ===== 赌场相关操作 =====
|
||||
|
||||
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:
|
||||
"""创建新的赌场游戏会话
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
game_type: 游戏类型
|
||||
banker_id: 庄家ID
|
||||
min_bet: 最小下注金额
|
||||
max_bet: 最大下注金额
|
||||
multiplier: 赔率
|
||||
house_fee: 抽水率
|
||||
|
||||
Returns:
|
||||
session_id
|
||||
"""
|
||||
cursor = self.conn.cursor()
|
||||
current_time = int(time.time())
|
||||
|
||||
# 检查是否已有活跃的会话
|
||||
cursor.execute("""
|
||||
SELECT id FROM casino_sessions
|
||||
WHERE chat_id = ? AND game_type = ? AND status = 'open'
|
||||
""", (chat_id, game_type))
|
||||
existing = cursor.fetchone()
|
||||
if existing:
|
||||
return existing['id']
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO casino_sessions
|
||||
(chat_id, game_type, banker_id, min_bet, max_bet, multiplier, house_fee, status, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, 'open', ?)
|
||||
""", (chat_id, game_type, banker_id, min_bet, max_bet, multiplier, house_fee, current_time))
|
||||
|
||||
return cursor.lastrowid
|
||||
|
||||
def get_active_casino_session(self, chat_id: int, game_type: str) -> Optional[Dict]:
|
||||
"""获取活跃的游戏会话
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
game_type: 游戏类型
|
||||
|
||||
Returns:
|
||||
会话信息字典或None
|
||||
"""
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT * FROM casino_sessions
|
||||
WHERE chat_id = ? AND game_type = ? AND status = 'open'
|
||||
ORDER BY id DESC LIMIT 1
|
||||
""", (chat_id, game_type))
|
||||
row = cursor.fetchone()
|
||||
|
||||
if row:
|
||||
return dict(row)
|
||||
return None
|
||||
|
||||
def create_casino_bet(self, chat_id: int, game_type: str, user_id: int,
|
||||
bet_type: str, amount: int, multiplier: float) -> int:
|
||||
"""创建下注记录
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
game_type: 游戏类型
|
||||
user_id: 用户ID
|
||||
bet_type: 下注类型
|
||||
amount: 下注金额
|
||||
multiplier: 赔率
|
||||
|
||||
Returns:
|
||||
bet_id
|
||||
"""
|
||||
cursor = self.conn.cursor()
|
||||
current_time = int(time.time())
|
||||
|
||||
cursor.execute("""
|
||||
INSERT INTO casino_bets
|
||||
(chat_id, game_type, user_id, bet_type, amount, multiplier, status, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'pending', ?)
|
||||
""", (chat_id, game_type, user_id, bet_type, amount, multiplier, current_time))
|
||||
|
||||
return cursor.lastrowid
|
||||
|
||||
def get_pending_bets(self, chat_id: int, game_type: str) -> List[Dict]:
|
||||
"""获取待结算的下注列表
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
game_type: 游戏类型
|
||||
|
||||
Returns:
|
||||
下注列表
|
||||
"""
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute("""
|
||||
SELECT * FROM casino_bets
|
||||
WHERE chat_id = ? AND game_type = ? AND status = 'pending'
|
||||
ORDER BY created_at ASC
|
||||
""", (chat_id, game_type))
|
||||
rows = cursor.fetchall()
|
||||
return [dict(row) for row in rows]
|
||||
|
||||
def settle_casino_bets(self, chat_id: int, game_type: str, result: str,
|
||||
banker_id: int) -> Dict:
|
||||
"""结算所有下注
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
game_type: 游戏类型
|
||||
result: 游戏结果('大' 或 '小')
|
||||
banker_id: 庄家ID
|
||||
|
||||
Returns:
|
||||
结算详情字典
|
||||
"""
|
||||
cursor = self.conn.cursor()
|
||||
current_time = int(time.time())
|
||||
|
||||
# 获取活跃会话
|
||||
session = self.get_active_casino_session(chat_id, game_type)
|
||||
if not session:
|
||||
raise ValueError("没有活跃的游戏会话")
|
||||
|
||||
if session['banker_id'] != banker_id:
|
||||
raise ValueError("只有庄家可以结算游戏")
|
||||
|
||||
# 获取所有待结算下注
|
||||
bets = self.get_pending_bets(chat_id, game_type)
|
||||
|
||||
winners = []
|
||||
losers = []
|
||||
total_win = 0
|
||||
|
||||
# 计算输赢
|
||||
for bet in bets:
|
||||
is_win = (bet['bet_type'] == result)
|
||||
if is_win:
|
||||
# 计算赢得金额
|
||||
win_amount = int(bet['amount'] * bet['multiplier'])
|
||||
# 扣除抽水
|
||||
house_fee = session['house_fee']
|
||||
actual_win = int(win_amount * (1 - house_fee))
|
||||
|
||||
winners.append({
|
||||
'user_id': bet['user_id'],
|
||||
'amount': bet['amount'],
|
||||
'win_amount': actual_win,
|
||||
'bet_id': bet['id']
|
||||
})
|
||||
total_win += actual_win
|
||||
else:
|
||||
losers.append({
|
||||
'user_id': bet['user_id'],
|
||||
'amount': bet['amount'],
|
||||
'bet_id': bet['id']
|
||||
})
|
||||
|
||||
# 使用事务确保原子性
|
||||
try:
|
||||
# 更新下注状态
|
||||
for bet in bets:
|
||||
is_win = (bet['bet_type'] == result)
|
||||
if is_win:
|
||||
win_amount = int(bet['amount'] * bet['multiplier'])
|
||||
actual_win = int(win_amount * (1 - session['house_fee']))
|
||||
|
||||
# 发放奖励
|
||||
self.add_points(bet['user_id'], actual_win, 'casino_win',
|
||||
f"赌场游戏{game_type}赢得")
|
||||
|
||||
cursor.execute("""
|
||||
UPDATE casino_bets
|
||||
SET status = 'settled', result = ?, win_amount = ?, settled_at = ?
|
||||
WHERE id = ?
|
||||
""", (result, actual_win, current_time, bet['id']))
|
||||
else:
|
||||
cursor.execute("""
|
||||
UPDATE casino_bets
|
||||
SET status = 'settled', result = ?, settled_at = ?
|
||||
WHERE id = ?
|
||||
""", (result, current_time, bet['id']))
|
||||
|
||||
# 关闭会话
|
||||
cursor.execute("""
|
||||
UPDATE casino_sessions
|
||||
SET status = 'closed', settled_at = ?
|
||||
WHERE id = ?
|
||||
""", (current_time, session['id']))
|
||||
|
||||
return {
|
||||
'winners': winners,
|
||||
'losers': losers,
|
||||
'total_win': total_win,
|
||||
'result': result
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"结算失败: {e}", exc_info=True)
|
||||
raise
|
||||
|
||||
def close_casino_session(self, chat_id: int, game_type: str):
|
||||
"""关闭游戏会话
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
game_type: 游戏类型
|
||||
"""
|
||||
cursor = self.conn.cursor()
|
||||
current_time = int(time.time())
|
||||
|
||||
cursor.execute("""
|
||||
UPDATE casino_sessions
|
||||
SET status = 'closed', settled_at = ?
|
||||
WHERE chat_id = ? AND game_type = ? AND status = 'open'
|
||||
""", (current_time, chat_id, game_type))
|
||||
|
||||
def close(self):
|
||||
"""关闭数据库连接"""
|
||||
if self._conn:
|
||||
|
||||
@@ -113,6 +113,12 @@ def get_help_message() -> str:
|
||||
- `.ai <问题>` - 向AI提问(支持多用户对话,等待10秒后回答)
|
||||
- `.aiconfig host=xxx port=xxx model=xxx` - 配置Ollama服务地址和模型
|
||||
|
||||
### 🎰 赌场系统
|
||||
- `.赌场 大小 open <最小> <最大> <赔率>` - 庄家开启大小游戏
|
||||
- `.赌场 大小 bet <大/小> <金额>` - 下注
|
||||
- `.赌场 大小 status` - 查看状态
|
||||
- `.赌场 大小 settle <大/小>` - 庄家结算
|
||||
|
||||
### 其他
|
||||
- `.help` - 显示帮助
|
||||
- `.stats` - 查看个人统计
|
||||
|
||||
346
games/casino.py
Normal file
346
games/casino.py
Normal file
@@ -0,0 +1,346 @@
|
||||
"""赌场游戏模块"""
|
||||
import logging
|
||||
from games.base import BaseGame
|
||||
from utils.parser import CommandParser
|
||||
from core.database import get_db
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CasinoGame(BaseGame):
|
||||
"""赌场游戏"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化游戏"""
|
||||
super().__init__()
|
||||
self.db = get_db()
|
||||
|
||||
async def handle(self, command: str, chat_id: int, user_id: int) -> str:
|
||||
"""处理赌场指令
|
||||
|
||||
Args:
|
||||
command: 完整指令
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
回复消息
|
||||
"""
|
||||
try:
|
||||
# 提取参数
|
||||
_, args = CommandParser.extract_command_args(command)
|
||||
args = args.strip().lower()
|
||||
|
||||
# 没有参数,显示帮助
|
||||
if not args:
|
||||
return self.get_help()
|
||||
|
||||
# 解析第一个参数为游戏类型
|
||||
parts = args.split(maxsplit=1)
|
||||
game_type = parts[0]
|
||||
sub_args = parts[1] if len(parts) > 1 else ""
|
||||
|
||||
# 根据游戏类型分发
|
||||
if game_type == '大小':
|
||||
return await self._handle_bigsmall(sub_args, chat_id, user_id)
|
||||
elif game_type in ['help', '帮助']:
|
||||
return self.get_help()
|
||||
else:
|
||||
return f"❌ 暂不支持的游戏类型: {game_type}\n\n支持的类型:大小"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理赌场指令错误: {e}", exc_info=True)
|
||||
return f"❌ 处理指令出错: {str(e)}"
|
||||
|
||||
async def _handle_bigsmall(self, args: str, chat_id: int, user_id: int) -> str:
|
||||
"""处理大小游戏
|
||||
|
||||
Args:
|
||||
args: 子命令和参数
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
回复消息
|
||||
"""
|
||||
if not args:
|
||||
return self._get_bigsmall_help()
|
||||
|
||||
# 解析子命令
|
||||
parts = args.split(maxsplit=1)
|
||||
action = parts[0].lower()
|
||||
sub_args = parts[1] if len(parts) > 1 else ""
|
||||
|
||||
if action in ['open', '开启', '开始']:
|
||||
return await self._open_bigsmall(sub_args, chat_id, user_id)
|
||||
elif action in ['bet', '下注', '押']:
|
||||
return await self._bet_bigsmall(sub_args, chat_id, user_id)
|
||||
elif action in ['status', '状态', '查看']:
|
||||
return await self._status_bigsmall(chat_id)
|
||||
elif action in ['settle', '结算', '开奖']:
|
||||
return await self._settle_bigsmall(sub_args, chat_id, user_id)
|
||||
elif action in ['help', '帮助']:
|
||||
return self._get_bigsmall_help()
|
||||
else:
|
||||
return f"❌ 未知命令: {action}\n\n{self._get_bigsmall_help()}"
|
||||
|
||||
async def _open_bigsmall(self, args: str, chat_id: int, user_id: int) -> str:
|
||||
"""庄家开启大小游戏
|
||||
|
||||
Args:
|
||||
args: 参数字符串 "<最小下注> <最大下注> <赔率>"
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
回复消息
|
||||
"""
|
||||
try:
|
||||
# 解析参数
|
||||
parts = args.split()
|
||||
if len(parts) != 3:
|
||||
return "❌ 参数格式错误!\n\n正确格式:`.赌场 大小 open <最小下注> <最大下注> <赔率>`\n\n示例:`.赌场 大小 open 10 100 2.0`"
|
||||
|
||||
try:
|
||||
min_bet = int(parts[0])
|
||||
max_bet = int(parts[1])
|
||||
multiplier = float(parts[2])
|
||||
except ValueError:
|
||||
return "❌ 参数必须是数字!"
|
||||
|
||||
# 参数验证
|
||||
if min_bet <= 0 or max_bet <= 0:
|
||||
return "❌ 下注金额必须大于0!"
|
||||
|
||||
if min_bet > max_bet:
|
||||
return "❌ 最小下注不能大于最大下注!"
|
||||
|
||||
if multiplier <= 0:
|
||||
return "❌ 赔率必须大于0!"
|
||||
|
||||
if max_bet > 10000:
|
||||
return "❌ 最大下注不能超过10000分!"
|
||||
|
||||
# 检查是否已有活跃游戏
|
||||
existing = self.db.get_active_casino_session(chat_id, '大小')
|
||||
if existing:
|
||||
return "⚠️ 当前已有进行中的大小游戏,请先结算后再开启新游戏。"
|
||||
|
||||
# 创建游戏会话
|
||||
session_id = self.db.create_casino_session(
|
||||
chat_id=chat_id,
|
||||
game_type='大小',
|
||||
banker_id=user_id,
|
||||
min_bet=min_bet,
|
||||
max_bet=max_bet,
|
||||
multiplier=multiplier,
|
||||
house_fee=0.05 # 固定5%抽水
|
||||
)
|
||||
|
||||
text = f"## 🎰 大小游戏已开启\n\n"
|
||||
text += f"**庄家**:用户{user_id}\n\n"
|
||||
text += f"**最小下注**:{min_bet} 分\n\n"
|
||||
text += f"**最大下注**:{max_bet} 分\n\n"
|
||||
text += f"**赔率**:{multiplier} 倍\n\n"
|
||||
text += f"**抽水率**:5%\n\n"
|
||||
text += "---\n\n"
|
||||
text += "💡 提示:玩家可以使用 `.赌场 大小 bet <大/小> <金额>` 进行下注"
|
||||
|
||||
return text
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"开启游戏失败: {e}", exc_info=True)
|
||||
return f"❌ 开启游戏失败: {str(e)}"
|
||||
|
||||
async def _bet_bigsmall(self, args: str, chat_id: int, user_id: int) -> str:
|
||||
"""玩家下注
|
||||
|
||||
Args:
|
||||
args: 参数字符串 "<大/小> <下注金额>"
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
回复消息
|
||||
"""
|
||||
try:
|
||||
# 解析参数
|
||||
parts = args.split()
|
||||
if len(parts) != 2:
|
||||
return "❌ 参数格式错误!\n\n正确格式:`.赌场 大小 bet <大/小> <下注金额>`\n\n示例:`.赌场 大小 bet 大 50`"
|
||||
|
||||
bet_type = parts[0]
|
||||
try:
|
||||
amount = int(parts[1])
|
||||
except ValueError:
|
||||
return "❌ 下注金额必须是数字!"
|
||||
|
||||
# 验证下注类型
|
||||
if bet_type not in ['大', '小']:
|
||||
return f"❌ 下注类型错误!只支持'大'或'小',您输入的是:{bet_type}"
|
||||
|
||||
# 检查是否有活跃的会话
|
||||
session = self.db.get_active_casino_session(chat_id, '大小')
|
||||
if not session:
|
||||
return "❌ 当前没有进行中的游戏,请等待庄家开启游戏。"
|
||||
|
||||
# 验证下注金额
|
||||
if amount < session['min_bet']:
|
||||
return f"❌ 下注金额太小!最小下注:{session['min_bet']} 分"
|
||||
|
||||
if amount > session['max_bet']:
|
||||
return f"❌ 下注金额太大!最大下注:{session['max_bet']} 分"
|
||||
|
||||
# 检查用户积分
|
||||
user_points = self.db.get_user_points(user_id)
|
||||
if user_points['points'] < amount:
|
||||
return f"❌ 积分不足!需要 {amount} 分,当前可用 {user_points['points']} 分"
|
||||
|
||||
# 扣除积分
|
||||
if not self.db.consume_points(user_id, amount, 'casino_bet', f"大小游戏下注{bet_type}"):
|
||||
return "❌ 扣除积分失败!"
|
||||
|
||||
# 记录下注
|
||||
bet_id = self.db.create_casino_bet(
|
||||
chat_id=chat_id,
|
||||
game_type='大小',
|
||||
user_id=user_id,
|
||||
bet_type=bet_type,
|
||||
amount=amount,
|
||||
multiplier=session['multiplier']
|
||||
)
|
||||
|
||||
# 获取更新后的积分
|
||||
updated_points = self.db.get_user_points(user_id)
|
||||
|
||||
text = f"## 🎲 下注成功\n\n"
|
||||
text += f"**下注类型**:{bet_type}\n\n"
|
||||
text += f"**下注金额**:{amount} 分\n\n"
|
||||
text += f"**赔率**:{session['multiplier']} 倍\n\n"
|
||||
text += f"**剩余积分**:{updated_points['points']} 分\n\n"
|
||||
text += "---\n\n"
|
||||
text += "💡 等待庄家结算结果..."
|
||||
|
||||
return text
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"下注失败: {e}", exc_info=True)
|
||||
return f"❌ 下注失败: {str(e)}"
|
||||
|
||||
async def _status_bigsmall(self, chat_id: int) -> str:
|
||||
"""查看当前游戏状态
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
|
||||
Returns:
|
||||
状态消息
|
||||
"""
|
||||
session = self.db.get_active_casino_session(chat_id, '大小')
|
||||
if not session:
|
||||
return "❌ 当前没有进行中的大小游戏"
|
||||
|
||||
# 获取所有待结算下注
|
||||
bets = self.db.get_pending_bets(chat_id, '大小')
|
||||
|
||||
# 统计下注情况
|
||||
bet_big = sum(b['amount'] for b in bets if b['bet_type'] == '大')
|
||||
bet_small = sum(b['amount'] for b in bets if b['bet_type'] == '小')
|
||||
|
||||
text = f"## 🎰 大小游戏状态\n\n"
|
||||
text += f"**庄家**:用户{session['banker_id']}\n\n"
|
||||
text += f"**最小下注**:{session['min_bet']} 分\n\n"
|
||||
text += f"**最大下注**:{session['max_bet']} 分\n\n"
|
||||
text += f"**赔率**:{session['multiplier']} 倍\n\n"
|
||||
text += "---\n\n"
|
||||
text += f"**当前下注统计**:\n"
|
||||
text += f"- 压大:{bet_big} 分({len([b for b in bets if b['bet_type'] == '大'])}注)\n"
|
||||
text += f"- 压小:{bet_small} 分({len([b for b in bets if b['bet_type'] == '小'])}注)\n"
|
||||
text += f"- 总下注:{len(bets)} 注\n\n"
|
||||
text += "---\n\n"
|
||||
text += "💡 庄家可以使用 `.赌场 大小 settle <大/小>` 结算"
|
||||
|
||||
return text
|
||||
|
||||
async def _settle_bigsmall(self, args: str, chat_id: int, user_id: int) -> str:
|
||||
"""庄家结算游戏
|
||||
|
||||
Args:
|
||||
args: 结果 "<大/小>"
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
结算结果消息
|
||||
"""
|
||||
try:
|
||||
if not args:
|
||||
return "❌ 请指定结算结果!\n\n正确格式:`.赌场 大小 settle <大/小>`"
|
||||
|
||||
result = args.strip()
|
||||
if result not in ['大', '小']:
|
||||
return f"❌ 结果类型错误!只支持'大'或'小',您输入的是:{result}"
|
||||
|
||||
# 结算
|
||||
settlement = self.db.settle_casino_bets(chat_id, '大小', result, user_id)
|
||||
|
||||
# 构建结算报告
|
||||
text = f"## 🎰 大小游戏结算\n\n"
|
||||
text += f"**结算结果**:{result}\n\n"
|
||||
text += "---\n\n"
|
||||
text += f"**赢家**:{len(settlement['winners'])} 人\n"
|
||||
text += f"**输家**:{len(settlement['losers'])} 人\n\n"
|
||||
|
||||
# 显示赢家详情
|
||||
if settlement['winners']:
|
||||
text += "**赢家明细**:\n"
|
||||
for winner in settlement['winners']:
|
||||
text += f"- 用户{winner['user_id']}: 下注{winner['amount']}分,赢得{winner['win_amount']}分\n"
|
||||
text += "\n"
|
||||
|
||||
# 显示输家详情
|
||||
if settlement['losers']:
|
||||
text += "**输家明细**:\n"
|
||||
for loser in settlement['losers']:
|
||||
text += f"- 用户{loser['user_id']}: 下注{loser['amount']}分\n"
|
||||
text += "\n"
|
||||
|
||||
text += "---\n\n"
|
||||
text += "✅ 游戏已结束,欢迎再次游戏!"
|
||||
|
||||
return text
|
||||
|
||||
except ValueError as e:
|
||||
return f"❌ {str(e)}"
|
||||
except Exception as e:
|
||||
logger.error(f"结算失败: {e}", exc_info=True)
|
||||
return f"❌ 结算失败: {str(e)}"
|
||||
|
||||
def _get_bigsmall_help(self) -> str:
|
||||
"""获取大小游戏帮助信息"""
|
||||
return """## 🎰 大小游戏帮助
|
||||
|
||||
### 庄家命令
|
||||
- `.赌场 大小 open <最小> <最大> <赔率>` - 开启游戏
|
||||
- 示例:`.赌场 大小 open 10 100 2.0`
|
||||
|
||||
### 玩家命令
|
||||
- `.赌场 大小 bet <大/小> <金额>` - 下注
|
||||
- 示例:`.赌场 大小 bet 大 50`
|
||||
- 示例:`.赌场 大小 bet 小 30`
|
||||
|
||||
### 通用命令
|
||||
- `.赌场 大小 status` - 查看当前状态
|
||||
- `.赌场 大小 settle <大/小>` - 庄家结算(仅庄家)
|
||||
|
||||
### 游戏规则
|
||||
- 玩家下注后积分立即扣除
|
||||
- 结算后根据结果发放奖励
|
||||
- 系统抽水5%
|
||||
- 赔率由庄家设置
|
||||
"""
|
||||
|
||||
def get_help(self) -> str:
|
||||
"""获取帮助信息"""
|
||||
return self._get_bigsmall_help()
|
||||
|
||||
@@ -208,6 +208,12 @@ async def handle_command(game_type: str, command: str,
|
||||
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 "❌ 未知的游戏类型"
|
||||
|
||||
@@ -80,6 +80,10 @@ class CommandParser:
|
||||
# 统计
|
||||
'.stats': 'stats',
|
||||
'.统计': 'stats',
|
||||
|
||||
# 赌场系统
|
||||
'.赌场': 'casino',
|
||||
'.casino': 'casino',
|
||||
}
|
||||
|
||||
# 机器人名称模式(用于从@消息中提取)
|
||||
|
||||
Reference in New Issue
Block a user