初始化

This commit is contained in:
2025-10-28 13:00:35 +08:00
commit ff3d9cc343
37 changed files with 10368 additions and 0 deletions

2
games/__init__.py Normal file
View File

@@ -0,0 +1,2 @@
"""游戏模块"""

122
games/base.py Normal file
View 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
View 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
View 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
View 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
View 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
View 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 🪨
```
"""