Files
WPSBot/games/alchemy.py

391 lines
15 KiB
Python
Raw Normal View History

2025-10-29 11:32:43 +08:00
"""炼金系统游戏模块"""
import random
import logging
from datetime import datetime
from games.base import BaseGame
from utils.parser import CommandParser
from core.database import get_db
logger = logging.getLogger(__name__)
class AlchemyGame(BaseGame):
"""炼金系统游戏"""
def __init__(self):
"""初始化游戏"""
super().__init__()
self.db = get_db()
# 奖品池配置 - 确保数学期望等于消耗积分
# 消耗10积分时的奖品池 - 数学期望约10.5
self.prize_pool_10 = [
# 负面奖励 (概率, 类型, 数值, 描述)
(0.05, "penalty", -5, "炼金失败,损失积分"), # 5% 概率损失5积分
(0.02, "penalty", -10, "炼金爆炸,损失积分"), # 2% 概率损失10积分
# 普通奖励 (概率, 类型, 数值, 描述)
(0.30, "points", 5, "少量积分"), # 30% 概率获得5积分
(0.25, "points", 8, "少量积分"), # 25% 概率获得8积分
(0.20, "points", 10, "等值积分"), # 20% 概率获得10积分
(0.10, "points", 15, "丰厚积分"), # 10% 概率获得15积分
(0.05, "points", 20, "丰厚积分"), # 5% 概率获得20积分
# 大奖 (概率, 类型, 数值, 描述)
(0.02, "points", 50, "🌟 巨额积分"), # 2% 概率获得50积分
(0.01, "points", 100, "💎 传说积分"), # 1% 概率获得100积分
]
# 消耗20积分时的奖品池 - 数学期望约21
self.prize_pool_20 = [
# 负面奖励
(0.03, "penalty", -10, "炼金失败,损失积分"), # 3% 概率损失10积分
(0.02, "penalty", -20, "炼金爆炸,损失积分"), # 2% 概率损失20积分
# 普通奖励
(0.25, "points", 8, "少量积分"), # 25% 概率获得8积分
(0.20, "points", 12, "少量积分"), # 20% 概率获得12积分
(0.20, "points", 20, "等值积分"), # 20% 概率获得20积分
(0.15, "points", 25, "丰厚积分"), # 15% 概率获得25积分
(0.10, "points", 30, "丰厚积分"), # 10% 概率获得30积分
(0.05, "points", 40, "丰厚积分"), # 5% 概率获得40积分
# 大奖
(0.03, "points", 80, "🌟 巨额积分"), # 3% 概率获得80积分
(0.02, "points", 150, "💎 传说积分"), # 2% 概率获得150积分
]
# 消耗50积分时的奖品池 - 数学期望约52
self.prize_pool_50 = [
# 负面奖励
(0.02, "penalty", -25, "炼金失败,损失积分"), # 2% 概率损失25积分
(0.01, "penalty", -50, "炼金爆炸,损失积分"), # 1% 概率损失50积分
# 普通奖励
(0.20, "points", 20, "少量积分"), # 20% 概率获得20积分
(0.20, "points", 30, "少量积分"), # 20% 概率获得30积分
(0.20, "points", 50, "等值积分"), # 20% 概率获得50积分
(0.15, "points", 60, "丰厚积分"), # 15% 概率获得60积分
(0.12, "points", 75, "丰厚积分"), # 12% 概率获得75积分
(0.08, "points", 100, "丰厚积分"), # 8% 概率获得100积分
# 大奖
(0.02, "points", 150, "🌟 巨额积分"), # 2% 概率获得150积分
(0.00, "points", 300, "💎 传说积分"), # 0% 概率获得300积分预留
]
async def handle(self, command: str, chat_id: int, user_id: int) -> str:
"""处理炼金相关指令
Args:
command: 指令 ".alchemy", ".alchemy 10", ".alchemy stats"
chat_id: 会话ID
user_id: 用户ID
Returns:
回复消息
"""
try:
# 提取参数
_, args = CommandParser.extract_command_args(command)
args = args.strip().lower()
# 炼金统计
if args in ['stats', '统计', '记录']:
return self._get_alchemy_stats(user_id)
# 炼金记录
elif args in ['records', '历史', 'log']:
return self._get_alchemy_records(user_id)
# 炼金说明
elif args in ['help', '帮助', 'info']:
return self._get_alchemy_help()
# 默认:炼金抽奖
else:
# 解析消耗积分数量
cost_points = 10 # 默认消耗10积分
if args.isdigit():
cost_points = int(args)
if cost_points not in [10, 20, 50]:
return "❌ 炼金消耗积分只能是 10、20 或 50"
return self._perform_alchemy(user_id, cost_points)
except Exception as e:
logger.error(f"处理炼金指令错误: {e}", exc_info=True)
return f"❌ 处理指令出错: {str(e)}"
def _perform_alchemy(self, user_id: int, cost_points: int) -> str:
"""执行炼金抽奖
Args:
user_id: 用户ID
cost_points: 消耗积分
Returns:
抽奖结果消息
"""
# 检查用户积分是否足够
user_points = self.db.get_user_points(user_id)
if user_points['available_points'] < cost_points:
return f"❌ 积分不足!需要 {cost_points} 积分,当前可用 {user_points['available_points']} 积分"
# 选择奖品池
if cost_points == 10:
prize_pool = self.prize_pool_10
elif cost_points == 20:
prize_pool = self.prize_pool_20
elif cost_points == 50:
prize_pool = self.prize_pool_50
else:
return "❌ 不支持的炼金消耗积分"
# 执行抽奖
reward = self._draw_prize(prize_pool)
# 消费积分
if not self.db.consume_points(user_id, cost_points, "alchemy", f"炼金抽奖消耗"):
return "❌ 积分消费失败,请稍后重试"
# 处理奖励
if reward['type'] == 'points' and reward['value'] > 0:
# 获得积分奖励
self.db.add_points(user_id, reward['value'], "alchemy", f"炼金奖励")
elif reward['type'] == 'penalty' and reward['value'] < 0:
# 负面奖励(扣分)
penalty_points = abs(reward['value'])
self.db.consume_points(user_id, penalty_points, "alchemy", f"炼金失败")
# 记录炼金抽奖
self.db.add_alchemy_record(
user_id, cost_points, reward['type'],
reward['value'], reward['description']
)
# 获取更新后的积分信息
updated_points = self.db.get_user_points(user_id)
# 格式化输出
text = f"## ⚗️ 炼金结果\n\n"
text += f"**消耗积分**{cost_points}\n\n"
if reward['type'] == 'points' and reward['value'] > 0:
# 根据奖励大小选择不同的表情符号
if reward['value'] >= 100:
emoji = "👑"
text += f"**{emoji} 传说奖励**{reward['value']} 积分\n\n"
elif reward['value'] >= 50:
emoji = "💎"
text += f"**{emoji} 巨额奖励**{reward['value']} 积分\n\n"
else:
emoji = "🎁"
text += f"**{emoji} 获得奖励**{reward['value']} 积分\n\n"
text += f"**奖励描述**{reward['description']}\n\n"
elif reward['type'] == 'penalty' and reward['value'] < 0:
penalty_points = abs(reward['value'])
text += f"**💥 炼金失败**:损失 {penalty_points} 积分\n\n"
text += f"**失败原因**{reward['description']}\n\n"
else:
text += f"**获得奖励**{reward['description']}\n\n"
text += f"**当前积分**{updated_points['available_points']}\n\n"
text += "---\n\n"
text += "💡 提示:炼金有风险,投资需谨慎!"
return text
def _draw_prize(self, prize_pool: list) -> dict:
"""从奖品池中抽取奖品
Args:
prize_pool: 奖品池
Returns:
奖品信息
"""
# 生成随机数
rand = random.random()
cumulative_prob = 0.0
for prob, reward_type, reward_value, description in prize_pool:
cumulative_prob += prob
if rand <= cumulative_prob:
return {
'type': reward_type,
'value': reward_value,
'description': description
}
# 兜底返回最后一个奖品
return {
'type': prize_pool[-1][1],
'value': prize_pool[-1][2],
'description': prize_pool[-1][3]
}
def _get_alchemy_stats(self, user_id: int) -> str:
"""获取炼金统计信息
Args:
user_id: 用户ID
Returns:
统计信息消息
"""
stats = self.db.get_alchemy_stats(user_id)
if stats['total_draws'] == 0:
return "📊 你还没有进行过炼金抽奖哦~"
text = f"## ⚗️ 炼金统计\n\n"
text += f"**总抽奖次数**{stats['total_draws']}\n\n"
text += f"**总消耗积分**{stats['total_cost']}\n\n"
text += f"**总获得积分**{stats['total_points_gained']}\n\n"
net_points = stats['net_points']
if net_points > 0:
text += f"**净收益**+{net_points} 分 🎉\n\n"
elif net_points < 0:
text += f"**净收益**{net_points} 分 😅\n\n"
else:
text += f"**净收益**0 分 ⚖️\n\n"
# 计算平均收益
avg_gain = stats['total_points_gained'] / stats['total_draws']
avg_cost = stats['total_cost'] / stats['total_draws']
text += f"**平均消耗**{avg_cost:.1f} 分/次\n\n"
text += f"**平均获得**{avg_gain:.1f} 分/次\n\n"
text += "---\n\n"
text += "💡 提示:使用 `.alchemy records` 查看详细记录"
return text
def _get_alchemy_records(self, user_id: int, limit: int = 10) -> str:
"""获取炼金记录
Args:
user_id: 用户ID
limit: 限制数量
Returns:
记录信息消息
"""
records = self.db.get_alchemy_records(user_id, limit)
if not records:
return "📝 暂无炼金记录"
text = f"## ⚗️ 炼金记录(最近 {len(records)} 条)\n\n"
for record in records:
timestamp = datetime.fromtimestamp(record['created_at']).strftime('%m-%d %H:%M')
cost = record['cost_points']
reward_type = record['reward_type']
reward_value = record['reward_value']
description = record['reward_description']
text += f"**{timestamp}** 消耗 {cost}\n"
if reward_type == 'points' and reward_value > 0:
if reward_value >= 100:
emoji = "👑"
elif reward_value >= 50:
emoji = "💎"
else:
emoji = "🎁"
text += f" {emoji} 获得 {reward_value} 积分 - {description}\n"
elif reward_type == 'penalty' and reward_value < 0:
penalty_points = abs(reward_value)
text += f" 💥 损失 {penalty_points} 积分 - {description}\n"
else:
text += f" {description}\n"
text += "\n"
return text
def _get_alchemy_help(self) -> str:
"""获取炼金帮助信息
Returns:
帮助信息消息
"""
text = f"## ⚗️ 炼金系统\n\n"
text += f"### 基础用法\n"
text += f"- `.alchemy` - 消耗10积分进行炼金\n"
text += f"- `.alchemy 10` - 消耗10积分进行炼金\n"
text += f"- `.alchemy 20` - 消耗20积分进行炼金\n"
text += f"- `.alchemy 50` - 消耗50积分进行炼金\n\n"
text += f"### 其他功能\n"
text += f"- `.alchemy stats` - 查看炼金统计\n"
text += f"- `.alchemy records` - 查看炼金记录\n"
text += f"- `.alchemy help` - 查看帮助\n\n"
text += f"### 炼金说明\n"
text += f"- **消耗积分**10、20、50 积分\n"
text += f"- **奖励类型**:积分奖励、负面惩罚\n"
text += f"- **大奖概率**:极小概率获得巨额积分\n"
text += f"- **风险提示**:小概率额外扣分\n"
text += f"- **数学期望**:略高于消耗积分,对玩家友好\n"
text += f"- **风险提示**:炼金有风险,投资需谨慎!\n\n"
text += f"### 示例\n"
text += f"```\n"
text += f".alchemy\n"
text += f".alchemy 20\n"
text += f".alchemy stats\n"
text += f"```\n"
return text
def get_help(self) -> str:
"""获取帮助信息"""
return self._get_alchemy_help()
def calculate_expected_value(self, cost_points: int) -> float:
"""计算奖品池的数学期望
Args:
cost_points: 消耗积分
Returns:
数学期望值
"""
if cost_points == 10:
prize_pool = self.prize_pool_10
elif cost_points == 20:
prize_pool = self.prize_pool_20
elif cost_points == 50:
prize_pool = self.prize_pool_50
else:
return 0.0
expected_value = 0.0
for prob, reward_type, reward_value, description in prize_pool:
if reward_type == 'points':
expected_value += prob * reward_value
return expected_value
def verify_prize_pools(self) -> dict:
"""验证所有奖品池的数学期望
Returns:
验证结果字典
"""
results = {}
for cost_points in [10, 20, 50]:
expected = self.calculate_expected_value(cost_points)
results[f"cost_{cost_points}"] = {
"expected_value": expected,
"is_fair": abs(expected - cost_points) < 0.01, # 允许0.01的误差
"difference": expected - cost_points
}
return results