aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/src/oneroll/tui.py
diff options
context:
space:
mode:
author简律纯 <i@jyunko.cn>2025-09-12 04:02:02 +0800
committer简律纯 <i@jyunko.cn>2025-09-12 04:02:02 +0800
commit0288d0956330d5ac8db48b752240f723e8703929 (patch)
tree0297dee9f8166af0a856dd3a1057ad5f25f14c6a /src/oneroll/tui.py
parent5135876b5e2a6c40232414ea0b7eb875fa225cf0 (diff)
downloadOneRoll-0288d0956330d5ac8db48b752240f723e8703929.tar.gz
OneRoll-0288d0956330d5ac8db48b752240f723e8703929.zip
feat: initial basic roll features
Diffstat (limited to 'src/oneroll/tui.py')
-rw-r--r--src/oneroll/tui.py251
1 files changed, 251 insertions, 0 deletions
diff --git a/src/oneroll/tui.py b/src/oneroll/tui.py
new file mode 100644
index 0000000..9e4e4c9
--- /dev/null
+++ b/src/oneroll/tui.py
@@ -0,0 +1,251 @@
+#!/usr/bin/env python3
+"""
+OneRoll Terminal User Interface (TUI)
+
+An interactive dice roll interface created using textual.
+"""
+
+from textual.app import App, ComposeResult
+from textual.containers import Container, Horizontal, Vertical
+from textual.widgets import Header, Footer, Input, Button, Static, DataTable, Tabs, Tab
+from textual.reactive import reactive
+from textual.message import Message
+from typing import List, Dict, Any
+import json
+from datetime import datetime
+
+from . import OneRoll, roll, roll_statistics, CommonRolls
+
+class RollResult(Message):
+ """Throw result message"""
+
+ def __init__(self, result: Dict[str, Any], expression: str) -> None:
+ self.result = result
+ self.expression = expression
+ super().__init__()
+
+class ExpressionInput(Input):
+ """Expression input box"""
+
+ def __init__(self) -> None:
+ super().__init__(
+ placeholder="输入骰子表达式,如 3d6 + 2",
+ id="expression_input"
+ )
+
+ def on_key(self, event) -> None:
+ if event.key == "enter":
+ self.post_message(RollResult(roll(self.value), self.value))
+ self.value = ""
+
+class QuickRollButton(Button):
+ """Quick Throw Button"""
+
+ def __init__(self, label: str, expression: str) -> None:
+ self.expression = expression
+ super().__init__(label, id=f"quick_{expression}")
+
+ def on_button_pressed(self) -> None:
+ self.post_message(RollResult(roll(self.expression), self.expression))
+
+class RollHistory(DataTable):
+ """Throw History Table"""
+
+ def __init__(self) -> None:
+ super().__init__()
+ self.add_columns("时间", "表达式", "总点数", "详情")
+
+ def add_roll(self, result: Dict[str, Any], expression: str) -> None:
+ """Add throw record"""
+ time_str = datetime.now().strftime("%H:%M:%S")
+ self.add_row(
+ time_str,
+ expression,
+ str(result["total"]),
+ result["details"]
+ )
+ # keep table at bottom
+ self.scroll_end()
+
+class StatisticsPanel(Static):
+ """Statistics Panel"""
+
+ def __init__(self) -> None:
+ super().__init__("统计功能", id="stats_panel")
+
+ def show_statistics(self, expression: str, times: int = 100) -> None:
+ """Show statistics information"""
+ try:
+ stats = roll_statistics(expression, times)
+ stats_text = f"""
+统计结果: {expression} (投掷 {stats['count']} 次)
+
+最小值: {stats['min']}
+最大值: {stats['max']}
+平均值: {stats['mean']:.2f}
+总和: {stats['total']}
+ """
+ self.update(stats_text)
+ except Exception as e:
+ self.update(f"统计错误: {e}")
+
+class RollDisplay(Static):
+ """The throwing result shows """
+
+ def __init__(self) -> None:
+ super().__init__("等待投掷...", id="roll_display")
+
+ def show_result(self, result: Dict[str, Any], expression: str) -> None:
+ """Display throwing result"""
+ total = result["total"]
+ details = result["details"]
+ rolls = result["rolls"]
+
+ # a kind of color selection based on result
+ if total >= 15:
+ color = "green"
+ elif total >= 10:
+ color = "yellow"
+ else:
+ color = "red"
+
+ display_text = f"""[bold blue]🎲 {expression}[/bold blue]
+
+[bold]总点数:[/bold] [bold {color}]{total}[/bold {color}]
+[bold]详情:[/bold] {details}
+
+[bold]投掷结果:[/bold] {rolls}"""
+
+ # show the comment
+ comment = result.get("comment", "")
+ if comment:
+ display_text += f"\n\n[bold]注释:[/bold] [italic blue]{comment}[/italic blue]"
+
+ self.update(display_text)
+
+class OneRollTUI(App):
+ """OneRoll 终端用户界面"""
+
+ CSS = """
+ Screen {
+ layout: vertical;
+ }
+
+ #expression_input {
+ margin: 1;
+ }
+
+ #roll_display {
+ height: 8;
+ border: solid $primary;
+ margin: 1;
+ padding: 1;
+ }
+
+ #stats_panel {
+ height: 8;
+ border: solid $secondary;
+ margin: 1;
+ padding: 1;
+ }
+
+ #history_table {
+ height: 10;
+ margin: 1;
+ }
+
+ .quick_buttons {
+ height: 3;
+ margin: 1;
+ }
+
+ Button {
+ margin: 1;
+ }
+ """
+
+ def compose(self) -> ComposeResult:
+ """UI components"""
+ yield Header()
+
+ with Container():
+ yield ExpressionInput()
+
+ with Horizontal(classes="quick_buttons"):
+ yield QuickRollButton("D20", CommonRolls.D20)
+ yield QuickRollButton("优势", CommonRolls.D20_ADVANTAGE)
+ yield QuickRollButton("劣势", CommonRolls.D20_DISADVANTAGE)
+ yield QuickRollButton("属性", CommonRolls.ATTRIBUTE_ROLL)
+ yield QuickRollButton("3D6", "3d6")
+ yield QuickRollButton("2D6", "2d6")
+
+ yield RollDisplay()
+
+ with Tabs():
+ with Tab("历史记录", id="history_tab"):
+ yield RollHistory(id="history_table")
+
+ with Tab("统计", id="stats_tab"):
+ yield StatisticsPanel()
+
+ yield Footer()
+
+ def on_mount(self) -> None:
+ """Initialization during interface mount"""
+ self.title = "OneRoll 骰子投掷器"
+ self.sub_title = "高性能骰子表达式解析器"
+
+ # Set focus to the input box
+ self.query_one(ExpressionInput).focus()
+
+ def on_roll_result(self, message: RollResult) -> None:
+ # display result
+ roll_display = self.query_one(RollDisplay)
+ roll_display.show_result(message.result, message.expression)
+
+ # add history
+ history_table = self.query_one(RollHistory)
+ history_table.add_roll(message.result, message.expression)
+
+ def on_key(self, event) -> None:
+ if event.key == "ctrl+q":
+ self.exit()
+ elif event.key == "ctrl+h":
+ self.show_help()
+ elif event.key == "ctrl+s":
+ self.show_statistics()
+
+ def show_help(self) -> None:
+ 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
+
+快捷键:
+• Ctrl+Q - 退出程序
+• Ctrl+H - 显示帮助
+• Ctrl+S - 显示统计
+• Enter - 执行投掷
+ """
+
+ self.notify(help_text, title="帮助信息", timeout=10)
+
+ def show_statistics(self) -> None:
+ self.notify("统计功能开发中...", title="统计")
+
+def run_tui():
+ app = OneRollTUI()
+ app.run()
+
+if __name__ == "__main__":
+ run_tui()