Files
WPSBot/games/casino.py
2025-10-31 12:09:07 +08:00

1321 lines
54 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""赌场游戏模块"""
import logging
import random
from typing import List
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 == '轮盘':
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支持的类型大小、轮盘、21点"
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 ['cancel', '放弃', '关闭']:
return await self._cancel_bigsmall(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_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=multiplier,
house_fee=0.05 # 固定5%抽水
)
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"**赔率**{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'] == '')
banker_display_name = self.db.get_user_display_name(session['banker_id'])
text = f"## 🎰 大小游戏状态\n\n"
text += f"**庄家**{banker_display_name}\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:
# 不需要参数,系统自动随机生成结果
# 使用系统随机数决定大小50%概率)
result = '' if random.random() > 0.5 else ''
# 结算
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']:
winner_display_name = self.db.get_user_display_name(winner['user_id'])
text += f"- {winner_display_name}: 下注{winner['amount']}分,赢得{winner['win_amount']}\n"
text += "\n"
# 显示输家详情
if settlement['losers']:
text += "**输家明细**\n"
for loser in settlement['losers']:
loser_display_name = self.db.get_user_display_name(loser['user_id'])
text += f"- {loser_display_name}: 下注{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)}"
async def _cancel_bigsmall(self, chat_id: int, user_id: int) -> str:
"""庄家放弃本轮游戏
Args:
chat_id: 会话ID
user_id: 用户ID
Returns:
放弃结果消息
"""
try:
# 检查是否有活跃的会话
session = self.db.get_active_casino_session(chat_id, '大小')
if not session:
return "❌ 当前没有进行中的游戏"
# 验证是否为庄家
if session['banker_id'] != user_id:
return "❌ 只有庄家可以放弃游戏"
# 获取所有待结算下注
bets = self.db.get_pending_bets(chat_id, '大小')
# 返还所有下注的积分
return_amount = 0
for bet in bets:
# 返还积分
self.db.add_points(bet['user_id'], bet['amount'], 'casino_cancel',
"庄家放弃游戏,返还下注")
return_amount += bet['amount']
# 关闭会话(不结算,返还积分)
self.db.close_casino_session(chat_id, '大小')
# 标记下注为已取消
cursor = self.db.conn.cursor()
cursor.execute("""
UPDATE casino_bets
SET status = 'cancelled'
WHERE chat_id = ? AND game_type = ? AND status = 'pending'
""", (chat_id, '大小'))
# 构建报告
banker_display_name = self.db.get_user_display_name(user_id)
text = f"## 🎰 游戏已放弃\n\n"
text += f"**庄家**{banker_display_name}\n\n"
text += f"**总返还积分**{return_amount}\n\n"
text += f"**返还人数**{len(bets)}\n\n"
text += "---\n\n"
text += "✅ 所有下注已返还,游戏已关闭"
return text
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`
- `.赌场 大小 settle` - 结算游戏(系统随机生成结果)
- `.赌场 大小 cancel` - 放弃游戏(返还所有下注)
### 玩家命令
- `.赌场 大小 bet <大/小> <金额>` - 下注
- 示例:`.赌场 大小 bet 大 50`
- 示例:`.赌场 大小 bet 小 30`
### 通用命令
- `.赌场 大小 status` - 查看当前状态
### 游戏规则
- 玩家下注后积分立即扣除
- 结算后根据结果发放奖励
- 庄家放弃游戏时,所有下注全额返还
- 系统抽水5%
- 赔率由庄家设置
"""
def get_help(self) -> str:
"""获取帮助信息"""
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)}"
async def _status_roulette(self, chat_id: int) -> str:
"""查看轮盘游戏状态"""
session = self.db.get_active_casino_session(chat_id, '轮盘')
if not session:
return "❌ 当前没有进行中的轮盘游戏"
bets = self.db.get_pending_bets(chat_id, '轮盘')
# 统计下注情况
bet_stats = {}
for bet in bets:
category = bet.get('bet_category', '未知')
value = bet.get('bet_value') or str(bet.get('bet_number', ''))
key = f"{category}-{value}"
if key not in bet_stats:
bet_stats[key] = {'amount': 0, 'count': 0}
bet_stats[key]['amount'] += bet['amount']
bet_stats[key]['count'] += 1
banker_display_name = self.db.get_user_display_name(session['banker_id'])
text = f"## 🎰 轮盘游戏状态\n\n"
text += f"**庄家**{banker_display_name}\n\n"
text += f"**最小下注**{session['min_bet']}\n\n"
text += f"**最大下注**{session['max_bet']}\n\n"
text += "---\n\n"
text += f"**当前下注统计**\n"
for key, stat in bet_stats.items():
parts = key.split('-', 1)
text += f"- {parts[0]}{' - ' + parts[1] if len(parts) > 1 else ''}{stat['amount']} 分({stat['count']}注)\n"
text += f"\n**总下注**{len(bets)}\n\n"
text += "---\n\n"
text += "💡 庄家可以使用 `.赌场 轮盘 settle` 结算"
return text
async def _settle_roulette(self, chat_id: int, user_id: int) -> str:
"""庄家结算轮盘游戏(系统随机生成结果)"""
try:
# 系统随机生成0-36的数字
result_number = random.randint(0, 36)
# 结算
settlement = self.db.settle_casino_bets(
chat_id, '轮盘', str(result_number), user_id,
result_number=result_number
)
# 构建结算报告
text = f"## 🎰 轮盘游戏结算\n\n"
text += f"**结算结果**:🎲 **{result_number}**\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']:
winner_display_name = self.db.get_user_display_name(winner['user_id'])
text += f"- {winner_display_name}: 下注{winner['amount']}分,赢得{winner['win_amount']}\n"
text += "\n"
if settlement['losers']:
text += "**输家明细**\n"
for loser in settlement['losers']:
loser_display_name = self.db.get_user_display_name(loser['user_id'])
text += f"- {loser_display_name}: 下注{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)}"
async def _cancel_roulette(self, chat_id: int, user_id: int) -> str:
"""庄家放弃轮盘游戏"""
try:
session = self.db.get_active_casino_session(chat_id, '轮盘')
if not session:
return "❌ 当前没有进行中的游戏"
if session['banker_id'] != user_id:
return "❌ 只有庄家可以放弃游戏"
bets = self.db.get_pending_bets(chat_id, '轮盘')
return_amount = 0
for bet in bets:
self.db.add_points(bet['user_id'], bet['amount'], 'casino_cancel',
"庄家放弃游戏,返还下注")
return_amount += bet['amount']
self.db.close_casino_session(chat_id, '轮盘')
cursor = self.db.conn.cursor()
cursor.execute("""
UPDATE casino_bets
SET status = 'cancelled'
WHERE chat_id = ? AND game_type = ? AND status = 'pending'
""", (chat_id, '轮盘'))
banker_display_name = self.db.get_user_display_name(user_id)
text = f"## 🎰 游戏已放弃\n\n"
text += f"**庄家**{banker_display_name}\n\n"
text += f"**总返还积分**{return_amount}\n\n"
text += f"**返还人数**{len(bets)}\n\n"
text += "---\n\n"
text += "✅ 所有下注已返还,游戏已关闭"
return text
except Exception as e:
logger.error(f"放弃轮盘游戏失败: {e}", exc_info=True)
return f"❌ 放弃游戏失败: {str(e)}"
def _get_roulette_help(self) -> str:
"""获取轮盘游戏帮助信息"""
return """## 🎰 轮盘游戏帮助
### 庄家命令
- `.赌场 轮盘 open <最小> <最大>` - 开启游戏
- 示例:`.赌场 轮盘 open 10 100`
- `.赌场 轮盘 settle` - 结算游戏系统随机生成0-36
- `.赌场 轮盘 cancel` - 放弃游戏(返还所有下注)
### 玩家命令
- `.赌场 轮盘 bet <类型> <选项> <金额>` - 下注
- 数字:`.赌场 轮盘 bet 数字 17 100` 押数字17100分35倍赔率
- 颜色:`.赌场 轮盘 bet 红色 50` 押红色50分2倍赔率
- 奇偶:`.赌场 轮盘 bet 奇数 30` 押奇数30分2倍赔率
- 大小:`.赌场 轮盘 bet 大 40` 押大19-3640分2倍赔率
- 区间:`.赌场 轮盘 bet 1-12 60` 押1-1260分3倍赔率
### 通用命令
- `.赌场 轮盘 status` - 查看当前状态
### 游戏规则
- 数字0-360为绿色其他数字为红色或黑色
- 数字下注35倍赔率
- 颜色/奇偶/大小2倍赔率
- 区间1-12/13-24/25-363倍赔率
- 系统抽水5%"""
# ===== 21点游戏 =====
async def _handle_blackjack(self, args: str, chat_id: int, user_id: int) -> str:
"""处理21点游戏"""
if not args:
return self._get_blackjack_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_blackjack(sub_args, chat_id, user_id)
elif action in ['bet', '下注', '']:
return await self._bet_blackjack(sub_args, chat_id, user_id)
elif action in ['deal', '发牌']:
return await self._deal_blackjack(chat_id, user_id)
elif action in ['hit', '要牌']:
return await self._hit_blackjack(chat_id, user_id)
elif action in ['stand', '停牌']:
return await self._stand_blackjack(chat_id, user_id)
elif action in ['status', '状态', '查看']:
return await self._status_blackjack(chat_id)
elif action in ['settle', '结算']:
return await self._settle_blackjack(chat_id, user_id)
elif action in ['cancel', '放弃', '关闭']:
return await self._cancel_blackjack(chat_id, user_id)
elif action in ['help', '帮助']:
return self._get_blackjack_help()
else:
return f"❌ 未知命令: {action}\n\n{self._get_blackjack_help()}"
async def _open_blackjack(self, args: str, chat_id: int, user_id: int) -> str:
"""庄家开启21点游戏"""
try:
parts = args.split()
if len(parts) < 2:
return "❌ 参数格式错误!\n\n正确格式:`.赌场 21点 open <最小下注> <最大下注> [黑杰克倍数]`\n\n示例:`.赌场 21点 open 10 100 1.5`"
try:
min_bet = int(parts[0])
max_bet = int(parts[1])
blackjack_multiplier = float(parts[2]) if len(parts) > 2 else 1.5
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分"
if blackjack_multiplier <= 0:
return "❌ 黑杰克倍数必须大于0"
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='21点',
banker_id=user_id,
min_bet=min_bet,
max_bet=max_bet,
multiplier=1.0, # 21点标准赔率1:1
house_fee=0.05,
current_phase='betting',
blackjack_multiplier=blackjack_multiplier
)
banker_display_name = self.db.get_user_display_name(user_id)
text = f"## 🎰 21点游戏已开启\n\n"
text += f"**庄家**{banker_display_name}\n\n"
text += f"**最小下注**{min_bet}\n\n"
text += f"**最大下注**{max_bet}\n\n"
text += f"**黑杰克倍数**{blackjack_multiplier}\n\n"
text += f"**抽水率**5%\n\n"
text += "---\n\n"
text += "💡 提示:玩家下注后,庄家使用 `.赌场 21点 deal` 发牌"
return text
except Exception as e:
logger.error(f"开启21点游戏失败: {e}", exc_info=True)
return f"❌ 开启游戏失败: {str(e)}"
async def _bet_blackjack(self, args: str, chat_id: int, user_id: int) -> str:
"""玩家下注21点"""
try:
try:
amount = int(args.strip())
except ValueError:
return "❌ 下注金额必须是数字!\n\n正确格式:`.赌场 21点 bet <金额>`"
session = self.db.get_active_casino_session(chat_id, '21点')
if not session:
return "❌ 当前没有进行中的游戏,请等待庄家开启游戏。"
if session['current_phase'] != 'betting':
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', "21点游戏下注"):
return "❌ 扣除积分失败!"
bet_id = self.db.create_casino_bet(
chat_id=chat_id,
game_type='21点',
user_id=user_id,
bet_type='标准',
amount=amount,
multiplier=1.0, # 标准赔率1:1
hand_status='playing'
)
updated_points = self.db.get_user_points(user_id)
text = f"## 🎲 下注成功\n\n"
text += f"**下注金额**{amount}\n\n"
text += f"**剩余积分**{updated_points['points']}\n\n"
text += "---\n\n"
text += "💡 等待庄家发牌..."
return text
except Exception as e:
logger.error(f"21点下注失败: {e}", exc_info=True)
return f"❌ 下注失败: {str(e)}"
def _draw_card(self) -> int:
"""抽取一张牌1-13其中1=A11=J12=Q13=K"""
return random.randint(1, 13)
def _calculate_points(self, cards: List[int]) -> int:
"""计算21点手牌点数复用数据库方法逻辑"""
return self.db._calculate_blackjack_points(cards)
def _check_blackjack(self, cards: List[int]) -> bool:
"""检查是否为黑杰克A+10/J/Q/K"""
if len(cards) != 2:
return False
has_ace = 1 in cards
has_ten = any(card >= 11 for card in cards) or 10 in cards
return has_ace and has_ten
async def _deal_blackjack(self, chat_id: int, user_id: int) -> str:
"""庄家发初始牌"""
try:
session = self.db.get_active_casino_session(chat_id, '21点')
if not session:
return "❌ 当前没有进行中的游戏"
if session['banker_id'] != user_id:
return "❌ 只有庄家可以发牌"
if session['current_phase'] != 'betting':
return "❌ 当前阶段不是下注阶段,无法发牌"
bets = self.db.get_pending_bets(chat_id, '21点')
if not bets:
return "❌ 还没有玩家下注,无法发牌"
session_id = session['id']
# 为庄家发两张牌
banker_cards = [self._draw_card(), self._draw_card()]
banker_points = self._calculate_points(banker_cards)
banker_status = 'blackjack' if self._check_blackjack(banker_cards) else 'playing'
self.db.create_blackjack_hand(session_id, 0, banker_cards, banker_status)
# 为每个下注的玩家发两张牌
player_hands = {}
for bet in bets:
player_cards = [self._draw_card(), self._draw_card()]
player_points = self._calculate_points(player_cards)
player_status = 'blackjack' if self._check_blackjack(player_cards) else 'playing'
self.db.create_blackjack_hand(session_id, bet['user_id'], player_cards, player_status)
player_hands[bet['user_id']] = {
'cards': player_cards,
'points': player_points,
'status': player_status
}
# 更新session阶段
cursor = self.db.conn.cursor()
cursor.execute("""
UPDATE casino_sessions
SET current_phase = 'playing'
WHERE id = ?
""", (session_id,))
# 构建发牌结果
text = f"## 🎰 发牌完成\n\n"
text += f"**庄家手牌**{self._format_cards(banker_cards)} ({banker_points}点)\n\n"
if banker_status == 'blackjack':
text += "**庄家黑杰克!**\n\n"
text += "---\n\n"
text += "**玩家手牌**\n"
for user_id, hand in player_hands.items():
player_name = self.db.get_user_display_name(user_id)
text += f"- {player_name}{self._format_cards(hand['cards'])} ({hand['points']}点)"
if hand['status'] == 'blackjack':
text += " **黑杰克!**"
text += "\n"
text += "\n---\n\n"
text += "💡 玩家可以使用 `.赌场 21点 hit` 要牌或 `.赌场 21点 stand` 停牌"
return text
except Exception as e:
logger.error(f"21点发牌失败: {e}", exc_info=True)
return f"❌ 发牌失败: {str(e)}"
def _format_cards(self, cards: List[int]) -> str:
"""格式化手牌显示"""
card_names = {
1: 'A',
11: 'J',
12: 'Q',
13: 'K'
}
formatted = []
for card in cards:
if card in card_names:
formatted.append(card_names[card])
else:
formatted.append(str(card))
return ' '.join(formatted)
async def _hit_blackjack(self, chat_id: int, user_id: int) -> str:
"""玩家要牌"""
try:
session = self.db.get_active_casino_session(chat_id, '21点')
if not session:
return "❌ 当前没有进行中的游戏"
if session['current_phase'] != 'playing':
return "❌ 当前不是游戏阶段,无法要牌"
# 检查玩家是否有下注
bets = self.db.get_pending_bets(chat_id, '21点')
user_bet = next((b for b in bets if b['user_id'] == user_id), None)
if not user_bet:
return "❌ 您没有下注,无法要牌"
# 获取玩家手牌
session_id = session['id']
hand = self.db.get_blackjack_hand(session_id, user_id)
if not hand:
return "❌ 未找到手牌信息"
if hand['hand_status'] != 'playing':
return f"❌ 当前手牌状态为:{hand['hand_status']},无法要牌"
# 抽一张新牌
new_card = self._draw_card()
cards = hand['hand_data'] + [new_card]
points = self._calculate_points(cards)
# 判断状态
if points > 21:
status = 'busted'
elif points == 21:
status = 'stood'
else:
status = 'playing'
# 更新手牌
self.db.update_blackjack_hand(session_id, user_id, cards, status)
player_name = self.db.get_user_display_name(user_id)
text = f"## 🎲 要牌\n\n"
text += f"**玩家**{player_name}\n\n"
text += f"**新手牌**{self._format_cards(cards)} ({points}点)\n\n"
if status == 'busted':
text += "**爆牌!** ❌\n\n"
elif points == 21:
text += "**21点** ✅\n\n"
text += "---\n\n"
if status == 'playing':
text += "💡 可以继续要牌或停牌"
elif status == 'stood':
text += "💡 21点自动停牌"
else:
text += "💡 已爆牌,等待结算"
return text
except Exception as e:
logger.error(f"21点要牌失败: {e}", exc_info=True)
return f"❌ 要牌失败: {str(e)}"
async def _stand_blackjack(self, chat_id: int, user_id: int) -> str:
"""玩家停牌"""
try:
session = self.db.get_active_casino_session(chat_id, '21点')
if not session:
return "❌ 当前没有进行中的游戏"
if session['current_phase'] != 'playing':
return "❌ 当前不是游戏阶段,无法停牌"
session_id = session['id']
hand = self.db.get_blackjack_hand(session_id, user_id)
if not hand:
return "❌ 未找到手牌信息"
if hand['hand_status'] != 'playing':
return f"❌ 当前手牌状态为:{hand['hand_status']},无法停牌"
# 更新状态为停牌
self.db.update_blackjack_hand(session_id, user_id, hand['hand_data'], 'stood')
points = self._calculate_points(hand['hand_data'])
player_name = self.db.get_user_display_name(user_id)
text = f"## 🎲 停牌\n\n"
text += f"**玩家**{player_name}\n\n"
text += f"**最终手牌**{self._format_cards(hand['hand_data'])} ({points}点)\n\n"
text += "---\n\n"
text += "💡 已停牌,等待其他玩家操作或庄家结算"
return text
except Exception as e:
logger.error(f"21点停牌失败: {e}", exc_info=True)
return f"❌ 停牌失败: {str(e)}"
async def _status_blackjack(self, chat_id: int) -> str:
"""查看21点游戏状态"""
session = self.db.get_active_casino_session(chat_id, '21点')
if not session:
return "❌ 当前没有进行中的21点游戏"
session_id = session['id']
bets = self.db.get_pending_bets(chat_id, '21点')
hands = self.db.get_all_blackjack_hands(session_id)
banker_display_name = self.db.get_user_display_name(session['banker_id'])
text = f"## 🎰 21点游戏状态\n\n"
text += f"**庄家**{banker_display_name}\n\n"
text += f"**当前阶段**{session['current_phase']}\n\n"
text += f"**最小下注**{session['min_bet']}\n\n"
text += f"**最大下注**{session['max_bet']}\n\n"
text += f"**黑杰克倍数**{session.get('blackjack_multiplier', 1.5)}\n\n"
# 显示庄家手牌
banker_hand = hands.get(0)
if banker_hand:
banker_points = self._calculate_points(banker_hand['cards'])
text += f"**庄家手牌**{self._format_cards(banker_hand['cards'])} ({banker_points}点)"
if banker_hand['status'] == 'blackjack':
text += " **黑杰克!**"
text += "\n\n"
text += "---\n\n"
text += f"**玩家下注**{len(bets)}\n\n"
# 显示玩家手牌
if hands:
text += "**玩家手牌**\n"
for user_id, hand in hands.items():
if user_id == 0: # 跳过庄家
continue
player_name = self.db.get_user_display_name(user_id)
player_bet = next((b for b in bets if b['user_id'] == user_id), None)
if player_bet:
points = self._calculate_points(hand['cards'])
text += f"- {player_name}{self._format_cards(hand['cards'])} ({points}点) - 下注{player_bet['amount']}"
if hand['status'] == 'blackjack':
text += " **黑杰克!**"
elif hand['status'] == 'busted':
text += " **爆牌**"
elif hand['status'] == 'stood':
text += " **已停牌**"
text += "\n"
text += "\n"
text += "---\n\n"
if session['current_phase'] == 'betting':
text += "💡 庄家可以使用 `.赌场 21点 deal` 发牌"
elif session['current_phase'] == 'playing':
text += "💡 玩家可以使用 `.赌场 21点 hit` 要牌或 `.赌场 21点 stand` 停牌\n"
text += "💡 庄家可以使用 `.赌场 21点 settle` 结算"
else:
text += "💡 庄家可以使用 `.赌场 21点 settle` 结算"
return text
async def _settle_blackjack(self, chat_id: int, user_id: int) -> str:
"""庄家结算21点游戏"""
try:
session = self.db.get_active_casino_session(chat_id, '21点')
if not session:
return "❌ 当前没有进行中的游戏"
if session['banker_id'] != user_id:
return "❌ 只有庄家可以结算游戏"
if session['current_phase'] == 'betting':
return "❌ 还未发牌,无法结算"
session_id = session['id']
hands = self.db.get_all_blackjack_hands(session_id)
# 获取庄家手牌
banker_hand = hands.get(0, {})
banker_cards = banker_hand.get('cards', [])
banker_points = self._calculate_points(banker_cards)
banker_status = banker_hand.get('status', 'stood')
# 如果庄家还在playing状态自动要牌到17点以上
while banker_status == 'playing' and banker_points < 17:
new_card = self._draw_card()
banker_cards.append(new_card)
banker_points = self._calculate_points(banker_cards)
if banker_points > 21:
banker_status = 'busted'
elif banker_points >= 17:
banker_status = 'stood'
# 更新庄家手牌
self.db.update_blackjack_hand(session_id, 0, banker_cards, banker_status)
# 更新hands字典
hands[0] = {'cards': banker_cards, 'status': banker_status}
#進行结算
settlement = self.db.settle_casino_bets(
chat_id, '21点', f"庄家{banker_points}", user_id,
hands_dict=hands
)
# 构建结算报告
text = f"## 🎰 21点游戏结算\n\n"
text += f"**庄家最终手牌**{self._format_cards(banker_cards)} ({banker_points}点)\n\n"
if banker_status == 'blackjack':
text += "**庄家黑杰克!**\n\n"
elif banker_status == 'busted':
text += "**庄家爆牌!**\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']:
winner_display_name = self.db.get_user_display_name(winner['user_id'])
player_hand = hands.get(winner['user_id'], {})
player_points = self._calculate_points(player_hand.get('cards', []))
text += f"- {winner_display_name}: {self._format_cards(player_hand.get('cards', []))} ({player_points}点) - 下注{winner['amount']}分,赢得{winner['win_amount']}\n"
text += "\n"
if settlement['losers']:
text += "**输家明细**\n"
for loser in settlement['losers']:
loser_display_name = self.db.get_user_display_name(loser['user_id'])
player_hand = hands.get(loser['user_id'], {})
player_points = self._calculate_points(player_hand.get('cards', []))
text += f"- {loser_display_name}: {self._format_cards(player_hand.get('cards', []))} ({player_points}点) - 下注{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"21点结算失败: {e}", exc_info=True)
return f"❌ 结算失败: {str(e)}"
async def _cancel_blackjack(self, chat_id: int, user_id: int) -> str:
"""庄家放弃21点游戏"""
try:
session = self.db.get_active_casino_session(chat_id, '21点')
if not session:
return "❌ 当前没有进行中的游戏"
if session['banker_id'] != user_id:
return "❌ 只有庄家可以放弃游戏"
bets = self.db.get_pending_bets(chat_id, '21点')
return_amount = 0
for bet in bets:
self.db.add_points(bet['user_id'], bet['amount'], 'casino_cancel',
"庄家放弃游戏,返还下注")
return_amount += bet['amount']
self.db.close_casino_session(chat_id, '21点')
cursor = self.db.conn.cursor()
cursor.execute("""
UPDATE casino_bets
SET status = 'cancelled'
WHERE chat_id = ? AND game_type = ? AND status = 'pending'
""", (chat_id, '21点'))
banker_display_name = self.db.get_user_display_name(user_id)
text = f"## 🎰 游戏已放弃\n\n"
text += f"**庄家**{banker_display_name}\n\n"
text += f"**总返还积分**{return_amount}\n\n"
text += f"**返还人数**{len(bets)}\n\n"
text += "---\n\n"
text += "✅ 所有下注已返还,游戏已关闭"
return text
except Exception as e:
logger.error(f"放弃21点游戏失败: {e}", exc_info=True)
return f"❌ 放弃游戏失败: {str(e)}"
def _get_blackjack_help(self) -> str:
"""获取21点游戏帮助信息"""
return """## 🎰 21点游戏帮助
### 庄家命令
- `.赌场 21点 open <最小> <最大> [黑杰克倍数]` - 开启游戏
- 示例:`.赌场 21点 open 10 100 1.5`
- `.赌场 21点 deal` - 发牌(所有玩家下注后)
- `.赌场 21点 settle` - 结算游戏自动要牌到17点以上后结算
- `.赌场 21点 cancel` - 放弃游戏(返还所有下注)
### 玩家命令
- `.赌场 21点 bet <金额>` - 下注
- 示例:`.赌场 21点 bet 50`
- `.赌场 21点 hit` - 要牌
- `.赌场 21点 stand` - 停牌
### 通用命令
- `.赌场 21点 status` - 查看当前状态
### 游戏规则
- 目标手牌点数尽可能接近21点但不能超过
- 黑杰克A+10/J/Q/K赢得1.5倍(默认)
- 玩家点数>庄家点数且未爆牌赢得1:1
- 平局:返还下注
- 爆牌:失去下注
- 庄家自动要牌到17点以上
- 系统抽水5%"""