初始化
This commit is contained in:
2
games/__init__.py
Normal file
2
games/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
"""游戏模块"""
|
||||
|
||||
122
games/base.py
Normal file
122
games/base.py
Normal file
@@ -0,0 +1,122 @@
|
||||
"""游戏基类"""
|
||||
import logging
|
||||
from abc import ABC, abstractmethod
|
||||
from core.database import get_db
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class BaseGame(ABC):
|
||||
"""游戏基类"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化游戏"""
|
||||
self.db = get_db()
|
||||
|
||||
@abstractmethod
|
||||
async def handle(self, command: str, chat_id: int, user_id: int) -> str:
|
||||
"""处理游戏指令
|
||||
|
||||
Args:
|
||||
command: 完整指令
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
回复消息
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
@abstractmethod
|
||||
def get_help(self) -> str:
|
||||
"""获取帮助信息
|
||||
|
||||
Returns:
|
||||
帮助文本
|
||||
"""
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
def get_help_message() -> str:
|
||||
"""获取总体帮助信息"""
|
||||
help_text = """## 🎮 WPS游戏机器人帮助
|
||||
|
||||
### 🎲 骰娘系统
|
||||
- `.r XdY` - 掷骰子(如:.r 1d20)
|
||||
- `.r XdY+Z` - 带修正掷骰(如:.r 2d6+3)
|
||||
|
||||
### ✊ 石头剪刀布
|
||||
- `.rps 石头` - 出石头
|
||||
- `.rps 剪刀` - 出剪刀
|
||||
- `.rps 布` - 出布
|
||||
- `.rps stats` - 查看战绩
|
||||
|
||||
### 🔮 运势占卜
|
||||
- `.fortune` - 今日运势
|
||||
- `.运势` - 今日运势
|
||||
|
||||
### 🔢 猜数字游戏
|
||||
- `.guess start` - 开始游戏
|
||||
- `.guess 数字` - 猜测数字
|
||||
- `.guess stop` - 结束游戏
|
||||
|
||||
### 📝 问答游戏
|
||||
- `.quiz` - 随机问题
|
||||
- `.quiz 答案` - 回答问题
|
||||
|
||||
### 其他
|
||||
- `.help` - 显示帮助
|
||||
- `.stats` - 查看个人统计
|
||||
|
||||
---
|
||||
💡 提示:@机器人 + 指令即可使用
|
||||
"""
|
||||
return help_text
|
||||
|
||||
|
||||
def get_stats_message(user_id: int) -> str:
|
||||
"""获取用户统计信息"""
|
||||
db = get_db()
|
||||
cursor = db.conn.cursor()
|
||||
|
||||
# 获取所有游戏统计
|
||||
cursor.execute(
|
||||
"SELECT game_type, wins, losses, draws, total_plays FROM game_stats WHERE user_id = ?",
|
||||
(user_id,)
|
||||
)
|
||||
stats = cursor.fetchall()
|
||||
|
||||
if not stats:
|
||||
return "📊 你还没有游戏记录哦~快来玩游戏吧!"
|
||||
|
||||
# 构建统计信息
|
||||
text = "## 📊 你的游戏统计\n\n"
|
||||
|
||||
game_names = {
|
||||
'rps': '✊ 石头剪刀布',
|
||||
'guess': '🔢 猜数字',
|
||||
'quiz': '📝 问答游戏'
|
||||
}
|
||||
|
||||
for row in stats:
|
||||
game_type = row[0]
|
||||
wins = row[1]
|
||||
losses = row[2]
|
||||
draws = row[3]
|
||||
total = row[4]
|
||||
|
||||
game_name = game_names.get(game_type, game_type)
|
||||
win_rate = (wins / total * 100) if total > 0 else 0
|
||||
|
||||
text += f"### {game_name}\n"
|
||||
text += f"- 总局数:{total}\n"
|
||||
text += f"- 胜利:{wins} 次\n"
|
||||
text += f"- 失败:{losses} 次\n"
|
||||
|
||||
if draws > 0:
|
||||
text += f"- 平局:{draws} 次\n"
|
||||
|
||||
text += f"- 胜率:{win_rate:.1f}%\n\n"
|
||||
|
||||
return text
|
||||
|
||||
175
games/dice.py
Normal file
175
games/dice.py
Normal file
@@ -0,0 +1,175 @@
|
||||
"""骰娘系统"""
|
||||
import re
|
||||
import random
|
||||
import logging
|
||||
from typing import Tuple, Optional, List
|
||||
from games.base import BaseGame
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DiceGame(BaseGame):
|
||||
"""骰娘游戏"""
|
||||
|
||||
# 骰子指令正则模式
|
||||
# 匹配:.r 3d6, .r 1d20+5, .r 2d10-3等
|
||||
DICE_PATTERN = re.compile(
|
||||
r'^\.r(?:oll)?\s+(\d+)d(\d+)(?:([+-])(\d+))?',
|
||||
re.IGNORECASE
|
||||
)
|
||||
|
||||
# 最大限制
|
||||
MAX_DICE_COUNT = 100
|
||||
MAX_DICE_SIDES = 1000
|
||||
|
||||
async def handle(self, command: str, chat_id: int, user_id: int) -> str:
|
||||
"""处理骰子指令
|
||||
|
||||
Args:
|
||||
command: 指令,如 ".r 1d20" 或 ".r 3d6+5"
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
回复消息
|
||||
"""
|
||||
try:
|
||||
# 解析指令
|
||||
result = self._parse_command(command)
|
||||
if not result:
|
||||
return self.get_help()
|
||||
|
||||
dice_count, dice_sides, modifier, modifier_value = result
|
||||
|
||||
# 验证参数
|
||||
if dice_count > self.MAX_DICE_COUNT:
|
||||
return f"❌ 骰子数量不能超过 {self.MAX_DICE_COUNT}"
|
||||
|
||||
if dice_sides > self.MAX_DICE_SIDES:
|
||||
return f"❌ 骰子面数不能超过 {self.MAX_DICE_SIDES}"
|
||||
|
||||
if dice_count <= 0 or dice_sides <= 0:
|
||||
return "❌ 骰子数量和面数必须大于0"
|
||||
|
||||
# 掷骰子
|
||||
rolls = [random.randint(1, dice_sides) for _ in range(dice_count)]
|
||||
total = sum(rolls)
|
||||
|
||||
# 应用修正值
|
||||
final_result = total
|
||||
if modifier:
|
||||
if modifier == '+':
|
||||
final_result = total + modifier_value
|
||||
elif modifier == '-':
|
||||
final_result = total - modifier_value
|
||||
|
||||
# 格式化输出
|
||||
return self._format_result(
|
||||
dice_count, dice_sides, rolls, total,
|
||||
modifier, modifier_value, final_result
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理骰子指令错误: {e}", exc_info=True)
|
||||
return f"❌ 处理指令出错: {str(e)}"
|
||||
|
||||
def _parse_command(self, command: str) -> Optional[Tuple[int, int, Optional[str], int]]:
|
||||
"""解析骰子指令
|
||||
|
||||
Args:
|
||||
command: 指令字符串
|
||||
|
||||
Returns:
|
||||
(骰子数量, 骰子面数, 修正符号, 修正值) 或 None
|
||||
"""
|
||||
match = self.DICE_PATTERN.match(command.strip())
|
||||
if not match:
|
||||
return None
|
||||
|
||||
dice_count = int(match.group(1))
|
||||
dice_sides = int(match.group(2))
|
||||
modifier = match.group(3) # '+' 或 '-' 或 None
|
||||
modifier_value = int(match.group(4)) if match.group(4) else 0
|
||||
|
||||
return dice_count, dice_sides, modifier, modifier_value
|
||||
|
||||
def _format_result(self, dice_count: int, dice_sides: int, rolls: List[int],
|
||||
total: int, modifier: Optional[str], modifier_value: int,
|
||||
final_result: int) -> str:
|
||||
"""格式化骰子结果
|
||||
|
||||
Args:
|
||||
dice_count: 骰子数量
|
||||
dice_sides: 骰子面数
|
||||
rolls: 各个骰子结果
|
||||
total: 骰子总和
|
||||
modifier: 修正符号
|
||||
modifier_value: 修正值
|
||||
final_result: 最终结果
|
||||
|
||||
Returns:
|
||||
格式化的Markdown消息
|
||||
"""
|
||||
# 构建表达式
|
||||
expression = f"{dice_count}d{dice_sides}"
|
||||
if modifier:
|
||||
expression += f"{modifier}{modifier_value}"
|
||||
|
||||
# Markdown格式输出
|
||||
text = f"## 🎲 掷骰结果\n\n"
|
||||
text += f"**表达式**:{expression}\n\n"
|
||||
|
||||
# 显示每个骰子的结果
|
||||
if dice_count <= 20: # 骰子数量不多时,显示详细结果
|
||||
rolls_str = ", ".join([f"**{r}**" for r in rolls])
|
||||
text += f"**骰子**:[{rolls_str}]\n\n"
|
||||
text += f"**点数和**:{total}\n\n"
|
||||
|
||||
if modifier:
|
||||
text += f"**修正**:{modifier}{modifier_value}\n\n"
|
||||
text += f"**最终结果**:<font color='#FF6B6B'>{final_result}</font>\n\n"
|
||||
else:
|
||||
text += f"**最终结果**:<font color='#FF6B6B'>{final_result}</font>\n\n"
|
||||
else:
|
||||
# 骰子太多,只显示总和
|
||||
text += f"**点数和**:{total}\n\n"
|
||||
if modifier:
|
||||
text += f"**修正**:{modifier}{modifier_value}\n\n"
|
||||
text += f"**最终结果**:<font color='#FF6B6B'>{final_result}</font>\n\n"
|
||||
|
||||
# 特殊提示
|
||||
if dice_count == 1:
|
||||
if rolls[0] == dice_sides:
|
||||
text += "✨ **大成功!**\n"
|
||||
elif rolls[0] == 1:
|
||||
text += "💥 **大失败!**\n"
|
||||
|
||||
return text
|
||||
|
||||
def get_help(self) -> str:
|
||||
"""获取帮助信息"""
|
||||
return """## 🎲 骰娘系统帮助
|
||||
|
||||
### 基础用法
|
||||
- `.r 1d20` - 掷一个20面骰
|
||||
- `.r 3d6` - 掷三个6面骰
|
||||
- `.r 2d10+5` - 掷两个10面骰,结果加5
|
||||
- `.r 1d20-3` - 掷一个20面骰,结果减3
|
||||
|
||||
### 说明
|
||||
- 格式:`.r XdY+Z`
|
||||
- X = 骰子数量(最多100个)
|
||||
- Y = 骰子面数(最多1000面)
|
||||
- Z = 修正值(可选)
|
||||
- 支持 + 和 - 修正
|
||||
- 单个d20骰出20为大成功,骰出1为大失败
|
||||
|
||||
### 示例
|
||||
```
|
||||
.r 1d6 → 掷一个6面骰
|
||||
.r 4d6 → 掷四个6面骰
|
||||
.r 1d20+5 → 1d20并加5
|
||||
.r 3d6-2 → 3d6并减2
|
||||
```
|
||||
"""
|
||||
|
||||
166
games/fortune.py
Normal file
166
games/fortune.py
Normal file
@@ -0,0 +1,166 @@
|
||||
"""运势占卜游戏"""
|
||||
import json
|
||||
import random
|
||||
import logging
|
||||
import hashlib
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from games.base import BaseGame
|
||||
from utils.parser import CommandParser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FortuneGame(BaseGame):
|
||||
"""运势占卜游戏"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化游戏"""
|
||||
super().__init__()
|
||||
self._fortunes = None
|
||||
self._tarot = None
|
||||
|
||||
def _load_data(self):
|
||||
"""懒加载运势数据"""
|
||||
if self._fortunes is None:
|
||||
try:
|
||||
data_file = Path(__file__).parent.parent / "data" / "fortunes.json"
|
||||
with open(data_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
self._fortunes = data.get('fortunes', [])
|
||||
self._tarot = data.get('tarot', [])
|
||||
logger.info("运势数据加载完成")
|
||||
except Exception as e:
|
||||
logger.error(f"加载运势数据失败: {e}")
|
||||
self._fortunes = []
|
||||
self._tarot = []
|
||||
|
||||
async def handle(self, command: str, chat_id: int, user_id: int) -> str:
|
||||
"""处理运势占卜指令
|
||||
|
||||
Args:
|
||||
command: 指令,如 ".fortune" 或 ".fortune tarot"
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
回复消息
|
||||
"""
|
||||
try:
|
||||
# 加载数据
|
||||
self._load_data()
|
||||
|
||||
# 提取参数
|
||||
_, args = CommandParser.extract_command_args(command)
|
||||
args = args.strip().lower()
|
||||
|
||||
# 塔罗牌
|
||||
if args in ['tarot', '塔罗', '塔罗牌']:
|
||||
return self._get_tarot(user_id)
|
||||
|
||||
# 默认:今日运势
|
||||
return self._get_daily_fortune(user_id)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理运势占卜指令错误: {e}", exc_info=True)
|
||||
return f"❌ 处理指令出错: {str(e)}"
|
||||
|
||||
def _get_daily_fortune(self, user_id: int) -> str:
|
||||
"""获取今日运势
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
运势信息
|
||||
"""
|
||||
if not self._fortunes:
|
||||
return "❌ 运势数据加载失败"
|
||||
|
||||
# 基于日期和用户ID生成seed
|
||||
# 同一用户同一天结果相同
|
||||
today = datetime.now().strftime('%Y-%m-%d')
|
||||
seed_str = f"{user_id}_{today}"
|
||||
seed = int(hashlib.md5(seed_str.encode()).hexdigest(), 16) % (10 ** 8)
|
||||
|
||||
# 使用seed选择运势
|
||||
random.seed(seed)
|
||||
fortune = random.choice(self._fortunes)
|
||||
|
||||
# 生成幸运数字和幸运颜色(同样基于seed)
|
||||
lucky_number = random.randint(1, 99)
|
||||
lucky_colors = ["红色", "蓝色", "绿色", "黄色", "紫色", "粉色", "橙色"]
|
||||
lucky_color = random.choice(lucky_colors)
|
||||
|
||||
# 重置随机seed
|
||||
random.seed()
|
||||
|
||||
# 格式化输出
|
||||
text = f"## 🔮 今日运势\n\n"
|
||||
text += f"**日期**:{today}\n\n"
|
||||
text += f"**运势**:{fortune['emoji']} <font color='{fortune['color']}'>{fortune['level']}</font>\n\n"
|
||||
text += f"**运势解读**:{fortune['description']}\n\n"
|
||||
text += f"**建议**:{fortune['advice']}\n\n"
|
||||
text += f"**幸运数字**:{lucky_number}\n\n"
|
||||
text += f"**幸运颜色**:{lucky_color}\n\n"
|
||||
text += "---\n\n"
|
||||
text += "💡 提示:运势仅供娱乐参考~"
|
||||
|
||||
return text
|
||||
|
||||
def _get_tarot(self, user_id: int) -> str:
|
||||
"""抽塔罗牌
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
塔罗牌信息
|
||||
"""
|
||||
if not self._tarot:
|
||||
return "❌ 塔罗牌数据加载失败"
|
||||
|
||||
# 基于时间和用户ID生成seed(分钟级别变化)
|
||||
now = datetime.now()
|
||||
seed_str = f"{user_id}_{now.strftime('%Y-%m-%d-%H-%M')}"
|
||||
seed = int(hashlib.md5(seed_str.encode()).hexdigest(), 16) % (10 ** 8)
|
||||
|
||||
# 使用seed选择塔罗牌
|
||||
random.seed(seed)
|
||||
card = random.choice(self._tarot)
|
||||
|
||||
# 重置随机seed
|
||||
random.seed()
|
||||
|
||||
# 格式化输出
|
||||
text = f"## 🃏 塔罗占卜\n\n"
|
||||
text += f"**牌面**:{card['emoji']} {card['name']}\n\n"
|
||||
text += f"**含义**:{card['meaning']}\n\n"
|
||||
text += f"**建议**:{card['advice']}\n\n"
|
||||
text += "---\n\n"
|
||||
text += "💡 提示:塔罗牌指引方向,最终决定权在你手中~"
|
||||
|
||||
return text
|
||||
|
||||
def get_help(self) -> str:
|
||||
"""获取帮助信息"""
|
||||
return """## 🔮 运势占卜
|
||||
|
||||
### 基础用法
|
||||
- `.fortune` - 查看今日运势
|
||||
- `.运势` - 查看今日运势
|
||||
- `.fortune tarot` - 抽塔罗牌
|
||||
|
||||
### 说明
|
||||
- 同一天查询,运势结果相同
|
||||
- 塔罗牌每分钟变化一次
|
||||
- 仅供娱乐参考
|
||||
|
||||
### 示例
|
||||
```
|
||||
.fortune
|
||||
.运势
|
||||
.fortune tarot
|
||||
```
|
||||
"""
|
||||
|
||||
240
games/guess.py
Normal file
240
games/guess.py
Normal file
@@ -0,0 +1,240 @@
|
||||
"""猜数字游戏"""
|
||||
import random
|
||||
import logging
|
||||
import time
|
||||
from games.base import BaseGame
|
||||
from utils.parser import CommandParser
|
||||
from config import GAME_CONFIG
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuessGame(BaseGame):
|
||||
"""猜数字游戏"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化游戏"""
|
||||
super().__init__()
|
||||
self.config = GAME_CONFIG.get('guess', {})
|
||||
self.min_number = self.config.get('min_number', 1)
|
||||
self.max_number = self.config.get('max_number', 100)
|
||||
self.max_attempts = self.config.get('max_attempts', 10)
|
||||
|
||||
async def handle(self, command: str, chat_id: int, user_id: int) -> str:
|
||||
"""处理猜数字指令
|
||||
|
||||
Args:
|
||||
command: 指令,如 ".guess start" 或 ".guess 50"
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
回复消息
|
||||
"""
|
||||
try:
|
||||
# 提取参数
|
||||
_, args = CommandParser.extract_command_args(command)
|
||||
args = args.strip().lower()
|
||||
|
||||
# 开始游戏
|
||||
if args in ['start', '开始']:
|
||||
return self._start_game(chat_id, user_id)
|
||||
|
||||
# 结束游戏
|
||||
if args in ['stop', '结束', 'end']:
|
||||
return self._stop_game(chat_id, user_id)
|
||||
|
||||
# 尝试解析为数字
|
||||
try:
|
||||
guess = int(args)
|
||||
return self._make_guess(chat_id, user_id, guess)
|
||||
except ValueError:
|
||||
return self.get_help()
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理猜数字指令错误: {e}", exc_info=True)
|
||||
return f"❌ 处理指令出错: {str(e)}"
|
||||
|
||||
def _start_game(self, chat_id: int, user_id: int) -> str:
|
||||
"""开始新游戏
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
提示消息
|
||||
"""
|
||||
# 检查是否已有进行中的游戏
|
||||
state = self.db.get_game_state(chat_id, user_id, 'guess')
|
||||
if state:
|
||||
state_data = state['state_data']
|
||||
attempts = state_data.get('attempts', 0)
|
||||
return f"⚠️ 你已经有一个进行中的游戏了!\n\n" \
|
||||
f"已经猜了 {attempts} 次,继续猜测或输入 `.guess stop` 结束游戏"
|
||||
|
||||
# 生成随机数
|
||||
target = random.randint(self.min_number, self.max_number)
|
||||
|
||||
# 保存游戏状态
|
||||
state_data = {
|
||||
'target': target,
|
||||
'attempts': 0,
|
||||
'guesses': [],
|
||||
'max_attempts': self.max_attempts
|
||||
}
|
||||
self.db.save_game_state(chat_id, user_id, 'guess', state_data)
|
||||
|
||||
text = f"## 🔢 猜数字游戏开始!\n\n"
|
||||
text += f"我想了一个 **{self.min_number}** 到 **{self.max_number}** 之间的数字\n\n"
|
||||
text += f"你有 **{self.max_attempts}** 次机会猜对它\n\n"
|
||||
text += f"输入 `.guess 数字` 开始猜测\n\n"
|
||||
text += f"输入 `.guess stop` 结束游戏"
|
||||
|
||||
return text
|
||||
|
||||
def _make_guess(self, chat_id: int, user_id: int, guess: int) -> str:
|
||||
"""进行猜测
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
guess: 猜测的数字
|
||||
|
||||
Returns:
|
||||
结果消息
|
||||
"""
|
||||
# 检查游戏状态
|
||||
state = self.db.get_game_state(chat_id, user_id, 'guess')
|
||||
if not state:
|
||||
return f"⚠️ 还没有开始游戏呢!\n\n输入 `.guess start` 开始游戏"
|
||||
|
||||
state_data = state['state_data']
|
||||
target = state_data['target']
|
||||
attempts = state_data['attempts']
|
||||
guesses = state_data['guesses']
|
||||
max_attempts = state_data['max_attempts']
|
||||
|
||||
# 检查数字范围
|
||||
if guess < self.min_number or guess > self.max_number:
|
||||
return f"❌ 请输入 {self.min_number} 到 {self.max_number} 之间的数字"
|
||||
|
||||
# 检查是否已经猜过
|
||||
if guess in guesses:
|
||||
return f"⚠️ 你已经猜过 {guess} 了!\n\n已猜过:{', '.join(map(str, sorted(guesses)))}"
|
||||
|
||||
# 更新状态
|
||||
attempts += 1
|
||||
guesses.append(guess)
|
||||
|
||||
# 判断结果
|
||||
if guess == target:
|
||||
# 猜对了!
|
||||
self.db.delete_game_state(chat_id, user_id, 'guess')
|
||||
self.db.update_game_stats(user_id, 'guess', win=True)
|
||||
|
||||
text = f"## 🎉 恭喜猜对了!\n\n"
|
||||
text += f"**答案**:<font color='#4CAF50'>{target}</font>\n\n"
|
||||
text += f"**用了**:{attempts} 次\n\n"
|
||||
|
||||
if attempts == 1:
|
||||
text += "太神了!一次就猜中!🎯"
|
||||
elif attempts <= 3:
|
||||
text += "真厉害!运气爆棚!✨"
|
||||
elif attempts <= 6:
|
||||
text += "不错哦!🌟"
|
||||
else:
|
||||
text += "虽然用了不少次,但最终还是猜对了!💪"
|
||||
|
||||
return text
|
||||
|
||||
# 没猜对
|
||||
if attempts >= max_attempts:
|
||||
# 次数用完了
|
||||
self.db.delete_game_state(chat_id, user_id, 'guess')
|
||||
self.db.update_game_stats(user_id, 'guess', loss=True)
|
||||
|
||||
text = f"## 😢 游戏结束\n\n"
|
||||
text += f"很遗憾,次数用完了\n\n"
|
||||
text += f"**答案是**:<font color='#F44336'>{target}</font>\n\n"
|
||||
text += f"下次再来挑战吧!"
|
||||
|
||||
return text
|
||||
|
||||
# 继续猜
|
||||
state_data['attempts'] = attempts
|
||||
state_data['guesses'] = guesses
|
||||
self.db.save_game_state(chat_id, user_id, 'guess', state_data)
|
||||
|
||||
# 提示大小
|
||||
hint = "太大了 📉" if guess > target else "太小了 📈"
|
||||
remaining = max_attempts - attempts
|
||||
|
||||
text = f"## ❌ {hint}\n\n"
|
||||
text += f"**第 {attempts} 次猜测**:{guess}\n\n"
|
||||
text += f"**剩余机会**:{remaining} 次\n\n"
|
||||
|
||||
# 给一些范围提示
|
||||
smaller_guesses = [g for g in guesses if g < target]
|
||||
larger_guesses = [g for g in guesses if g > target]
|
||||
|
||||
if smaller_guesses and larger_guesses:
|
||||
min_larger = min(larger_guesses)
|
||||
max_smaller = max(smaller_guesses)
|
||||
text += f"💡 提示:答案在 **{max_smaller}** 和 **{min_larger}** 之间\n\n"
|
||||
|
||||
text += f"已猜过:{', '.join(map(str, sorted(guesses)))}"
|
||||
|
||||
return text
|
||||
|
||||
def _stop_game(self, chat_id: int, user_id: int) -> str:
|
||||
"""结束游戏
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
提示消息
|
||||
"""
|
||||
state = self.db.get_game_state(chat_id, user_id, 'guess')
|
||||
if not state:
|
||||
return "⚠️ 当前没有进行中的游戏"
|
||||
|
||||
state_data = state['state_data']
|
||||
target = state_data['target']
|
||||
attempts = state_data['attempts']
|
||||
|
||||
self.db.delete_game_state(chat_id, user_id, 'guess')
|
||||
|
||||
text = f"## 🔢 游戏已结束\n\n"
|
||||
text += f"**答案是**:{target}\n\n"
|
||||
text += f"你猜了 {attempts} 次\n\n"
|
||||
text += "下次再来挑战吧!"
|
||||
|
||||
return text
|
||||
|
||||
def get_help(self) -> str:
|
||||
"""获取帮助信息"""
|
||||
return f"""## 🔢 猜数字游戏
|
||||
|
||||
### 基础用法
|
||||
- `.guess start` - 开始游戏
|
||||
- `.guess 数字` - 猜测数字
|
||||
- `.guess stop` - 结束游戏
|
||||
|
||||
### 游戏规则
|
||||
- 范围:{self.min_number} - {self.max_number}
|
||||
- 机会:{self.max_attempts} 次
|
||||
- 每次猜测后会提示"太大"或"太小"
|
||||
- 猜对即可获胜
|
||||
|
||||
### 示例
|
||||
```
|
||||
.guess start # 开始游戏
|
||||
.guess 50 # 猜50
|
||||
.guess 75 # 猜75
|
||||
.guess stop # 放弃游戏
|
||||
```
|
||||
"""
|
||||
|
||||
244
games/quiz.py
Normal file
244
games/quiz.py
Normal file
@@ -0,0 +1,244 @@
|
||||
"""问答游戏"""
|
||||
import json
|
||||
import random
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from games.base import BaseGame
|
||||
from utils.parser import CommandParser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class QuizGame(BaseGame):
|
||||
"""问答游戏"""
|
||||
|
||||
def __init__(self):
|
||||
"""初始化游戏"""
|
||||
super().__init__()
|
||||
self._questions = None
|
||||
|
||||
def _load_questions(self):
|
||||
"""懒加载题库"""
|
||||
if self._questions is None:
|
||||
try:
|
||||
data_file = Path(__file__).parent.parent / "data" / "quiz.json"
|
||||
with open(data_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
self._questions = data.get('questions', [])
|
||||
logger.info(f"题库加载完成,共 {len(self._questions)} 道题")
|
||||
except Exception as e:
|
||||
logger.error(f"加载题库失败: {e}")
|
||||
self._questions = []
|
||||
|
||||
async def handle(self, command: str, chat_id: int, user_id: int) -> str:
|
||||
"""处理问答指令
|
||||
|
||||
Args:
|
||||
command: 指令,如 ".quiz" 或 ".quiz 答案"
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
回复消息
|
||||
"""
|
||||
try:
|
||||
# 加载题库
|
||||
self._load_questions()
|
||||
|
||||
if not self._questions:
|
||||
return "❌ 题库加载失败"
|
||||
|
||||
# 提取参数
|
||||
_, args = CommandParser.extract_command_args(command)
|
||||
args = args.strip()
|
||||
|
||||
# 检查是否有进行中的题目
|
||||
state = self.db.get_game_state(chat_id, user_id, 'quiz')
|
||||
|
||||
if not args:
|
||||
# 没有参数,出新题或显示当前题
|
||||
if state:
|
||||
# 显示当前题目
|
||||
state_data = state['state_data']
|
||||
return self._show_current_question(state_data)
|
||||
else:
|
||||
# 出新题
|
||||
return self._new_question(chat_id, user_id)
|
||||
else:
|
||||
# 有参数,检查答案
|
||||
if state:
|
||||
return self._check_answer(chat_id, user_id, args)
|
||||
else:
|
||||
# 没有进行中的题目
|
||||
return "⚠️ 当前没有题目,输入 `.quiz` 获取新题目"
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理问答指令错误: {e}", exc_info=True)
|
||||
return f"❌ 处理指令出错: {str(e)}"
|
||||
|
||||
def _new_question(self, chat_id: int, user_id: int) -> str:
|
||||
"""出新题目
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
题目信息
|
||||
"""
|
||||
# 随机选择一道题
|
||||
question = random.choice(self._questions)
|
||||
|
||||
# 保存游戏状态
|
||||
state_data = {
|
||||
'question_id': question['id'],
|
||||
'question': question['question'],
|
||||
'answer': question['answer'],
|
||||
'keywords': question['keywords'],
|
||||
'hint': question.get('hint', ''),
|
||||
'category': question.get('category', ''),
|
||||
'attempts': 0,
|
||||
'max_attempts': 3
|
||||
}
|
||||
self.db.save_game_state(chat_id, user_id, 'quiz', state_data)
|
||||
|
||||
# 格式化输出
|
||||
text = f"## 📝 问答题\n\n"
|
||||
text += f"**分类**:{question.get('category', '未分类')}\n\n"
|
||||
text += f"**问题**:{question['question']}\n\n"
|
||||
text += f"💡 你有 **3** 次回答机会\n\n"
|
||||
text += f"输入 `.quiz 答案` 来回答"
|
||||
|
||||
return text
|
||||
|
||||
def _show_current_question(self, state_data: dict) -> str:
|
||||
"""显示当前题目
|
||||
|
||||
Args:
|
||||
state_data: 游戏状态数据
|
||||
|
||||
Returns:
|
||||
题目信息
|
||||
"""
|
||||
attempts = state_data['attempts']
|
||||
max_attempts = state_data['max_attempts']
|
||||
remaining = max_attempts - attempts
|
||||
|
||||
text = f"## 📝 当前题目\n\n"
|
||||
text += f"**分类**:{state_data.get('category', '未分类')}\n\n"
|
||||
text += f"**问题**:{state_data['question']}\n\n"
|
||||
text += f"**剩余机会**:{remaining} 次\n\n"
|
||||
|
||||
# 如果已经尝试过,显示提示
|
||||
if attempts > 0 and state_data.get('hint'):
|
||||
text += f"💡 提示:{state_data['hint']}\n\n"
|
||||
|
||||
text += f"输入 `.quiz 答案` 来回答"
|
||||
|
||||
return text
|
||||
|
||||
def _check_answer(self, chat_id: int, user_id: int, user_answer: str) -> str:
|
||||
"""检查答案
|
||||
|
||||
Args:
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
user_answer: 用户答案
|
||||
|
||||
Returns:
|
||||
结果信息
|
||||
"""
|
||||
state = self.db.get_game_state(chat_id, user_id, 'quiz')
|
||||
if not state:
|
||||
return "⚠️ 当前没有题目"
|
||||
|
||||
state_data = state['state_data']
|
||||
correct_answer = state_data['answer']
|
||||
keywords = state_data['keywords']
|
||||
attempts = state_data['attempts']
|
||||
max_attempts = state_data['max_attempts']
|
||||
|
||||
# 更新尝试次数
|
||||
attempts += 1
|
||||
|
||||
# 检查答案(关键词匹配)
|
||||
user_answer_lower = user_answer.lower().strip()
|
||||
is_correct = False
|
||||
|
||||
for keyword in keywords:
|
||||
if keyword.lower() in user_answer_lower:
|
||||
is_correct = True
|
||||
break
|
||||
|
||||
if is_correct:
|
||||
# 回答正确
|
||||
self.db.delete_game_state(chat_id, user_id, 'quiz')
|
||||
self.db.update_game_stats(user_id, 'quiz', win=True)
|
||||
|
||||
text = f"## 🎉 回答正确!\n\n"
|
||||
text += f"**答案**:<font color='#4CAF50'>{correct_answer}</font>\n\n"
|
||||
text += f"**用了**:{attempts} 次机会\n\n"
|
||||
|
||||
if attempts == 1:
|
||||
text += "太棒了!一次就答对!🎯"
|
||||
else:
|
||||
text += "虽然用了几次机会,但最终还是答对了!💪"
|
||||
|
||||
text += "\n\n输入 `.quiz` 获取下一题"
|
||||
|
||||
return text
|
||||
|
||||
# 回答错误
|
||||
if attempts >= max_attempts:
|
||||
# 机会用完
|
||||
self.db.delete_game_state(chat_id, user_id, 'quiz')
|
||||
self.db.update_game_stats(user_id, 'quiz', loss=True)
|
||||
|
||||
text = f"## ❌ 很遗憾,答错了\n\n"
|
||||
text += f"**正确答案**:<font color='#F44336'>{correct_answer}</font>\n\n"
|
||||
text += "下次加油!\n\n"
|
||||
text += "输入 `.quiz` 获取下一题"
|
||||
|
||||
return text
|
||||
|
||||
# 还有机会
|
||||
state_data['attempts'] = attempts
|
||||
self.db.save_game_state(chat_id, user_id, 'quiz', state_data)
|
||||
|
||||
remaining = max_attempts - attempts
|
||||
|
||||
text = f"## ❌ 答案不对\n\n"
|
||||
text += f"**你的答案**:{user_answer}\n\n"
|
||||
text += f"**剩余机会**:{remaining} 次\n\n"
|
||||
|
||||
# 显示提示
|
||||
if state_data.get('hint'):
|
||||
text += f"💡 提示:{state_data['hint']}\n\n"
|
||||
|
||||
text += "再想想,继续回答吧!"
|
||||
|
||||
return text
|
||||
|
||||
def get_help(self) -> str:
|
||||
"""获取帮助信息"""
|
||||
return """## 📝 问答游戏
|
||||
|
||||
### 基础用法
|
||||
- `.quiz` - 获取新题目
|
||||
- `.quiz 答案` - 回答问题
|
||||
|
||||
### 游戏规则
|
||||
- 每道题有 3 次回答机会
|
||||
- 答错会显示提示
|
||||
- 回答正确可继续下一题
|
||||
|
||||
### 示例
|
||||
```
|
||||
.quiz # 获取题目
|
||||
.quiz Python # 回答
|
||||
.quiz 北京 # 回答
|
||||
```
|
||||
|
||||
💡 提示:题目涵盖编程、地理、常识等多个领域
|
||||
"""
|
||||
|
||||
193
games/rps.py
Normal file
193
games/rps.py
Normal file
@@ -0,0 +1,193 @@
|
||||
"""石头剪刀布游戏"""
|
||||
import random
|
||||
import logging
|
||||
from games.base import BaseGame
|
||||
from utils.parser import CommandParser
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class RPSGame(BaseGame):
|
||||
"""石头剪刀布游戏"""
|
||||
|
||||
# 选择列表
|
||||
CHOICES = ["石头", "剪刀", "布"]
|
||||
|
||||
# 胜负关系:key 击败 value
|
||||
WINS_AGAINST = {
|
||||
"石头": "剪刀",
|
||||
"剪刀": "布",
|
||||
"布": "石头"
|
||||
}
|
||||
|
||||
# 英文/表情符号映射
|
||||
CHOICE_MAP = {
|
||||
"石头": "石头", "rock": "石头", "🪨": "石头", "👊": "石头",
|
||||
"剪刀": "剪刀", "scissors": "剪刀", "✂️": "剪刀", "✌️": "剪刀",
|
||||
"布": "布", "paper": "布", "📄": "布", "✋": "布"
|
||||
}
|
||||
|
||||
async def handle(self, command: str, chat_id: int, user_id: int) -> str:
|
||||
"""处理石头剪刀布指令
|
||||
|
||||
Args:
|
||||
command: 指令,如 ".rps 石头" 或 ".rps stats"
|
||||
chat_id: 会话ID
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
回复消息
|
||||
"""
|
||||
try:
|
||||
# 提取参数
|
||||
_, args = CommandParser.extract_command_args(command)
|
||||
args = args.strip()
|
||||
|
||||
# 查看战绩
|
||||
if args in ['stats', '战绩', '统计']:
|
||||
return self._get_stats(user_id)
|
||||
|
||||
# 没有参数,显示帮助
|
||||
if not args:
|
||||
return self.get_help()
|
||||
|
||||
# 解析用户选择
|
||||
player_choice = self._parse_choice(args)
|
||||
if not player_choice:
|
||||
return f"❌ 无效的选择:{args}\n\n{self.get_help()}"
|
||||
|
||||
# 机器人随机选择
|
||||
bot_choice = random.choice(self.CHOICES)
|
||||
|
||||
# 判定胜负
|
||||
result = self._judge(player_choice, bot_choice)
|
||||
|
||||
# 更新统计
|
||||
if result == 'win':
|
||||
self.db.update_game_stats(user_id, 'rps', win=True)
|
||||
elif result == 'loss':
|
||||
self.db.update_game_stats(user_id, 'rps', loss=True)
|
||||
elif result == 'draw':
|
||||
self.db.update_game_stats(user_id, 'rps', draw=True)
|
||||
|
||||
# 格式化输出
|
||||
return self._format_result(player_choice, bot_choice, result)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"处理石头剪刀布指令错误: {e}", exc_info=True)
|
||||
return f"❌ 处理指令出错: {str(e)}"
|
||||
|
||||
def _parse_choice(self, choice_str: str) -> str:
|
||||
"""解析用户选择
|
||||
|
||||
Args:
|
||||
choice_str: 用户输入的选择
|
||||
|
||||
Returns:
|
||||
标准化的选择(石头/剪刀/布)或空字符串
|
||||
"""
|
||||
choice_str = choice_str.lower().strip()
|
||||
return self.CHOICE_MAP.get(choice_str, "")
|
||||
|
||||
def _judge(self, player: str, bot: str) -> str:
|
||||
"""判定胜负
|
||||
|
||||
Args:
|
||||
player: 玩家选择
|
||||
bot: 机器人选择
|
||||
|
||||
Returns:
|
||||
'win', 'loss', 或 'draw'
|
||||
"""
|
||||
if player == bot:
|
||||
return 'draw'
|
||||
elif self.WINS_AGAINST[player] == bot:
|
||||
return 'win'
|
||||
else:
|
||||
return 'loss'
|
||||
|
||||
def _format_result(self, player_choice: str, bot_choice: str, result: str) -> str:
|
||||
"""格式化游戏结果
|
||||
|
||||
Args:
|
||||
player_choice: 玩家选择
|
||||
bot_choice: 机器人选择
|
||||
result: 游戏结果
|
||||
|
||||
Returns:
|
||||
格式化的Markdown消息
|
||||
"""
|
||||
# 表情符号映射
|
||||
emoji_map = {
|
||||
"石头": "🪨",
|
||||
"剪刀": "✂️",
|
||||
"布": "📄"
|
||||
}
|
||||
|
||||
text = f"## ✊ 石头剪刀布\n\n"
|
||||
text += f"**你出**:{emoji_map[player_choice]} {player_choice}\n\n"
|
||||
text += f"**我出**:{emoji_map[bot_choice]} {bot_choice}\n\n"
|
||||
|
||||
if result == 'win':
|
||||
text += "**结果**:<font color='#4CAF50'>🎉 你赢了!</font>\n"
|
||||
elif result == 'loss':
|
||||
text += "**结果**:<font color='#F44336'>😢 你输了!</font>\n"
|
||||
else:
|
||||
text += "**结果**:<font color='#FFC107'>🤝 平局!</font>\n"
|
||||
|
||||
return text
|
||||
|
||||
def _get_stats(self, user_id: int) -> str:
|
||||
"""获取用户战绩
|
||||
|
||||
Args:
|
||||
user_id: 用户ID
|
||||
|
||||
Returns:
|
||||
战绩信息
|
||||
"""
|
||||
stats = self.db.get_game_stats(user_id, 'rps')
|
||||
|
||||
total = stats['total_plays']
|
||||
if total == 0:
|
||||
return "📊 你还没有玩过石头剪刀布呢~\n\n快来试试吧!输入 `.rps 石头/剪刀/布` 开始游戏"
|
||||
|
||||
wins = stats['wins']
|
||||
losses = stats['losses']
|
||||
draws = stats['draws']
|
||||
win_rate = (wins / total * 100) if total > 0 else 0
|
||||
|
||||
text = f"## 📊 石头剪刀布战绩\n\n"
|
||||
text += f"**总局数**:{total} 局\n\n"
|
||||
text += f"**胜利**:{wins} 次 🎉\n\n"
|
||||
text += f"**失败**:{losses} 次 😢\n\n"
|
||||
text += f"**平局**:{draws} 次 🤝\n\n"
|
||||
text += f"**胜率**:<font color='#4CAF50'>{win_rate:.1f}%</font>\n"
|
||||
|
||||
return text
|
||||
|
||||
def get_help(self) -> str:
|
||||
"""获取帮助信息"""
|
||||
return """## ✊ 石头剪刀布
|
||||
|
||||
### 基础用法
|
||||
- `.rps 石头` - 出石头
|
||||
- `.rps 剪刀` - 出剪刀
|
||||
- `.rps 布` - 出布
|
||||
|
||||
### 其他指令
|
||||
- `.rps stats` - 查看战绩
|
||||
|
||||
### 支持的输入
|
||||
- 中文:石头、剪刀、布
|
||||
- 英文:rock、scissors、paper
|
||||
- 表情:🪨 ✂️ 📄
|
||||
|
||||
### 示例
|
||||
```
|
||||
.rps 石头
|
||||
.rps rock
|
||||
.rps 🪨
|
||||
```
|
||||
"""
|
||||
|
||||
Reference in New Issue
Block a user