diff --git a/.tasks/2025-10-30_1_add_casino_games.md b/.tasks/2025-10-30_1_add_casino_games.md new file mode 100644 index 0000000..e0d91c2 --- /dev/null +++ b/.tasks/2025-10-30_1_add_casino_games.md @@ -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类,实现大小游戏所有功能 + - 注册指令映射和路由 + - 添加帮助信息 +- 原因:按照详细实施计划完成全部功能开发 +- 阻碍因素:无 +- 状态:成功 + +# 最终审查 +待完成 diff --git a/core/database.py b/core/database.py index cd9680c..14c4716 100644 --- a/core/database.py +++ b/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: diff --git a/games/base.py b/games/base.py index 9b4cde1..a75a4f6 100644 --- a/games/base.py +++ b/games/base.py @@ -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` - 查看个人统计 diff --git a/games/casino.py b/games/casino.py new file mode 100644 index 0000000..919fb83 --- /dev/null +++ b/games/casino.py @@ -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() + diff --git a/routers/callback.py b/routers/callback.py index ff5454c..4697038 100644 --- a/routers/callback.py +++ b/routers/callback.py @@ -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 "❌ 未知的游戏类型" diff --git a/utils/parser.py b/utils/parser.py index 15cc4b8..920055c 100644 --- a/utils/parser.py +++ b/utils/parser.py @@ -80,6 +80,10 @@ class CommandParser: # 统计 '.stats': 'stats', '.统计': 'stats', + + # 赌场系统 + '.赌场': 'casino', + '.casino': 'casino', } # 机器人名称模式(用于从@消息中提取)