From cef684f64b9474f77ba4f01b605956b23c3de012 Mon Sep 17 00:00:00 2001 From: ninemine <1371605831@qq.com> Date: Fri, 31 Oct 2025 11:58:35 +0800 Subject: [PATCH] Save --- .tasks/2025-10-30_1_add_casino_games.md | 14 + core/database.py | 359 +++++++++++++++++++++++- games/casino.py | 208 +++++++++++++- 3 files changed, 573 insertions(+), 8 deletions(-) diff --git a/.tasks/2025-10-30_1_add_casino_games.md b/.tasks/2025-10-30_1_add_casino_games.md index 216f9e2..d43f555 100644 --- a/.tasks/2025-10-30_1_add_casino_games.md +++ b/.tasks/2025-10-30_1_add_casino_games.md @@ -461,5 +461,19 @@ if game_type == 'casino': - 阻碍因素:无 - 状态:成功 +[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点游戏提供数据库支持,确保字段分离和向后兼容 +- 阻碍因素:无 +- 状态:成功 + # 最终审查 待完成 diff --git a/core/database.py b/core/database.py index ee6187d..0ffdd0b 100644 --- a/core/database.py +++ b/core/database.py @@ -859,13 +859,39 @@ class Database: return [dict(row) for row in rows] def settle_casino_bets(self, chat_id: int, game_type: str, result: str, - banker_id: int) -> Dict: - """结算所有下注 + banker_id: int, **kwargs) -> Dict: + """结算所有下注(根据游戏类型分发到不同结算方法) Args: chat_id: 会话ID game_type: 游戏类型 - result: 游戏结果('大' 或 '小') + result: 游戏结果(大小游戏:"大"/"小";轮盘:数字字符串;21点:特殊格式) + banker_id: 庄家ID + **kwargs: 其他参数(轮盘:result_number;21点:hands_dict等) + + Returns: + 结算详情字典 + """ + if game_type == '大小': + return self._settle_bigsmall_bets(chat_id, game_type, result, banker_id) + elif game_type == '轮盘': + result_number = kwargs.get('result_number', int(result) if result.isdigit() else 0) + return self._settle_roulette_bets(chat_id, game_type, result_number, banker_id) + elif game_type == '21点': + hands_dict = kwargs.get('hands_dict', {}) + return self._settle_blackjack_bets(chat_id, game_type, hands_dict, banker_id) + else: + # 兼容旧的大小游戏逻辑 + return self._settle_bigsmall_bets(chat_id, game_type, result, banker_id) + + def _settle_bigsmall_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: @@ -955,6 +981,333 @@ class Database: logger.error(f"结算失败: {e}", exc_info=True) raise + def _settle_roulette_bets(self, chat_id: int, game_type: str, result_number: int, + banker_id: int) -> Dict: + """结算轮盘游戏下注 + + Args: + chat_id: 会话ID + game_type: 游戏类型 + result_number: 结果数字(0-36) + 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 + + # 轮盘数字对应的颜色(欧式轮盘,0为绿色) + roulette_red = {1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36} + result_color = '绿色' if result_number == 0 else ('红色' if result_number in roulette_red else '黑色') + result_odd_even = None if result_number == 0 else ('奇数' if result_number % 2 == 1 else '偶数') + result_big_small = None if result_number == 0 else ('小' if 1 <= result_number <= 18 else '大') + + result_range = None + if 1 <= result_number <= 12: + result_range = '1-12' + elif 13 <= result_number <= 24: + result_range = '13-24' + elif 25 <= result_number <= 36: + result_range = '25-36' + + for bet in bets: + is_win = False + multiplier = bet['multiplier'] + + bet_category = bet.get('bet_category') + if bet_category == '数字': + if bet.get('bet_number') == result_number: + is_win = True + elif bet_category == '颜色': + if bet.get('bet_value') == result_color: + is_win = True + elif bet_category == '奇偶': + if result_odd_even and bet.get('bet_value') == result_odd_even: + is_win = True + elif bet_category == '大小': + if result_big_small and bet.get('bet_value') == result_big_small: + is_win = True + elif bet_category == '区间': + if result_range and bet.get('bet_value') == result_range: + is_win = True + + if is_win: + win_amount = int(bet['amount'] * 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: + result_str = str(result_number) + for bet in bets: + is_win = False + multiplier = bet['multiplier'] + + bet_category = bet.get('bet_category') + if bet_category == '数字': + if bet.get('bet_number') == result_number: + is_win = True + elif bet_category == '颜色': + if bet.get('bet_value') == result_color: + is_win = True + elif bet_category == '奇偶': + if result_odd_even and bet.get('bet_value') == result_odd_even: + is_win = True + elif bet_category == '大小': + if result_big_small and bet.get('bet_value') == result_big_small: + is_win = True + elif bet_category == '区间': + if result_range and bet.get('bet_value') == result_range: + is_win = True + + if is_win: + win_amount = int(bet['amount'] * 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_str, actual_win, current_time, bet['id'])) + else: + cursor.execute(""" + UPDATE casino_bets + SET status = 'settled', result = ?, settled_at = ? + WHERE id = ? + """, (result_str, 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_str, + 'result_number': result_number + } + except Exception as e: + logger.error(f"结算失败: {e}", exc_info=True) + raise + + def _settle_blackjack_bets(self, chat_id: int, game_type: str, hands_dict: Dict, + banker_id: int) -> Dict: + """结算21点游戏下注 + + Args: + chat_id: 会话ID + game_type: 游戏类型 + hands_dict: 手牌字典 {user_id: {'cards': [2,3,4], 'status': 'stood'}, ...} + 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) + banker_hand = hands_dict.get(0, {}) # 0表示庄家 + banker_cards = banker_hand.get('cards', []) + banker_status = banker_hand.get('status', 'stood') + banker_points = self._calculate_blackjack_points(banker_cards) + banker_is_busted = banker_status == 'busted' + banker_is_blackjack = banker_status == 'blackjack' + + winners = [] + losers = [] + total_win = 0 + + for bet in bets: + user_id = bet['user_id'] + player_hand = hands_dict.get(user_id, {}) + player_cards = player_hand.get('cards', []) + player_status = player_hand.get('status', 'stood') + player_points = self._calculate_blackjack_points(player_cards) + player_is_busted = player_status == 'busted' + player_is_blackjack = player_status == 'blackjack' + + is_win = False + multiplier = bet['multiplier'] + + if player_is_busted: + is_win = False + elif player_is_blackjack: + if banker_is_blackjack: + # 双方都是黑杰克,平局(返还下注) + is_win = False + self.add_points(user_id, bet['amount'], 'casino_blackjack_push', + "21点游戏平局,返还下注") + else: + # 玩家黑杰克,赢得1.5倍 + is_win = True + multiplier = session.get('blackjack_multiplier', 1.5) + elif banker_is_busted: + is_win = True + elif player_points > banker_points: + is_win = True + elif player_points == banker_points: + # 平局,返还下注 + is_win = False + self.add_points(user_id, bet['amount'], 'casino_blackjack_push', + "21点游戏平局,返还下注") + + if is_win: + win_amount = int(bet['amount'] * multiplier) + house_fee = session['house_fee'] + actual_win = int(win_amount * (1 - house_fee)) + winners.append({ + 'user_id': user_id, + 'amount': bet['amount'], + 'win_amount': actual_win, + 'bet_id': bet['id'] + }) + total_win += actual_win + elif not player_is_busted and player_points != banker_points: + losers.append({ + 'user_id': user_id, + 'amount': bet['amount'], + 'bet_id': bet['id'] + }) + + try: + for bet in bets: + user_id = bet['user_id'] + player_hand = hands_dict.get(user_id, {}) + player_cards = player_hand.get('cards', []) + player_status = player_hand.get('status', 'stood') + player_points = self._calculate_blackjack_points(player_cards) + player_is_busted = player_status == 'busted' + player_is_blackjack = player_status == 'blackjack' + + is_win = False + multiplier = bet['multiplier'] + + if player_is_busted: + is_win = False + elif player_is_blackjack: + if banker_is_blackjack: + is_win = False + self.add_points(user_id, bet['amount'], 'casino_blackjack_push', + "21点游戏平局,返还下注") + else: + is_win = True + multiplier = session.get('blackjack_multiplier', 1.5) + elif banker_is_busted: + is_win = True + elif player_points > banker_points: + is_win = True + elif player_points == banker_points: + is_win = False + self.add_points(user_id, bet['amount'], 'casino_blackjack_push', + "21点游戏平局,返还下注") + + result_str = f"庄家{banker_points}点 vs 玩家{player_points}点" + + if is_win: + win_amount = int(bet['amount'] * multiplier) + actual_win = int(win_amount * (1 - session['house_fee'])) + self.add_points(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_str, actual_win, current_time, bet['id'])) + elif not player_is_busted and player_points != banker_points: + cursor.execute(""" + UPDATE casino_bets + SET status = 'settled', result = ?, settled_at = ? + WHERE id = ? + """, (result_str, current_time, bet['id'])) + else: + # 平局,已返还下注 + cursor.execute(""" + UPDATE casino_bets + SET status = 'settled', result = ?, settled_at = ? + WHERE id = ? + """, (result_str, 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': f"庄家{banker_points}点" + } + except Exception as e: + logger.error(f"结算失败: {e}", exc_info=True) + raise + + def _calculate_blackjack_points(self, cards: List[int]) -> int: + """计算21点手牌点数 + + Args: + cards: 手牌列表,A=1,J/Q/K=10,其他为本身值(1-13,其中11=J, 12=Q, 13=K) + + Returns: + 点数 + """ + points = 0 + ace_count = 0 + + for card in cards: + if card == 1: + ace_count += 1 + points += 11 + elif card >= 11: + points += 10 + else: + points += card + + # 处理A的1/11选择 + while points > 21 and ace_count > 0: + points -= 10 + ace_count -= 1 + + return points + def close_casino_session(self, chat_id: int, game_type: str): """关闭游戏会话 diff --git a/games/casino.py b/games/casino.py index ba56992..3d270ad 100644 --- a/games/casino.py +++ b/games/casino.py @@ -44,10 +44,14 @@ class CasinoGame(BaseGame): # 根据游戏类型分发 if game_type == '大小': return await self._handle_bigsmall(sub_args, chat_id, user_id) + elif game_type == '轮盘': + return await self._handle_roulette(sub_args, chat_id, user_id) + elif game_type in ['21点', 'blackjack', '21']: + return await self._handle_blackjack(sub_args, chat_id, user_id) elif game_type in ['help', '帮助']: return self.get_help() else: - return f"❌ 暂不支持的游戏类型: {game_type}\n\n支持的类型:大小" + return f"❌ 暂不支持的游戏类型: {game_type}\n\n支持的类型:大小、轮盘、21点" except Exception as e: logger.error(f"处理赌场指令错误: {e}", exc_info=True) @@ -124,10 +128,10 @@ class CasinoGame(BaseGame): if max_bet > 10000: return "❌ 最大下注不能超过10000分!" - # 检查是否已有活跃游戏 - existing = self.db.get_active_casino_session(chat_id, '大小') + # 检查是否已有活跃游戏(单场限制) + existing = self.db.get_any_active_casino_session(chat_id) if existing: - return "⚠️ 当前已有进行中的大小游戏,请先结算后再开启新游戏。" + return f"⚠️ 当前已有进行中的游戏({existing['game_type']}),请先结算后再开启新游戏。" # 创建游戏会话 session_id = self.db.create_casino_session( @@ -405,5 +409,199 @@ class CasinoGame(BaseGame): def get_help(self) -> str: """获取帮助信息""" - return self._get_bigsmall_help() + text = "# 🎰 赌场游戏帮助\n\n" + text += self._get_bigsmall_help() + "\n\n" + text += self._get_roulette_help() + "\n\n" + text += self._get_blackjack_help() + return text + + # ===== 轮盘游戏 ===== + + async def _handle_roulette(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_roulette_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_roulette(sub_args, chat_id, user_id) + elif action in ['bet', '下注', '押']: + return await self._bet_roulette(sub_args, chat_id, user_id) + elif action in ['status', '状态', '查看']: + return await self._status_roulette(chat_id) + elif action in ['settle', '结算', '开奖']: + return await self._settle_roulette(chat_id, user_id) + elif action in ['cancel', '放弃', '关闭']: + return await self._cancel_roulette(chat_id, user_id) + elif action in ['help', '帮助']: + return self._get_roulette_help() + else: + return f"❌ 未知命令: {action}\n\n{self._get_roulette_help()}" + + async def _open_roulette(self, args: str, chat_id: int, user_id: int) -> str: + """庄家开启轮盘游戏""" + try: + parts = args.split() + if len(parts) != 2: + return "❌ 参数格式错误!\n\n正确格式:`.赌场 轮盘 open <最小下注> <最大下注>`\n\n示例:`.赌场 轮盘 open 10 100`" + + try: + min_bet = int(parts[0]) + max_bet = int(parts[1]) + except ValueError: + return "❌ 参数必须是数字!" + + if min_bet <= 0 or max_bet <= 0: + return "❌ 下注金额必须大于0!" + + if min_bet > max_bet: + return "❌ 最小下注不能大于最大下注!" + + if max_bet > 10000: + return "❌ 最大下注不能超过10000分!" + + existing = self.db.get_any_active_casino_session(chat_id) + if existing: + return f"⚠️ 当前已有进行中的游戏({existing['game_type']}),请先结算后再开启新游戏。" + + 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=1.0, # 轮盘赔率根据下注类型自动计算 + house_fee=0.05 + ) + + banker_display_name = self.db.get_user_display_name(user_id) + text = f"## 🎰 轮盘游戏已开启\n\n" + text += f"**庄家**:{banker_display_name}\n\n" + text += f"**最小下注**:{min_bet} 分\n\n" + text += f"**最大下注**:{max_bet} 分\n\n" + text += f"**抽水率**:5%\n\n" + text += "**赔率**:\n" + text += "- 数字:35倍\n" + text += "- 颜色/奇偶/大小:2倍\n" + text += "- 区间:3倍\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_roulette(self, args: str, chat_id: int, user_id: int) -> str: + """玩家下注轮盘""" + try: + parts = args.split() + if len(parts) < 2: + return "❌ 参数格式错误!\n\n正确格式:`.赌场 轮盘 bet <类型> <选项> <金额>`\n\n示例:`.赌场 轮盘 bet 数字 17 100`" + + bet_category = parts[0] + if len(parts) == 2: + # 颜色/奇偶/大小/区间下注:bet <类型> <金额> + bet_value = bet_category + bet_category_map = { + '红色': '颜色', '黑色': '颜色', '绿色': '颜色', + '奇数': '奇偶', '偶数': '奇偶', + '大': '大小', '小': '大小', + '1-12': '区间', '13-24': '区间', '25-36': '区间' + } + bet_category = bet_category_map.get(bet_value) + if not bet_category: + return f"❌ 未知的下注类型: {bet_value}" + try: + amount = int(parts[1]) + except ValueError: + return "❌ 下注金额必须是数字!" + bet_number = None + elif len(parts) == 3: + # 数字下注:bet 数字 <数字> <金额> + if bet_category != '数字': + return "❌ 数字下注格式:`.赌场 轮盘 bet 数字 <0-36> <金额>`" + try: + bet_number = int(parts[1]) + amount = int(parts[2]) + except ValueError: + return "❌ 数字和金额必须是数字!" + + if bet_number < 0 or bet_number > 36: + return "❌ 数字必须在0-36之间!" + bet_value = None + else: + return "❌ 参数格式错误!" + + 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 bet_category == '数字': + multiplier = 36.0 # 35:1赔率,返回36倍(包含本金) + elif bet_category in ['颜色', '奇偶', '大小']: + multiplier = 2.0 + elif bet_category == '区间': + multiplier = 3.0 + else: + return f"❌ 未知的下注类别: {bet_category}" + + if not self.db.consume_points(user_id, amount, 'casino_bet', f"轮盘游戏下注{bet_category}"): + return "❌ 扣除积分失败!" + + bet_id = self.db.create_casino_bet( + chat_id=chat_id, + game_type='轮盘', + user_id=user_id, + bet_type='轮盘', + amount=amount, + multiplier=multiplier, + bet_category=bet_category, + bet_number=bet_number, + bet_value=bet_value + ) + + updated_points = self.db.get_user_points(user_id) + + text = f"## 🎲 下注成功\n\n" + text += f"**下注类型**:{bet_category}" + if bet_number is not None: + text += f" - {bet_number}" + elif bet_value: + text += f" - {bet_value}" + text += f"\n\n**下注金额**:{amount} 分\n\n" + text += f"**赔率**:{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)}"