aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/oneroll/__main__.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/oneroll/__main__.py')
-rw-r--r--src/oneroll/__main__.py335
1 files changed, 335 insertions, 0 deletions
diff --git a/src/oneroll/__main__.py b/src/oneroll/__main__.py
new file mode 100644
index 0000000..f454df8
--- /dev/null
+++ b/src/oneroll/__main__.py
@@ -0,0 +1,335 @@
+#!/usr/bin/env python3
+"""
+OneRoll interactive dice roll program
+
+Supports command line parameters and interactive mode.
+
+Example of usage:
+# Direct throw
+python -m oneroll "3d6 + 2"
+
+# Interactive mode
+python -m oneroll
+
+# Statistical Mode
+python -m oneroll --stats "3d6" --times 100
+"""
+
+import sys
+import argparse
+from typing import List, Dict, Any
+from rich.console import Console
+from rich.table import Table
+from rich.panel import Panel
+from rich.text import Text
+from rich.prompt import Prompt, Confirm
+from rich.progress import Progress, SpinnerColumn, TextColumn
+
+from . import OneRoll, roll, roll_simple, roll_multiple, roll_statistics, CommonRolls
+
+console = Console()
+
+class OneRollCLI:
+ """OneRoll command line interface"""
+
+ def __init__(self):
+ self.roller = OneRoll()
+ self.history: List[Dict[str, Any]] = []
+
+ def print_result(self, result: Dict[str, Any], expression: str = None):
+ """Pretty print the dice roll result"""
+ if expression is None:
+ expression = result.get('expression', 'Unknown')
+
+ # Create result panel
+ total = result['total']
+ details = result['details']
+ rolls = result['rolls']
+
+ # Select color based on result value
+ if total >= 15:
+ color = "green"
+ elif total >= 10:
+ color = "yellow"
+ else:
+ color = "red"
+
+ # Build display text
+ text = Text()
+ text.append(f"🎲 {expression}\n", style="bold blue")
+ text.append(f"总点数: ", style="bold")
+ text.append(f"{total}", style=f"bold {color}")
+ text.append(f"\n详情: {details}", style="white")
+
+ if rolls:
+ text.append(f"\n投掷结果: ", style="bold")
+ text.append(f"{rolls}", style="cyan")
+
+ # Display comment
+ comment = result.get("comment", "")
+ if comment:
+ text.append(f"\n注释: ", style="bold")
+ text.append(f"{comment}", style="italic blue")
+
+ panel = Panel(text, title="Dice Roll Result", border_style=color)
+ console.print(panel)
+
+ def print_statistics(self, stats: Dict[str, Any], expression: str):
+ """Print statistics information"""
+ table = Table(title=f"统计结果: {expression} (投掷 {stats['count']} 次)")
+ table.add_column("统计项", style="cyan")
+ table.add_column("数值", style="green")
+
+ table.add_row("最小值", str(stats['min']))
+ table.add_row("最大值", str(stats['max']))
+ table.add_row("平均值", f"{stats['mean']:.2f}")
+ table.add_row("总和", str(stats['total']))
+ table.add_row("投掷次数", str(stats['count']))
+
+ console.print(table)
+
+ def print_history(self):
+ """Print dice roll history"""
+ if not self.history:
+ console.print("暂无投掷历史", style="yellow")
+ return
+
+ table = Table(title="投掷历史")
+ table.add_column("序号", style="cyan")
+ table.add_column("表达式", style="green")
+ table.add_column("总点数", style="yellow")
+ table.add_column("注释", style="blue")
+ table.add_column("详情", style="white")
+
+ for i, result in enumerate(self.history[-10:], 1): # only show last 30 times
+ table.add_row(
+ str(i),
+ result.get('expression', 'Unknown'),
+ str(result['total']),
+ result.get('comment', ''),
+ result['details']
+ )
+
+ console.print(table)
+
+ def show_help(self):
+ """show help information"""
+ help_text = """
+🎲 OneRoll 骰子表达式解析器
+
+支持的表达式格式:
+• 基本骰子: 3d6, 1d20, 2d10
+• 数学运算: 3d6 + 2, 2d6 * 3, (2d6 + 3) * 2
+• 修饰符:
+ - ! 爆炸骰子: 2d6!
+ - kh 取高: 4d6kh3
+ - kl 取低: 4d6kl2
+ - dh 丢弃高: 5d6dh1
+ - dl 丢弃低: 5d6dl1
+ - r 重投: 3d6r1
+ - ro 条件重投: 4d6ro1
+• 注释: 在表达式末尾使用 # 添加注释
+
+常用命令:
+• help - 显示帮助
+• history - 显示投掷历史
+• stats <表达式> <次数> - 统计投掷
+• clear - 清空历史
+• quit/exit - 退出程序
+
+常用表达式:
+• d20 - 1d20
+• advantage - 2d20kh1 (优势)
+• disadvantage - 2d20kl1 (劣势)
+• attribute - 4d6kh3 (属性投掷)
+ """
+
+ console.print(Panel(help_text, title="帮助信息", border_style="blue"))
+
+ def interactive_mode(self):
+ """Interactive Mode"""
+ console.print(Panel.fit("🎲 OneRoll 交互式掷骰程序", style="bold blue"))
+ console.print("输入 'help' 查看帮助,输入 'quit' 退出程序\n")
+
+ while True:
+ try:
+ user_input = Prompt.ask("🎲 请输入骰子表达式").strip()
+
+ if not user_input:
+ continue
+
+ # handle special command
+ if user_input.lower() in ['quit', 'exit', 'q']:
+ if Confirm.ask("确定要退出吗?"):
+ break
+ continue
+
+ if user_input.lower() == 'help':
+ self.show_help()
+ continue
+
+ if user_input.lower() == 'history':
+ self.print_history()
+ continue
+
+ if user_input.lower() == 'clear':
+ self.history.clear()
+ console.print("历史已清空", style="green")
+ continue
+
+ if user_input.lower().startswith('stats '):
+ parts = user_input.split()
+ if len(parts) >= 3:
+ expression = parts[1]
+ try:
+ times = int(parts[2])
+ if times > 1000:
+ console.print("统计次数不能超过1000次", style="red")
+ continue
+
+ with Progress(
+ SpinnerColumn(),
+ TextColumn("[progress.description]{task.description}"),
+ console=console
+ ) as progress:
+ task = progress.add_task(f"正在统计 {expression}...", total=None)
+ stats = roll_statistics(expression, times)
+ progress.stop()
+
+ self.print_statistics(stats, expression)
+ except ValueError:
+ console.print("统计次数必须是数字", style="red")
+ except Exception as e:
+ console.print(f"统计错误: {e}", style="red")
+ else:
+ console.print("用法: stats <表达式> <次数>", style="red")
+ continue
+
+ # resolve regular expression's alias
+ expression = self._resolve_expression_alias(user_input)
+
+ # execute roll
+ try:
+ result = roll(expression)
+ self.history.append(result)
+ self.print_result(result, user_input)
+ except Exception as e:
+ console.print(f"错误: {e}", style="red")
+
+ except KeyboardInterrupt:
+ if Confirm.ask("\n确定要退出吗?"):
+ break
+ except EOFError:
+ break
+
+ def _resolve_expression_alias(self, user_input: str) -> str:
+ """resolve regular expression's alias"""
+ aliases = {
+ 'd20': CommonRolls.D20,
+ 'advantage': CommonRolls.D20_ADVANTAGE,
+ 'disadvantage': CommonRolls.D20_DISADVANTAGE,
+ 'attr': CommonRolls.ATTRIBUTE_ROLL,
+ 'attribute': CommonRolls.ATTRIBUTE_ROLL,
+ }
+
+ return aliases.get(user_input.lower(), user_input)
+
+ def run(self, args):
+ """run tui mode"""
+ if args.tui:
+ # start tui
+ try:
+ from .tui import run_tui
+ run_tui()
+ except ImportError:
+ console.print("TUI 模式需要安装 textual: pip install textual", style="red")
+ sys.exit(1)
+ except Exception as e:
+ console.print(f"TUI 启动失败: {e}", style="red")
+ sys.exit(1)
+
+ elif args.expression:
+ # single roll mode
+ try:
+ result = roll(args.expression)
+ self.print_result(result)
+ except Exception as e:
+ console.print(f"错误: {e}", style="red")
+ sys.exit(1)
+
+ elif args.stats:
+ # stats mode
+ try:
+ times = args.times or 100
+ if times > 10000:
+ console.print("统计次数不能超过10000次", style="red")
+ sys.exit(1)
+
+ with Progress(
+ SpinnerColumn(),
+ TextColumn("[progress.description]{task.description}"),
+ console=console
+ ) as progress:
+ task = progress.add_task(f"正在统计 {args.stats}...", total=None)
+ stats = roll_statistics(args.stats, times)
+ progress.stop()
+
+ self.print_statistics(stats, args.stats)
+ except Exception as e:
+ console.print(f"错误: {e}", style="red")
+ sys.exit(1)
+
+ else:
+ # interactive mode
+ self.interactive_mode()
+
+def main():
+ parser = argparse.ArgumentParser(
+ description="OneRoll 骰子表达式解析器",
+ formatter_class=argparse.RawDescriptionHelpFormatter,
+ epilog="""
+使用示例:
+ %(prog)s "3d6 + 2" # 单次投掷
+ %(prog)s --stats "3d6" --times 100 # 统计模式
+ %(prog)s --tui # 终端用户界面
+ %(prog)s # 交互式模式
+ """
+ )
+
+ parser.add_argument(
+ 'expression',
+ nargs='?',
+ help='骰子表达式,如 "3d6 + 2"'
+ )
+
+ parser.add_argument(
+ '--stats',
+ help='统计模式,指定要统计的表达式'
+ )
+
+ parser.add_argument(
+ '--times',
+ type=int,
+ default=100,
+ help='统计次数,默认100次'
+ )
+
+ parser.add_argument(
+ '--version',
+ action='version',
+ version='OneRoll 0.0.1'
+ )
+
+ parser.add_argument(
+ '--tui',
+ action='store_true',
+ help='启动终端用户界面 (TUI)'
+ )
+
+ args = parser.parse_args()
+
+ cli = OneRollCLI()
+ cli.run(args)
+
+if __name__ == '__main__':
+ main() \ No newline at end of file