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
|
2025-10-29 15:07:49 +08:00
|
|
|
|
from typing import *
|
2025-10-29 11:32:43 +08:00
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class AlchemyGame(BaseGame):
|
|
|
|
|
|
"""炼金系统游戏"""
|
|
|
|
|
|
|
|
|
|
|
|
def __init__(self):
|
|
|
|
|
|
"""初始化游戏"""
|
|
|
|
|
|
super().__init__()
|
|
|
|
|
|
self.db = get_db()
|
|
|
|
|
|
|
|
|
|
|
|
# 奖品池配置 - 确保数学期望等于消耗积分
|
2025-10-29 15:07:49 +08:00
|
|
|
|
self.prize_pool: List[Tuple[int, str, float, str]] = [
|
|
|
|
|
|
# (权重, 类型, 倍率, 描述)
|
|
|
|
|
|
(9, "penalty", 0, "炼金失败"),
|
|
|
|
|
|
(1, "penalty", -1, "炼金爆炸"),
|
|
|
|
|
|
(200, "points", 0.1, "少量积分"),
|
|
|
|
|
|
(490, "points", 0.5, "少量积分"),
|
|
|
|
|
|
(600, "points", 1, "等值积分"),
|
|
|
|
|
|
(490, "points", 2, "丰厚积分"),
|
|
|
|
|
|
(200, "points", 5, "丰厚积分"),
|
|
|
|
|
|
(9, "points", 10, "🌟 巨额积分"),
|
|
|
|
|
|
(1, "points", 100, "💎 传说积分"),
|
2025-10-29 11:32:43 +08:00
|
|
|
|
]
|
2025-10-29 15:07:49 +08:00
|
|
|
|
|
|
|
|
|
|
self.total_weight: int = 0
|
|
|
|
|
|
for weight,_,_,_ in self.prize_pool:
|
|
|
|
|
|
self.total_weight += weight
|
2025-10-29 11:32:43 +08:00
|
|
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 炼金说明
|
2025-10-29 12:56:57 +08:00
|
|
|
|
if args in ['help', '帮助', 'info']:
|
2025-10-29 11:32:43 +08:00
|
|
|
|
return self._get_alchemy_help()
|
|
|
|
|
|
|
|
|
|
|
|
# 默认:炼金抽奖
|
|
|
|
|
|
else:
|
|
|
|
|
|
# 解析消耗积分数量
|
|
|
|
|
|
cost_points = 10 # 默认消耗10积分
|
|
|
|
|
|
if args.isdigit():
|
|
|
|
|
|
cost_points = int(args)
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
2025-10-29 12:56:57 +08:00
|
|
|
|
if user_points['points'] < cost_points:
|
|
|
|
|
|
return f"❌ 积分不足!需要 {cost_points} 积分,当前可用 {user_points['points']} 积分"
|
2025-10-29 11:32:43 +08:00
|
|
|
|
|
|
|
|
|
|
# 选择奖品池
|
2025-10-29 15:07:49 +08:00
|
|
|
|
prize_pool = self.prize_pool
|
2025-10-29 11:32:43 +08:00
|
|
|
|
|
|
|
|
|
|
# 执行抽奖
|
|
|
|
|
|
reward = self._draw_prize(prize_pool)
|
2025-10-29 15:07:49 +08:00
|
|
|
|
reward_points = reward['value']*cost_points
|
2025-10-29 11:32:43 +08:00
|
|
|
|
|
|
|
|
|
|
# 消费积分
|
|
|
|
|
|
if not self.db.consume_points(user_id, cost_points, "alchemy", f"炼金抽奖消耗"):
|
|
|
|
|
|
return "❌ 积分消费失败,请稍后重试"
|
|
|
|
|
|
|
|
|
|
|
|
# 处理奖励
|
|
|
|
|
|
if reward['type'] == 'points' and reward['value'] > 0:
|
|
|
|
|
|
# 获得积分奖励
|
2025-10-29 15:07:49 +08:00
|
|
|
|
self.db.add_points(user_id, reward_points, "alchemy", f"炼金奖励")
|
2025-10-29 11:32:43 +08:00
|
|
|
|
elif reward['type'] == 'penalty' and reward['value'] < 0:
|
|
|
|
|
|
# 负面奖励(扣分)
|
2025-10-29 15:07:49 +08:00
|
|
|
|
penalty_points = abs(reward_points)
|
2025-10-29 11:32:43 +08:00
|
|
|
|
self.db.consume_points(user_id, penalty_points, "alchemy", f"炼金失败")
|
|
|
|
|
|
|
2025-10-29 12:56:57 +08:00
|
|
|
|
# 炼金系统已简化,不再记录历史
|
2025-10-29 11:32:43 +08:00
|
|
|
|
|
|
|
|
|
|
# 获取更新后的积分信息
|
|
|
|
|
|
updated_points = self.db.get_user_points(user_id)
|
|
|
|
|
|
|
|
|
|
|
|
# 格式化输出
|
|
|
|
|
|
text = f"## ⚗️ 炼金结果\n\n"
|
|
|
|
|
|
text += f"**消耗积分**:{cost_points} 分\n\n"
|
|
|
|
|
|
|
2025-10-29 15:07:49 +08:00
|
|
|
|
if reward['type'] == 'points':
|
|
|
|
|
|
text += f"**{reward['description']}**: 获得{reward_points} 积分\n\n"
|
|
|
|
|
|
elif reward['type'] == 'penalty':
|
|
|
|
|
|
text += f"**{reward['description']}**: 损失 {abs(reward_points)} 积分\n\n"
|
2025-10-29 11:32:43 +08:00
|
|
|
|
|
2025-10-29 12:56:57 +08:00
|
|
|
|
text += f"**当前积分**:{updated_points['points']} 分\n\n"
|
2025-10-29 11:32:43 +08:00
|
|
|
|
text += "---\n\n"
|
|
|
|
|
|
text += "💡 提示:炼金有风险,投资需谨慎!"
|
|
|
|
|
|
|
|
|
|
|
|
return text
|
|
|
|
|
|
|
|
|
|
|
|
def _draw_prize(self, prize_pool: list) -> dict:
|
|
|
|
|
|
"""从奖品池中抽取奖品
|
|
|
|
|
|
|
|
|
|
|
|
Args:
|
|
|
|
|
|
prize_pool: 奖品池
|
|
|
|
|
|
|
|
|
|
|
|
Returns:
|
|
|
|
|
|
奖品信息
|
|
|
|
|
|
"""
|
|
|
|
|
|
# 生成随机数
|
2025-10-29 15:07:49 +08:00
|
|
|
|
rand = random.random()*self.total_weight
|
2025-10-29 11:32:43 +08:00
|
|
|
|
cumulative_prob = 0.0
|
|
|
|
|
|
|
2025-10-29 15:07:49 +08:00
|
|
|
|
for weight, reward_type, reward_value, description in prize_pool:
|
|
|
|
|
|
cumulative_prob += weight
|
2025-10-29 11:32:43 +08:00
|
|
|
|
if rand <= cumulative_prob:
|
|
|
|
|
|
return {
|
|
|
|
|
|
'type': reward_type,
|
|
|
|
|
|
'value': reward_value,
|
|
|
|
|
|
'description': description
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2025-10-29 15:07:49 +08:00
|
|
|
|
# 兜底返回第一个奖品
|
2025-10-29 11:32:43 +08:00
|
|
|
|
return {
|
2025-10-29 15:07:49 +08:00
|
|
|
|
'type': prize_pool[0][1],
|
|
|
|
|
|
'value': prize_pool[0][2],
|
|
|
|
|
|
'description': prize_pool[0][3]
|
2025-10-29 11:32:43 +08:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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
|