aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
author简律纯 <i@jyunko.cn>2025-09-13 02:34:14 +0800
committer简律纯 <i@jyunko.cn>2025-09-13 02:34:14 +0800
commit00670619f3a9ac80683f1a1d751349e010c602dc (patch)
tree242ccdbede9b38099ed773ea5ab725ded053843f
parent93d462c41cf0ed359475f07578bac511a329c580 (diff)
downloadOneRoll-00670619f3a9ac80683f1a1d751349e010c602dc.tar.gz
OneRoll-00670619f3a9ac80683f1a1d751349e010c602dc.zip
feat: introduce new dice modifiers for grouping and merging, enhance variable references and instruction sequences in parser
-rw-r--r--README.md16
-rw-r--r--src/calculator.rs94
-rw-r--r--src/oneroll/grammar.pest14
-rw-r--r--src/parser.rs59
-rw-r--r--src/types.rs5
5 files changed, 183 insertions, 5 deletions
diff --git a/README.md b/README.md
index edfca7a..8d31755 100644
--- a/README.md
+++ b/README.md
@@ -76,9 +76,11 @@ Dice Expression Syntax
----------------------
- `XdY`: Roll X dice with Y sides
-- Modifiers: `kh`, `kl`, `dh`, `dl`, `!`, `r`, `ro`
+- Modifiers: `kh`, `kl`, `dh`, `dl`, `!`, `e`, `r`, `ro`, `R`, `a`, `u`, `s`, `c`, `m`, `g`, `gs`
- Mathematical operations: `+`, `-`, `*`, `/`, `^`
- Comments: Add with `#`, e.g., `3d6 + 2 # Attack roll`
+- Instruction sequences: Use `;` to separate multiple instructions
+- Variable references: Use `$n` to reference the result of the nth instruction
Examples
--------
@@ -96,6 +98,18 @@ stats = oneroll.roll_statistics("3d6", 100)
# Comment usage
result = oneroll.roll("1d20 + 5 # Attack check")
print(result["comment"])
+
+# Instruction sequences
+result = oneroll.roll("3d6; 1d20; 2d8")
+
+# Variable references
+result = oneroll.roll("3d6; $1c6") # Count 6s in first roll
+
+# Group modifier
+result = oneroll.roll("4d6g10") # Count groups >= 10
+
+# Count modifier
+result = oneroll.roll("5d6c6") # Count occurrences of 6
```
Documentation
diff --git a/src/calculator.rs b/src/calculator.rs
index 07518b0..39934ad 100644
--- a/src/calculator.rs
+++ b/src/calculator.rs
@@ -154,6 +154,38 @@ impl DiceCalculator {
let count = values.iter().filter(|&&v| v == *target).count() as i32;
final_rolls = vec![vec![count]];
}
+ DiceModifier::Merge => {
+ // Merge with previous instruction rolls stored as $prev_rolls not yet available
+ // For MVP: no-op here; actual merge handled at sequence level
+ }
+ DiceModifier::Group { threshold, show_structure } => {
+ // Greedy grouping to reach >= threshold using sorted descending values
+ let mut values: Vec<i32> = final_rolls.iter().flatten().cloned().collect();
+ values.sort_by(|a, b| b.cmp(a));
+ let mut groups: Vec<Vec<i32>> = Vec::new();
+ let mut current: Vec<i32> = Vec::new();
+ let mut current_sum = 0;
+ for v in values {
+ current.push(v);
+ current_sum += v;
+ if current_sum >= *threshold {
+ groups.push(current.clone());
+ current.clear();
+ current_sum = 0;
+ }
+ }
+ let group_count = groups.len() as i32;
+ let mut details = format!("g{} -> {}", threshold, group_count);
+ if *show_structure {
+ details.push_str(": ");
+ details.push_str(&format!("{:?}", groups));
+ if !current.is_empty() {
+ details.push_str(&format!(" - {:?}", current));
+ }
+ }
+ // Represent as single result equal to number of groups
+ final_rolls = vec![vec![group_count]];
+ }
_ => {}
}
}
@@ -170,6 +202,31 @@ impl DiceCalculator {
details: format!("{}", n),
comment: None,
}),
+ Expression::VariableRef(index) => {
+ let key = format!("${}", index);
+ if let Some(value) = self.variables.get(&key) {
+ Ok(DiceResult {
+ expression: key.clone(),
+ total: value,
+ rolls: vec![vec![value]],
+ details: format!("var {} = {}", key, value),
+ comment: None,
+ })
+ } else {
+ Err(DiceError::InvalidExpression(format!("未定义的变量引用: {}", key)))
+ }
+ }
+ Expression::Sequence(items) => {
+ let mut last: Option<DiceResult> = None;
+ for (i, item) in items.iter().enumerate() {
+ let res = self.evaluate_expression(item)?;
+ // 将每条指令的总和放入变量存储,键为 $1, $2...
+ let var_name = format!("${}", i + 1);
+ self.variables.set(&var_name, res.total);
+ last = Some(res);
+ }
+ Ok(last.unwrap_or(DiceResult { expression: String::new(), total: 0, rolls: vec![], details: String::new(), comment: None }))
+ }
Expression::DiceRoll(dice) => {
let rolls = self.roll_dice(dice)?;
let total: i32 = rolls.iter().flatten().sum();
@@ -254,6 +311,35 @@ impl DiceCalculator {
result.comment = comment.clone();
Ok(result)
}
+ Expression::VariableRefWithModifiers(index, modifiers) => {
+ let key = format!("${}", index);
+ if let Some(value) = self.variables.get(&key) {
+ // Apply modifiers to the variable value
+ let mut rolls = vec![vec![value]];
+ for modifier in modifiers {
+ match modifier {
+ DiceModifier::Count(target) => {
+ // For count modifier on variable, we count how many times the variable equals target
+ let count = if value == *target { 1 } else { 0 };
+ rolls = vec![vec![count]];
+ }
+ _ => {
+ // For other modifiers, we'll need to implement them
+ // For now, just keep the original value
+ }
+ }
+ }
+ Ok(DiceResult {
+ expression: format!("${}{}", index, self.modifiers_to_string(modifiers)),
+ total: rolls.iter().flatten().sum(),
+ rolls,
+ details: format!("var ${} = {} with modifiers", index, value),
+ comment: None,
+ })
+ } else {
+ Err(DiceError::InvalidExpression(format!("未定义的变量引用: {}", key)))
+ }
+ }
}
}
@@ -277,6 +363,14 @@ impl DiceCalculator {
DiceModifier::Unique => result.push('u'),
DiceModifier::Sort => result.push('s'),
DiceModifier::Count(v) => result.push_str(&format!("c{}", v)),
+ DiceModifier::Merge => result.push('m'),
+ DiceModifier::Group { threshold, show_structure } => {
+ if *show_structure {
+ result.push_str(&format!("gs{}", threshold));
+ } else {
+ result.push_str(&format!("g{}", threshold));
+ }
+ }
}
}
result
diff --git a/src/oneroll/grammar.pest b/src/oneroll/grammar.pest
index e5ce667..ac017b7 100644
--- a/src/oneroll/grammar.pest
+++ b/src/oneroll/grammar.pest
@@ -29,6 +29,8 @@ modifier = {
| unique
| sort
| count
+ | merge
+ | group
}
explode = { "!" }
explode_alias = { "e" }
@@ -45,5 +47,15 @@ drop_low = { "dl" ~ number }
unique = { "u" }
sort = { "s" }
count = { "c" ~ number }
+merge = { "m" }
+group = { "g" ~ ("s")? ~ number }
+
op = { "+" | "-" | "*" | "/" | "^" }
-main = { SOI ~ dice_expr ~ EOI } \ No newline at end of file
+
+// Instruction sequences and variable references
+var_ref = { "$" ~ number ~ modifiers? }
+
+instruction = { dice_expr | var_ref }
+instruction_list = { instruction ~ (";" ~ instruction)* }
+
+main = { SOI ~ instruction_list ~ EOI } \ No newline at end of file
diff --git a/src/parser.rs b/src/parser.rs
index 20cd992..73b5c49 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -16,15 +16,38 @@ impl DiceParser {
.map_err(|e| DiceError::ParseError(e.to_string()))?;
let pair = pairs.peek().unwrap();
- Self::parse_dice_expr(pair)
+ Self::parse_instruction_list(pair)
}
fn parse_dice_expr(pair: pest::iterators::Pair<Rule>) -> Result<Expression, DiceError> {
match pair.as_rule() {
Rule::main => {
- // main 规则包含 dice_expr
let inner = pair.into_inner().next().unwrap();
- Self::parse_dice_expr(inner)
+ Self::parse_instruction_list(inner)
+ }
+ Rule::instruction_list => Self::parse_instruction_list(pair),
+ Rule::instruction => {
+ let inner = pair.into_inner().next().unwrap();
+ match inner.as_rule() {
+ Rule::var_ref => {
+ let mut pairs = inner.into_inner();
+ let n = pairs.next().unwrap().as_str().trim_start_matches('$').parse::<i32>()
+ .map_err(|_| DiceError::ParseError("无效的变量引用".to_string()))?;
+ if n <= 0 { return Err(DiceError::ParseError("变量引用必须>=1".to_string())); }
+
+ let mut modifiers = Vec::new();
+ if let Some(modifiers_pair) = pairs.next() {
+ for modifier_pair in modifiers_pair.into_inner() {
+ let modifier = Self::parse_modifier(modifier_pair)?;
+ modifiers.push(modifier);
+ }
+ }
+
+ // Store variable reference with modifiers for special handling
+ Ok(Expression::VariableRefWithModifiers(n as usize, modifiers))
+ }
+ _ => Self::parse_dice_expr(inner),
+ }
}
Rule::dice_expr => {
let mut pairs = pair.into_inner();
@@ -168,6 +191,16 @@ impl DiceParser {
.map_err(|_| DiceError::ParseError("无效的计数数值".to_string()))?;
Ok(DiceModifier::Count(num))
}
+ Rule::merge => Ok(DiceModifier::Merge),
+ Rule::group => {
+ let s = inner.as_str();
+ let show_structure = s.starts_with("gs");
+ let inn = inner.into_inner();
+ // last number
+ let num = inn.last().unwrap().as_str().parse::<i32>()
+ .map_err(|_| DiceError::ParseError("无效的分组阈值".to_string()))?;
+ Ok(DiceModifier::Group { threshold: num, show_structure })
+ }
_ => Err(DiceError::ParseError("未知的修饰符".to_string())),
}
}
@@ -175,6 +208,26 @@ impl DiceParser {
}
}
+ fn parse_instruction_list(pair: pest::iterators::Pair<Rule>) -> Result<Expression, DiceError> {
+ match pair.as_rule() {
+ Rule::instruction_list => {
+ let mut exprs = Vec::new();
+ for p in pair.into_inner() {
+ if p.as_rule() == Rule::instruction {
+ let e = Self::parse_dice_expr(p)?;
+ exprs.push(e);
+ }
+ }
+ if exprs.len() == 1 {
+ Ok(exprs.into_iter().next().unwrap())
+ } else {
+ Ok(Expression::Sequence(exprs))
+ }
+ }
+ _ => Self::parse_dice_expr(pair),
+ }
+ }
+
fn parse_comment(pair: pest::iterators::Pair<Rule>) -> Result<Option<String>, DiceError> {
match pair.as_rule() {
Rule::comment => {
diff --git a/src/types.rs b/src/types.rs
index 1d5d369..cb20908 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -34,6 +34,8 @@ pub enum DiceModifier {
Unique, // u
Sort, // s (sort results)
Count(i32), // cV (count value V)
+ Merge, // m (merge with previous instruction)
+ Group { threshold: i32, show_structure: bool }, // gN / gsN
}
@@ -41,6 +43,9 @@ pub enum DiceModifier {
pub enum Expression {
Number(i32),
DiceRoll(DiceRoll),
+ VariableRef(usize), // $n
+ VariableRefWithModifiers(usize, Vec<DiceModifier>), // $n with modifiers
+ Sequence(Vec<Expression>), // e1; e2; ...
Add(Box<Expression>, Box<Expression>),
Subtract(Box<Expression>, Box<Expression>),
Multiply(Box<Expression>, Box<Expression>),