- 新增最终验证脚本和迁移测试脚本,确保DSL引擎的完整性和功能 - 实现故事模块的音频配置、角色定义和情感故事模块 - 迁移现有故事数据到新的DSL格式,支持动态内容和条件导航 - 更新主题和UI组件,确保一致的黑色背景和暗色主题 - 添加音频管理器和性能监控工具,提升游戏体验和调试能力
115 lines
3.4 KiB
Python
115 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
||
# -*- coding: utf-8 -*-
|
||
|
||
import argparse
|
||
import os
|
||
import re
|
||
from typing import Dict, List, Tuple
|
||
|
||
|
||
NODE_RE = re.compile(r'^\s*@node\s+(?P<id>[A-Za-z0-9_]+)\s*$')
|
||
TITLE_RE = re.compile(r'^\s*@title\s+"(?P<title>.*)"\s*$')
|
||
CHOICE_RE = re.compile(r'^\s*choice_\d+\s*:\s*"(?P<label>.*?)"\s*->\s*(?P<target>[A-Za-z0-9_]+)')
|
||
|
||
ENDING_ID_PREFIXES = (
|
||
'ending_',
|
||
)
|
||
ENDING_TITLE_KEYWORDS = (
|
||
'结局', '最终', '终极', '拯救', '守护', '和谐', '文明', '宽恕', '宇宙', '完美'
|
||
)
|
||
|
||
|
||
def parse_story(path: str) -> Tuple[Dict[str, str], Dict[str, List[Tuple[str, str]]]]:
|
||
with open(path, 'r', encoding='utf-8') as f:
|
||
lines = f.readlines()
|
||
|
||
nodes: Dict[str, str] = {}
|
||
edges: Dict[str, List[Tuple[str, str]]] = {}
|
||
|
||
current_id: str = ''
|
||
i = 0
|
||
n = len(lines)
|
||
while i < n:
|
||
line = lines[i].rstrip('\n')
|
||
m_node = NODE_RE.match(line)
|
||
if m_node:
|
||
current_id = m_node.group('id')
|
||
if current_id not in nodes:
|
||
nodes[current_id] = current_id
|
||
if current_id not in edges:
|
||
edges[current_id] = []
|
||
|
||
j = i + 1
|
||
while j < n:
|
||
l2 = lines[j].rstrip('\n')
|
||
if NODE_RE.match(l2):
|
||
break
|
||
m_title = TITLE_RE.match(l2)
|
||
if m_title:
|
||
title = m_title.group('title').strip()
|
||
if title:
|
||
nodes[current_id] = title
|
||
m_choice = CHOICE_RE.match(l2)
|
||
if m_choice:
|
||
label = m_choice.group('label').strip()
|
||
target = m_choice.group('target').strip()
|
||
edges.setdefault(current_id, []).append((label, target))
|
||
j += 1
|
||
|
||
i = j
|
||
continue
|
||
i += 1
|
||
|
||
return nodes, edges
|
||
|
||
|
||
def is_ending(node_id: str, title: str) -> bool:
|
||
if any(node_id.startswith(p) for p in ENDING_ID_PREFIXES):
|
||
return True
|
||
return any(k in (title or '') for k in ENDING_TITLE_KEYWORDS)
|
||
|
||
|
||
def main():
|
||
ap = argparse.ArgumentParser()
|
||
ap.add_argument('--input', required=True)
|
||
ap.add_argument('--output', required=True)
|
||
args = ap.parse_args()
|
||
|
||
nodes, edges = parse_story(args.input)
|
||
|
||
# Compute out-degree
|
||
outdeg = {nid: len(edges.get(nid, [])) for nid in nodes.keys()}
|
||
|
||
leaf_nodes = [nid for nid, deg in outdeg.items() if deg == 0]
|
||
|
||
endings = []
|
||
non_endings = []
|
||
for nid in sorted(leaf_nodes):
|
||
title = nodes.get(nid, '')
|
||
(endings if is_ending(nid, title) else non_endings).append((nid, title))
|
||
|
||
os.makedirs(os.path.dirname(args.output), exist_ok=True)
|
||
with open(args.output, 'w', encoding='utf-8') as f:
|
||
f.write('# 无后续(无 choices)节点清单\n\n')
|
||
f.write(f'- 总节点数: {len(nodes)}\n')
|
||
f.write(f'- 无后续节点数: {len(leaf_nodes)}\n')
|
||
f.write(f'- 结局型: {len(endings)}\n')
|
||
f.write(f'- 可能未接续: {len(non_endings)}\n\n')
|
||
|
||
f.write('## 结局型(Leaf Endings)\n')
|
||
for nid, title in endings:
|
||
f.write(f'- {title} | id: `{nid}`\n')
|
||
f.write('\n')
|
||
|
||
f.write('## 可能未接续(需人工确认是否应有后续)\n')
|
||
for nid, title in non_endings:
|
||
f.write(f'- {title} | id: `{nid}`\n')
|
||
|
||
print(f"Wrote report to {args.output}. Endings={len(endings)} NonEndings={len(non_endings)}")
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|
||
|
||
|