aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/examples
diff options
context:
space:
mode:
author简律纯 <i@jyunko.cn>2025-10-25 00:30:48 +0800
committer简律纯 <i@jyunko.cn>2025-10-25 00:30:48 +0800
commitcbc653ffd0ea9abf4360623dc7a7651e1a49cc61 (patch)
treeea3c396148158077bae3e77eaa9341f8c1990636 /examples
parent08299b37dfda86e56e4f2b442f68ccd2da7a82e3 (diff)
downloadconventional_role_play-cbc653ffd0ea9abf4360623dc7a7651e1a49cc61.tar.gz
conventional_role_play-cbc653ffd0ea9abf4360623dc7a7651e1a49cc61.zip
feat: Implement plugin system with combat tracker and dice analyzer
- Added `plugin_system_demo.py` to demonstrate basic plugin usage, processing, and analysis. - Created `CombatTrackerPlugin` for tracking combat statistics including damage and healing. - Developed `DiceAnalyzerPlugin` for analyzing dice rolls and calculating success rates. - Introduced `renderer_demo.py` for rendering output in HTML, Markdown, and JSON formats. - Implemented `rule_system_demo.py` to showcase rule engine capabilities with various examples. - Established core rule engine functionality in `rules.py` with support for conditions and actions. - Enhanced base plugin structure in `base.py` to support different plugin types (Processor, Renderer, Analyzer). - Added custom exception handling in `exceptions.py` for better error management. - Configured logging setup in `logging_config.py` for improved logging capabilities. - Created unit tests in `test_rust_core.py` to validate core functionalities and performance.
Diffstat (limited to 'examples')
-rw-r--r--examples/README.md190
-rw-r--r--examples/basic_usage.py58
-rw-r--r--examples/custom_plugin.py70
-rw-r--r--examples/plugin_system_demo.py173
-rw-r--r--examples/plugins/combat_tracker_plugin.py96
-rw-r--r--examples/plugins/dice_analyzer_plugin.py90
-rw-r--r--examples/renderer_demo.py147
-rw-r--r--examples/rule_system_demo.py210
8 files changed, 737 insertions, 297 deletions
diff --git a/examples/README.md b/examples/README.md
deleted file mode 100644
index d823f5b..0000000
--- a/examples/README.md
+++ /dev/null
@@ -1,190 +0,0 @@
-# ConventionalRP 示例
-
-本目录包含 ConventionalRP SDK 的使用示例。
-
-## 目录结构
-
-```
-examples/
-├── basic_usage.py # 基础使用示例
-├── custom_plugin.py # 自定义插件示例
-├── rules/ # 规则文件
-│ └── dnd5e_rules.json5 # D&D 5E 解析规则
-├── logs/ # 示例日志文件
-│ ├── sample_session.txt # 完整会话日志
-│ └── combat_log.txt # 战斗日志
-└── output/ # 输出文件目录(自动生成)
-```
-
-## 快速开始
-
-### 1. 基础使用示例
-
-演示如何解析 TRPG 日志并以多种格式输出:
-
-```bash
-cd examples
-python basic_usage.py
-```
-
-**输出:**
-- `output/session_output.json` - JSON 格式
-- `output/session_output.html` - HTML 格式
-- `output/session_output.md` - Markdown 格式
-
-### 2. 自定义插件示例
-
-演示如何创建自定义插件进行数据分析:
-
-```bash
-python custom_plugin.py
-```
-
-**功能:**
-- 骰子统计分析
-- 对话提取
-- 角色行为分析
-
-## 规则文件格式
-
-规则文件使用 JSON5 格式(支持注释和尾随逗号):
-
-```json5
-{
- metadata: [{
- type: "metadata",
- patterns: ["正则表达式"],
- groups: ["字段名"],
- priority: 100
- }],
-
- content: [{
- type: "内容类型",
- match_type: "匹配模式", // enclosed, prefix, suffix
- patterns: ["正则表达式"],
- groups: ["提取字段"],
- priority: 90
- }]
-}
-```
-
-### 匹配模式说明
-
-- **enclosed**: 封闭匹配(如 `**动作**`、`「对话」`)
-- **prefix**: 前缀匹配(如 `[系统]消息`)
-- **suffix**: 后缀匹配(文本结尾)
-
-### 优先级
-
-- 数字越大优先级越高
-- 建议范围:1-100
-- 元数据通常设置为最高优先级(100)
-
-## 日志文件格式
-
-标准 TRPG 日志格式:
-
-```
-[时间戳] <角色名> 内容
-```
-
-**示例:**
-
-```
-[2025-10-24 14:30:01] <艾莉娅> 「我要检查这扇门」
-[2025-10-24 14:30:05] <DiceBot> 检定结果: [d20 = 18]
-[2025-10-24 14:30:10] <DM> 你发现了陷阱
-```
-
-## 自定义规则
-
-你可以为不同的游戏系统创建自定义规则:
-
-### D&D 5E
-
-已提供 `rules/dnd5e_rules.json5`
-
-### 其他系统
-
-创建新的规则文件,参考 D&D 5E 规则的结构:
-
-```bash
-cp rules/dnd5e_rules.json5 rules/my_system_rules.json5
-# 然后编辑 my_system_rules.json5
-```
-
-## 创建自定义插件
-
-插件是用于扩展功能的 Python 类:
-
-```python
-class MyPlugin:
- def __init__(self):
- self.name = "My Plugin"
-
- def process(self, parsed_data):
- # 你的处理逻辑
- return result
-```
-
-查看 `custom_plugin.py` 了解完整示例。
-
-## 常见模式
-
-### 1. 骰子投掷
-
-- `[d20 = 18]` - 简单投掷结果
-- `.r1d20+5` - 投掷命令
-- `(1d20+5 = 18)` - 完整投掷信息
-
-### 2. 角色动作
-
-- `*动作描述*` - 单星号
-- `**重要动作**` - 双星号
-
-### 3. 对话
-
-- `「对话内容」` - 中文引号
-- `"对话内容"` - 英文引号
-- `"对话内容"` - 弯引号
-
-### 4. OOC(脱戏)
-
-- `((OOC内容))` - 双括号
-- `//OOC注释` - 双斜杠
-
-### 5. 系统消息
-
-- `[系统]消息内容`
-- `[System]Message`
-
-## 疑难解答
-
-### 问题:规则文件加载失败
-
-**解决方案:**
-1. 确保文件是有效的 JSON5 格式
-2. 检查正则表达式是否转义正确(使用 `\\` 而不是 `\`)
-3. 验证文件编码为 UTF-8
-
-### 问题:解析结果不正确
-
-**解决方案:**
-1. 调整规则的优先级
-2. 测试正则表达式(使用 https://regex101.com/)
-3. 检查 match_type 是否正确
-
-### 问题:中文字符显示异常
-
-**解决方案:**
-- 确保所有文件使用 UTF-8 编码
-- 在打开文件时指定 `encoding='utf-8'`
-
-## 更多示例
-
-访问项目文档查看更多示例:
-https://crp.hydroroll.team/
-
-## 贡献
-
-欢迎提交新的示例和规则文件!请参考 CONTRIBUTING.md
diff --git a/examples/basic_usage.py b/examples/basic_usage.py
index c327cb0..d1ab724 100644
--- a/examples/basic_usage.py
+++ b/examples/basic_usage.py
@@ -1,13 +1,6 @@
-#!/usr/bin/env python3
-"""
-基础使用示例
-演示如何使用 ConventionalRP 解析和处理 TRPG 日志
-"""
-
import sys
from pathlib import Path
-# 添加 src 目录到 Python 路径
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root / "src"))
@@ -18,37 +11,22 @@ from conventionalrp.renderers.html_renderer import HTMLRenderer
from conventionalrp.renderers.json_renderer import JSONRenderer
from conventionalrp.renderers.markdown_renderer import MarkdownRenderer
-
-def main():
- # 获取示例文件路径
+def main():
example_dir = Path(__file__).parent
rules_file = example_dir / "rules" / "dnd5e_rules.json5"
log_file = example_dir / "logs" / "sample_session.txt"
-
- print("=" * 60)
- print("ConventionalRP 基础使用示例")
- print("=" * 60)
-
- # 步骤 1: 加载规则
- print("\n[步骤 1] 加载解析规则...")
+
parser = Parser()
parser.load_rules(str(rules_file))
- print(f"✓ 规则加载成功: {rules_file.name}")
+ print(f"Rule loaded: {rules_file.name}")
- # 步骤 2: 解析日志
- print("\n[步骤 2] 解析 TRPG 日志...")
parsed_data = parser.parse_log(str(log_file))
- print(f"✓ 日志解析完成,共 {len(parsed_data)} 条记录")
+ print(f"Log parsed successfully, {len(parsed_data)} entries found.")
- # 步骤 3: 处理解析结果
- print("\n[步骤 3] 处理解析后的数据...")
processor = Processor()
processed_data = processor.process_tokens(parsed_data)
- print(f"✓ 数据处理完成")
-
- # 步骤 4: 渲染输出
- print("\n[步骤 4] 渲染输出...")
-
+ print(f"Done processing data")
+
# JSON 格式
json_renderer = JSONRenderer()
json_output = json_renderer.render(processed_data)
@@ -56,7 +34,7 @@ def main():
json_file.parent.mkdir(exist_ok=True)
with open(json_file, "w", encoding="utf-8") as f:
f.write(json_output)
- print(f"✓ JSON 输出已保存: {json_file}")
+ print(f"Json: {json_file}")
# HTML 格式
html_renderer = HTMLRenderer()
@@ -64,32 +42,14 @@ def main():
html_file = example_dir / "output" / "session_output.html"
with open(html_file, "w", encoding="utf-8") as f:
f.write(html_output)
- print(f"✓ HTML 输出已保存: {html_file}")
+ print(f"HTML: {html_file}")
- # Markdown 格式
md_renderer = MarkdownRenderer()
md_output = md_renderer.render(processed_data)
md_file = example_dir / "output" / "session_output.md"
with open(md_file, "w", encoding="utf-8") as f:
f.write(md_output)
- print(f"✓ Markdown 输出已保存: {md_file}")
-
- # 预览前几条记录
- print("\n" + "=" * 60)
- print("解析结果预览(前3条):")
- print("=" * 60)
- for i, entry in enumerate(parsed_data[:3], 1):
- print(f"\n[记录 {i}]")
- print(f" 时间: {entry.get('timestamp', 'N/A')}")
- print(f" 发言者: {entry.get('speaker', 'N/A')}")
- print(f" 内容类型数: {len(entry.get('content', []))}")
- for content in entry.get('content', [])[:2]: # 只显示前2个内容
- print(f" - {content.get('type', 'unknown')}: {content.get('content', '')[:50]}...")
-
- print("\n" + "=" * 60)
- print("✓ 所有步骤完成!")
- print("=" * 60)
-
+ print(f"Markdown: {md_file}")
if __name__ == "__main__":
main()
diff --git a/examples/custom_plugin.py b/examples/custom_plugin.py
index dd96311..5f77c41 100644
--- a/examples/custom_plugin.py
+++ b/examples/custom_plugin.py
@@ -1,14 +1,7 @@
-#!/usr/bin/env python3
-"""
-自定义插件示例
-演示如何创建和使用自定义插件来扩展 ConventionalRP 的功能
-"""
-
import sys
from typing import List, Dict, Any
from pathlib import Path
-# 添加 src 目录到 Python 路径
project_root = Path(__file__).parent.parent
sys.path.insert(0, str(project_root / "src"))
@@ -17,21 +10,12 @@ from conventionalrp.core.processor import Processor
class DiceRollAnalyzer:
- """骰子统计分析插件"""
+ """骰点统计分析插件"""
def __init__(self):
self.name = "Dice Roll Analyzer"
def analyze(self, parsed_data: List[Dict[str, Any]]) -> Dict[str, Any]:
- """
- 分析日志中的所有骰子投掷
-
- Args:
- parsed_data: 解析后的日志数据
-
- Returns:
- 统计结果
- """
stats = {
"total_rolls": 0,
"by_character": {},
@@ -65,15 +49,6 @@ class DialogueExtractor:
self.name = "Dialogue Extractor"
def extract(self, parsed_data: List[Dict[str, Any]]) -> List[Dict[str, str]]:
- """
- 提取所有角色对话
-
- Args:
- parsed_data: 解析后的日志数据
-
- Returns:
- 对话列表
- """
dialogues = []
for entry in parsed_data:
@@ -92,59 +67,38 @@ class DialogueExtractor:
def main():
- print("=" * 60)
- print("ConventionalRP 自定义插件示例")
- print("=" * 60)
-
- # 准备数据
example_dir = Path(__file__).parent
rules_file = example_dir / "rules" / "dnd5e_rules.json5"
log_file = example_dir / "logs" / "combat_log.txt"
-
- print("\n[1] 解析日志...")
+
+ print("\nLoading log...")
parser = Parser()
parser.load_rules(str(rules_file))
parsed_data = parser.parse_log(str(log_file))
- print(f"✓ 解析完成,共 {len(parsed_data)} 条记录")
+ print(f"Done, {len(parsed_data)} in total")
- # 使用骰子分析插件
- print("\n[2] 运行骰子统计分析插件...")
dice_analyzer = DiceRollAnalyzer()
dice_stats = dice_analyzer.analyze(parsed_data)
-
- print(f"\n骰子统计结果:")
+
+ print(f"\nStatistics:")
print(f" 总投掷次数: {dice_stats['total_rolls']}")
print(f"\n 按角色统计:")
for character, count in dice_stats['by_character'].items():
print(f" {character}: {count} 次")
- print(f"\n 按骰子类型统计:")
+ print(f"\n Statistics in Dice Types:")
for dice_type, count in dice_stats['dice_types'].items():
- print(f" d{dice_type}: {count} 次")
+ print(f" d{dice_type}: {count} times")
- # 使用对话提取插件
- print("\n[3] 运行对话提取插件...")
dialogue_extractor = DialogueExtractor()
dialogues = dialogue_extractor.extract(parsed_data)
-
- print(f"\n提取到 {len(dialogues)} 条对话:")
- for i, dialogue in enumerate(dialogues[:5], 1): # 只显示前5条
+
+ print(f"\nExtracted {len(dialogues)} dialogues:")
+ for i, dialogue in enumerate(dialogues[:5], 1): # Only show the first 5
print(f"\n [{i}] {dialogue['speaker']} ({dialogue['timestamp']})")
print(f" {dialogue['dialogue']}")
if len(dialogues) > 5:
- print(f"\n ... 还有 {len(dialogues) - 5} 条对话")
-
- print("\n" + "=" * 60)
- print("✓ 插件演示完成!")
- print("=" * 60)
- print("\n提示: 你可以创建自己的插件来实现:")
- print(" - 战斗统计分析")
- print(" - 角色行为分析")
- print(" - 关键词提取")
- print(" - 情感分析")
- print(" - 自动摘要生成")
- print(" - ... 以及更多!")
-
+ print(f"\n ... and {len(dialogues) - 5} more dialogues")
if __name__ == "__main__":
main()
diff --git a/examples/plugin_system_demo.py b/examples/plugin_system_demo.py
new file mode 100644
index 0000000..1899b34
--- /dev/null
+++ b/examples/plugin_system_demo.py
@@ -0,0 +1,173 @@
+import sys
+import os
+from pathlib import Path
+
+sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
+
+from conventionalrp.plugins import PluginManager
+from conventionalrp.core.parser import Parser
+
+sys.path.insert(0, str(Path(__file__).parent / "plugins"))
+from dice_analyzer_plugin import DiceAnalyzerPlugin
+from combat_tracker_plugin import CombatTrackerPlugin
+
+
+def demo_basic_plugin_usage():
+ manager = PluginManager()
+
+ dice_analyzer = DiceAnalyzerPlugin()
+ dice_analyzer.initialize()
+ manager.register_plugin(dice_analyzer)
+
+ combat_tracker = CombatTrackerPlugin()
+ combat_tracker.initialize()
+ manager.register_plugin(combat_tracker)
+
+ print("\nRegistered Plugins:")
+ for plugin_info in manager.list_plugins():
+ print(f" - {plugin_info['name']} v{plugin_info['version']} (type: {plugin_info['type']})")
+
+ stats = manager.get_statistics()
+ print(f"\nPlugin Statistics: {stats}")
+
+
+def demo_processor_plugin():
+ manager = PluginManager()
+
+ combat_tracker = CombatTrackerPlugin()
+ combat_tracker.initialize()
+ manager.register_plugin(combat_tracker)
+
+ combat_log = [
+ {"type": "dialogue", "speaker": "战士", "content": "我攻击兽人,造成12点伤害!"},
+ {"type": "dialogue", "speaker": "法师", "content": "火球术!造成28点火焰伤害"},
+ {"type": "dialogue", "speaker": "牧师", "content": "治疗之光,恢复15点生命值"},
+ {"type": "dialogue", "speaker": "战士", "content": "重击!造成18点伤害"},
+ {"type": "dialogue", "speaker": "牧师", "content": "群体治疗,恢复10点生命值"},
+ ]
+
+ print("\nProcessing Combat Log:")
+ from conventionalrp.plugins.base import ProcessorPlugin
+ processed_log = manager.execute_plugins(combat_log, plugin_type=ProcessorPlugin)
+
+ for token in processed_log:
+ if "combat_data" in token:
+ print(f" {token['speaker']}: {token['content']}")
+ print(f" -> {token['combat_data']}")
+
+ print("\nCombat Summary:")
+ summary = combat_tracker.get_combat_summary()
+ print(f" 总伤害: {summary['total_damage']}")
+ print(f" 总治疗: {summary['total_healing']}")
+ print(f" 净伤害: {summary['net_damage']}")
+ print("\n 角色统计:")
+ for character, stats in summary['character_stats'].items():
+ print(f" {character}: 造成伤害={stats['damage_dealt']}, 治疗={stats['healing_done']}")
+
+
+def demo_analyzer_plugin():
+ manager = PluginManager()
+
+ dice_analyzer = DiceAnalyzerPlugin()
+ dice_analyzer.initialize()
+ manager.register_plugin(dice_analyzer)
+
+ dice_rolls = [
+ {"type": "dice", "content": "d20=15", "result": 15},
+ {"type": "success", "content": "检定成功"},
+ {"type": "dice", "content": "d6=4", "result": 4},
+ {"type": "dice", "content": "d20=20", "result": 20},
+ {"type": "success", "content": "大成功!Critical hit!"},
+ {"type": "dice", "content": "d20=1", "result": 1},
+ {"type": "failure", "content": "大失败..."},
+ {"type": "dice", "content": "d20=12", "result": 12},
+ {"type": "dice", "content": "d6=3", "result": 3},
+ {"type": "success", "content": "检定成功"},
+ ]
+
+ print("\nDice Roll Data:")
+ for roll in dice_rolls:
+ if roll["type"] == "dice":
+ print(f" {roll['content']}")
+
+ from conventionalrp.plugins.base import AnalyzerPlugin
+ analysis = manager.execute_plugins(dice_rolls, plugin_type=AnalyzerPlugin)
+
+ print("\nAnalyze result:")
+ print(f" 总投掷次数: {analysis['total_rolls']}")
+ print(f" 骰子类型分布: {analysis['dice_types']}")
+ print(f" 成功次数: {analysis['success_count']}")
+ print(f" 失败次数: {analysis['failure_count']}")
+ print(f" 大成功次数: {analysis['critical_hits']}")
+ print(f" 大失败次数: {analysis['critical_fails']}")
+ print(f" 成功率: {analysis['success_rate']:.1%}")
+ print(f" 出现极值比率: {analysis['critical_rate']:.1%}")
+
+
+def demo_plugin_enable_disable():
+ manager = PluginManager()
+ dice_analyzer = DiceAnalyzerPlugin()
+ dice_analyzer.initialize()
+ manager.register_plugin(dice_analyzer)
+
+ combat_tracker = CombatTrackerPlugin()
+ combat_tracker.initialize()
+ manager.register_plugin(combat_tracker)
+
+ print("\nInitial State:")
+ for plugin_info in manager.list_plugins():
+ print(f" {plugin_info['name']}: {'Enabled' if plugin_info['enabled'] else 'Disabled'}")
+
+ # Disable DiceAnalyzer
+ print("\nDisabling DiceAnalyzer...")
+ manager.disable_plugin("DiceAnalyzer")
+
+ print("\nCurrent State:")
+ for plugin_info in manager.list_plugins():
+ print(f" {plugin_info['name']}: {'Enabled' if plugin_info['enabled'] else 'Disabled'}")
+
+ print("\nRe-enabling DiceAnalyzer...")
+ manager.enable_plugin("DiceAnalyzer")
+
+ print("\nFinal State:")
+ for plugin_info in manager.list_plugins():
+ print(f" {plugin_info['name']}: {'Enabled' if plugin_info['enabled'] else 'Disabled'}")
+
+
+def demo_plugin_discovery():
+ plugin_dir = Path(__file__).parent / "plugins"
+ manager = PluginManager(plugin_dirs=[str(plugin_dir)])
+
+ print(f"\nSearching for plugins in directory: {plugin_dir}")
+ discovered = manager.discover_plugins()
+
+ print(f"\nFound {len(discovered)} plugin modules:")
+ for module in discovered:
+ print(f" - {module}")
+
+ print("\nLoading plugins...")
+ for py_file in plugin_dir.glob("*.py"):
+ if py_file.name.startswith("_"):
+ continue
+
+ plugin_class = manager.load_plugin_from_file(str(py_file))
+ if plugin_class:
+ print(f" ✓ Successfully loaded: {py_file.name}")
+
+
+def main():
+ try:
+ demo_basic_plugin_usage()
+ demo_processor_plugin()
+ demo_analyzer_plugin()
+ demo_plugin_enable_disable()
+ demo_plugin_discovery()
+
+ except Exception as e:
+ print(f"\n{e!r}: {e}")
+ import traceback
+ traceback.print_exc()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/plugins/combat_tracker_plugin.py b/examples/plugins/combat_tracker_plugin.py
new file mode 100644
index 0000000..a12bc9e
--- /dev/null
+++ b/examples/plugins/combat_tracker_plugin.py
@@ -0,0 +1,96 @@
+import sys
+import os
+from pathlib import Path
+import re
+from typing import Any, Dict, List
+
+sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
+
+from conventionalrp.plugins.base import ProcessorPlugin
+
+
+class CombatTrackerPlugin(ProcessorPlugin):
+ """战斗数据追踪插件"""
+
+ def __init__(self):
+ super().__init__("CombatTracker", "1.0.0")
+ self.damage_pattern = re.compile(r'(\d+)\s*点?(伤害|damage|dmg)', re.IGNORECASE)
+ self.heal_pattern = re.compile(r'(\d+)\s*点?(治疗|healing|heal)', re.IGNORECASE)
+
+ def initialize(self, config: Dict[str, Any] | None = None):
+ self.config = config or {}
+ self.total_damage = 0
+ self.total_healing = 0
+ self.character_stats = {}
+ self.logger.info("CombatTrackerPlugin initialized")
+
+ def process_token(self, token: Dict[str, Any]) -> Dict[str, Any]:
+ content = token.get("content", "")
+ speaker = token.get("speaker", "Unknown")
+
+ damage_match = self.damage_pattern.search(content)
+ if damage_match:
+ damage = int(damage_match.group(1))
+ self.total_damage += damage
+
+ if speaker not in self.character_stats:
+ self.character_stats[speaker] = {"damage_dealt": 0, "healing_done": 0}
+ self.character_stats[speaker]["damage_dealt"] += damage
+
+ token["combat_data"] = {
+ "type": "damage",
+ "amount": damage,
+ "total_damage": self.total_damage,
+ }
+
+ heal_match = self.heal_pattern.search(content)
+ if heal_match:
+ healing = int(heal_match.group(1))
+ self.total_healing += healing
+
+ if speaker not in self.character_stats:
+ self.character_stats[speaker] = {"damage_dealt": 0, "healing_done": 0}
+ self.character_stats[speaker]["healing_done"] += healing
+
+ token["combat_data"] = {
+ "type": "healing",
+ "amount": healing,
+ "total_healing": self.total_healing,
+ }
+
+ return token
+
+ def get_combat_summary(self) -> Dict[str, Any]:
+ return {
+ "total_damage": self.total_damage,
+ "total_healing": self.total_healing,
+ "net_damage": self.total_damage - self.total_healing,
+ "character_stats": self.character_stats,
+ }
+
+ def reset_stats(self):
+ self.total_damage = 0
+ self.total_healing = 0
+ self.character_stats.clear()
+ self.logger.info("Combat stats reset")
+
+
+if __name__ == "__main__":
+ plugin = CombatTrackerPlugin()
+ plugin.initialize()
+
+ test_tokens = [
+ {"type": "dialogue", "speaker": "战士", "content": "我攻击兽人,造成12点伤害"},
+ {"type": "dialogue", "speaker": "法师", "content": "火球术命中,造成28点伤害"},
+ {"type": "dialogue", "speaker": "牧师", "content": "治疗术,恢复15点生命值"},
+ {"type": "dialogue", "speaker": "战士", "content": "再次攻击,造成8点伤害"},
+ ]
+
+ for token in test_tokens:
+ processed = plugin.process_token(token)
+ if "combat_data" in processed:
+ print(f" {processed['speaker']}: {processed['combat_data']}")
+
+ summary = plugin.get_combat_summary()
+ for key, value in summary.items():
+ print(f" {key}: {value}")
diff --git a/examples/plugins/dice_analyzer_plugin.py b/examples/plugins/dice_analyzer_plugin.py
new file mode 100644
index 0000000..3ab2fb9
--- /dev/null
+++ b/examples/plugins/dice_analyzer_plugin.py
@@ -0,0 +1,90 @@
+import sys
+import os
+from pathlib import Path
+import re
+from typing import Any, Dict, List
+
+sys.path.insert(0, str(Path(__file__).parent.parent.parent / "src"))
+
+from conventionalrp.plugins.base import AnalyzerPlugin
+
+
+class DiceAnalyzerPlugin(AnalyzerPlugin):
+ """骰子投掷数据分析插件"""
+ def __init__(self):
+ super().__init__("DiceAnalyzer", "1.0.0")
+ self.dice_pattern = re.compile(r'd(\d+)')
+
+ def initialize(self, config: Dict[str, Any] | None = None):
+ self.config = config or {}
+ self.logger.info("DiceAnalyzerPlugin initialized")
+
+ def analyze(self, data: Any) -> Dict[str, Any]:
+ if not isinstance(data, list):
+ return {"error": "Input must be a list of tokens"}
+
+ total_rolls = 0
+ dice_types = {}
+ success_count = 0
+ failure_count = 0
+ critical_hits = 0
+ critical_fails = 0
+
+ for token in data:
+ if not isinstance(token, dict):
+ continue
+
+ token_type = token.get("type", "")
+ content = token.get("content", "")
+
+ if token_type == "dice":
+ total_rolls += 1
+
+ match = self.dice_pattern.search(content)
+ if match:
+ dice_type = f"d{match.group(1)}"
+ dice_types[dice_type] = dice_types.get(dice_type, 0) + 1
+
+ if token_type == "success":
+ success_count += 1
+ elif token_type == "failure":
+ failure_count += 1
+
+ if "critical" in content.lower():
+ if "success" in token_type or "成功" in content:
+ critical_hits += 1
+ elif "failure" in token_type or "失败" in content:
+ critical_fails += 1
+
+ result = {
+ "total_rolls": total_rolls,
+ "dice_types": dice_types,
+ "success_count": success_count,
+ "failure_count": failure_count,
+ "critical_hits": critical_hits,
+ "critical_fails": critical_fails,
+ "success_rate": success_count / total_rolls if total_rolls > 0 else 0,
+ "critical_rate": (critical_hits + critical_fails) / total_rolls if total_rolls > 0 else 0,
+ }
+
+ self.logger.info(f"Analyzed {total_rolls} dice rolls")
+ return result
+
+
+if __name__ == "__main__":
+ plugin = DiceAnalyzerPlugin()
+ plugin.initialize()
+
+ test_data = [
+ {"type": "dice", "content": "d20=15"},
+ {"type": "success", "content": "检定成功"},
+ {"type": "dice", "content": "d6=4"},
+ {"type": "dice", "content": "d20=20"},
+ {"type": "success", "content": "大成功!Critical hit!"},
+ {"type": "dice", "content": "d20=1"},
+ {"type": "failure", "content": "大失败..."},
+ ]
+
+ result = plugin.analyze(test_data)
+ for key, value in result.items():
+ print(f" {key}: {value}")
diff --git a/examples/renderer_demo.py b/examples/renderer_demo.py
new file mode 100644
index 0000000..fa89b42
--- /dev/null
+++ b/examples/renderer_demo.py
@@ -0,0 +1,147 @@
+import sys
+import os
+from pathlib import Path
+
+# 添加父目录到 Python 路径
+sys.path.insert(0, str(Path(__file__).parent.parent / "src"))
+
+from conventionalrp.renderers.html_renderer import HTMLRenderer
+from conventionalrp.renderers.markdown_renderer import MarkdownRenderer
+from conventionalrp.renderers.json_renderer import JSONRenderer
+
+
+TEST_DATA = [
+ {
+ "type": "dialogue",
+ "speaker": "战士",
+ "content": "我们需要更小心地前进,前面可能有陷阱。",
+ "timestamp": "2024-01-15 20:30:15",
+ "tags": ["对话", "警告"],
+ },
+ {
+ "type": "dice",
+ "speaker": "战士",
+ "content": "d20+5",
+ "result": 18,
+ "timestamp": "2024-01-15 20:30:30",
+ },
+ {
+ "type": "success",
+ "content": "战士成功发现了隐藏的陷阱!",
+ "timestamp": "2024-01-15 20:30:35",
+ },
+ {
+ "type": "narration",
+ "content": "昏暗的走廊中,石板地面上隐约可见一些不寻常的纹路。",
+ "timestamp": "2024-01-15 20:31:00",
+ "tags": ["环境描述"],
+ },
+ {
+ "type": "dialogue",
+ "speaker": "法师",
+ "content": "让我施放侦测魔法,看看这里是否有魔法陷阱。",
+ "timestamp": "2024-01-15 20:31:15",
+ },
+ {
+ "type": "dice",
+ "speaker": "法师",
+ "content": "d20+8",
+ "result": 23,
+ "timestamp": "2024-01-15 20:31:20",
+ "combat_data": {"type": "damage", "amount": 12, "total_damage": 12},
+ },
+ {
+ "type": "system",
+ "content": "法师侦测到前方10英尺处有一个魔法陷阱(火球术触发)。",
+ "timestamp": "2024-01-15 20:31:25",
+ },
+]
+
+
+def demo_html_themes():
+ themes = ["light", "dark", "fantasy"]
+
+ for theme in themes:
+ print(f"\nGenerate {theme} theme...")
+ renderer = HTMLRenderer(theme=theme)
+ html_output = renderer.render(TEST_DATA)
+
+ output_file = f"output_html_{theme}.html"
+ with open(output_file, "w", encoding="utf-8") as f:
+ f.write(html_output)
+
+def demo_html_custom_css():
+ custom_css = """
+ body {
+ background-image: linear-gradient(120deg, #fdfbfb 0%, #ebedee 100%);
+ }
+
+ .token {
+ animation: fadeIn 0.5s;
+ }
+
+ @keyframes fadeIn {
+ from { opacity: 0; transform: translateY(20px); }
+ to { opacity: 1; transform: translateY(0); }
+ }
+ """
+
+ renderer = HTMLRenderer(theme="light", custom_css=custom_css)
+ html_output = renderer.render(TEST_DATA)
+
+ output_file = "output_html_custom.html"
+ with open(output_file, "w", encoding="utf-8") as f:
+ f.write(html_output)
+
+
+def demo_markdown_styles():
+ renderer_full = MarkdownRenderer(enable_syntax_hints=True, enable_emoji=True)
+ md_output_full = renderer_full.render(TEST_DATA)
+
+ with open("output_markdown_full.md", "w", encoding="utf-8") as f:
+ f.write(md_output_full)
+
+ renderer_simple = MarkdownRenderer(enable_syntax_hints=False, enable_emoji=False)
+ md_output_simple = renderer_simple.render(TEST_DATA)
+
+ with open("output_markdown_simple.md", "w", encoding="utf-8") as f:
+ f.write(md_output_simple)
+
+ print(md_output_full[:100] + "...")
+
+
+def demo_json_formats():
+ renderer_pretty = JSONRenderer(pretty=True, indent=2)
+ json_output_pretty = renderer_pretty.render(TEST_DATA)
+
+ with open("output_json_pretty.json", "w", encoding="utf-8") as f:
+ f.write(json_output_pretty)
+
+ renderer_compact = JSONRenderer(pretty=False)
+ json_output_compact = renderer_compact.render(TEST_DATA)
+
+ with open("output_json_compact.json", "w", encoding="utf-8") as f:
+ f.write(json_output_compact)
+
+ renderer_sorted = JSONRenderer(pretty=True, indent=4, sort_keys=True)
+ json_output_sorted = renderer_sorted.render(TEST_DATA)
+
+ with open("output_json_sorted.json", "w", encoding="utf-8") as f:
+ f.write(json_output_sorted)
+
+def main():
+
+ try:
+ demo_html_themes()
+ demo_html_custom_css()
+ demo_markdown_styles()
+ demo_json_formats()
+
+ except Exception as e:
+ print(f"\n{e!r}: {e}")
+ import traceback
+ traceback.print_exc()
+
+
+if __name__ == "__main__":
+ main()
diff --git a/examples/rule_system_demo.py b/examples/rule_system_demo.py
new file mode 100644
index 0000000..d38056b
--- /dev/null
+++ b/examples/rule_system_demo.py
@@ -0,0 +1,210 @@
+import sys
+from pathlib import Path
+
+project_root = Path(__file__).parent.parent
+sys.path.insert(0, str(project_root / "src"))
+
+from conventionalrp.core.rules import Rule, RuleEngine
+from conventionalrp.core.processor import Processor
+
+
+def example_1_simple_rules():
+ rule = Rule(
+ name="tag_dice_rolls",
+ condition={"type": "dice_roll"},
+ action={"type": "add_tag", "tag": "game_mechanics"},
+ priority=100
+ )
+
+ engine = RuleEngine()
+ engine.add_rule(rule)
+
+ data = {"type": "dice_roll", "content": "[d20 = 18]"}
+
+ print(f"Original data: {data}")
+ result = engine.process(data)
+ print(f"Processed result: {result}")
+ print()
+
+
+def example_2_conditional_rules():
+ engine = RuleEngine()
+
+ # Rule1: High rolls (>=15) get "success" tag
+ engine.add_rule_dict(
+ name="high_roll",
+ condition={
+ "type": "dice_roll",
+ "result": {"type": "greater_than", "value": 15}
+ },
+ action={"type": "add_tag", "tag": "success"},
+ priority=100
+ )
+
+ # Rule2: Low rolls (<10) get "failure" tag
+ engine.add_rule_dict(
+ name="low_roll",
+ condition={
+ "type": "dice_roll",
+ "result": {"type": "less_than", "value": 10}
+ },
+ action={"type": "add_tag", "tag": "failure"},
+ priority=100
+ )
+
+ test_cases = [
+ {"type": "dice_roll", "result": 18, "content": "[d20 = 18]"},
+ {"type": "dice_roll", "result": 5, "content": "[d20 = 5]"},
+ {"type": "dice_roll", "result": 12, "content": "[d20 = 12]"},
+ ]
+
+ for data in test_cases:
+ result = engine.process(data)
+ print(f"结果: {data['result']} -> 标签: {result.get('tags', [])}")
+ print()
+
+
+def example_3_field_transformation():
+ engine = RuleEngine()
+
+ # Rule: Normalize speaker names
+ engine.add_rule_dict(
+ name="normalize_speaker",
+ condition={"type": "metadata"},
+ action={
+ "type": "transform",
+ "field": "speaker",
+ "function": "upper"
+ },
+ priority=90
+ )
+
+ data = {
+ "type": "metadata",
+ "speaker": "艾莉娅",
+ "timestamp": "2025-10-24 14:30:01"
+ }
+
+ print(f"Original data: {data}")
+ result = engine.process(data)
+ print(f"Processed result: {result}")
+ print()
+
+
+def example_4_processor_with_rules():
+ processor = Processor()
+
+ # Add rule: Highlight important dialogues
+ processor.add_rule(Rule(
+ name="highlight_important_dialogue",
+ condition={
+ "type": "dialogue",
+ "content": {"type": "contains", "value": "重要"}
+ },
+ action={"type": "add_tag", "tag": "important"},
+ priority=100
+ ))
+
+ # Add rule: Mark all metadata as processed
+ processor.add_rule(Rule(
+ name="mark_metadata",
+ condition={"type": "metadata"},
+ action={"type": "set_field", "field": "processed_by", "value": "rule_engine"},
+ priority=90
+ ))
+
+ tokens = [
+ {"type": "metadata", "speaker": "DM", "timestamp": "2025-10-24"},
+ {"type": "dialogue", "content": "这是重要的线索"},
+ {"type": "dialogue", "content": "普通对话"},
+ {"type": "dice_roll", "result": 20},
+ ]
+
+ print(f"Processing {len(tokens)} tokens...")
+ results = processor.process_tokens(tokens)
+
+ for i, result in enumerate(results):
+ print(f" [{i+1}] {result.get('type')}: "
+ f"Tags={result.get('tags', [])} "
+ f"Processed by={result.get('processed_by', 'N/A')}")
+ print()
+
+
+def example_5_custom_processor():
+ processor = Processor()
+
+ # Custom processing function: Count characters
+ def add_char_count(data):
+ if "content" in data:
+ data["char_count"] = len(data["content"])
+ return data
+
+ # Custom processing function: Ensure timestamp exists
+ def ensure_timestamp(data):
+ if "timestamp" not in data:
+ from datetime import datetime
+ data["timestamp"] = datetime.now().isoformat()
+ return data
+
+ processor.add_processor(add_char_count)
+ processor.add_processor(ensure_timestamp)
+
+ test_data = [
+ {"type": "dialogue", "content": "你好世界"},
+ {"type": "text", "content": "这是一段很长的文本内容"},
+ ]
+
+ results = processor.process_tokens(test_data)
+
+ for result in results:
+ print(f" {result.get('type')}: "
+ f"Character count={result.get('char_count')} "
+ f"Timestamp={result.get('timestamp', 'N/A')[:19]}")
+ print()
+
+
+def example_6_priority_and_order():
+ engine = RuleEngine()
+
+ engine.add_rule_dict(
+ name="low_priority",
+ condition={"type": "test"},
+ action={"type": "set_field", "field": "processed_by", "value": "low"},
+ priority=10
+ )
+
+ engine.add_rule_dict(
+ name="high_priority",
+ condition={"type": "test"},
+ action={"type": "set_field", "field": "processed_by", "value": "high"},
+ priority=100
+ )
+
+ engine.add_rule_dict(
+ name="medium_priority",
+ condition={"type": "test"},
+ action={"type": "set_field", "field": "processed_by", "value": "medium"},
+ priority=50
+ )
+
+ data = {"type": "test"}
+
+ result1 = engine.process(data, apply_all=False)
+ print(f"Only apply the highest priority matching rule: {result1}")
+
+ result2 = engine.process(data, apply_all=True)
+ print(f"Apply all matching rules: {result2}")
+ print()
+
+
+def main():
+ example_1_simple_rules()
+ example_2_conditional_rules()
+ example_3_field_transformation()
+ example_4_processor_with_rules()
+ example_5_custom_processor()
+ example_6_priority_and_order()
+
+
+if __name__ == "__main__":
+ main()