Files
WPSBot/games/alchemy.py
2025-10-29 11:32:43 +08:00

391 lines
15 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 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