aboutsummaryrefslogtreecommitdiffstatshomepage
diff options
context:
space:
mode:
-rw-r--r--README.md19
-rw-r--r--src/calculator.rs40
-rw-r--r--src/oneroll/grammar.pest10
-rw-r--r--src/parser.rs19
-rw-r--r--src/types.rs5
5 files changed, 82 insertions, 11 deletions
diff --git a/README.md b/README.md
index 2011ca9..771fb9a 100644
--- a/README.md
+++ b/README.md
@@ -83,14 +83,17 @@ python -m oneroll --tui
- `(2d6 + 3) * 2` - 括号和复合运算
### 修饰符
-- `!` - 爆炸骰子: `2d6!`
-- `kh` - 取高: `4d6kh3` (取最高的3个)
-- `kl` - 取低: `4d6kl2` (取最低的2个)
-- `dh` - 丢弃高: `5d6dh1` (丢弃最高的1个)
-- `dl` - 丢弃低: `5d6dl1` (丢弃最低的1个)
-- `r` - 重投: `3d6r1` (重投小于等于1的结果)
-- `ro` - 条件重投: `4d6ro1` (条件重投一次)
- - `u` - 去重: `4d6u` (相同点数只计一次)
+- `!` / `e` - 爆炸骰子: `2d6!` 或 `2d6e`
+- `KX` - 爆炸并取高X个: `4d6K3` 等价 `4d6!kh3`
+- `khX` / `kX` - 取高: `4d6kh3` 或 `4d6k3`
+- `klX` - 取低: `4d6kl2`
+- `dhX` - 丢弃高: `5d6dh1`
+- `dlX` - 丢弃低: `5d6dl1`
+- `rX` - 重投: `3d6r1` (重投小于等于1的结果)
+- `roX` - 条件重投: `4d6ro1` (条件重投一次)
+- `u` - 去重: `4d6u` (相同点数只计一次)
+- `s` - 排序(不改变总和): `5d6s`
+- `cV` - 计数值V出现次数(作为一个结果输出): `5d6c6`
### 复合表达式
- `6d6dl2kh3` - 丢弃最低2个,然后取最高3个
diff --git a/src/calculator.rs b/src/calculator.rs
index 7289921..5376f76 100644
--- a/src/calculator.rs
+++ b/src/calculator.rs
@@ -35,6 +35,12 @@ impl DiceCalculator {
final_rolls.push(roll as i32);
}
}
+ DiceModifier::ExplodeAlias => {
+ while roll == dice.sides as u32 {
+ roll = rand::random::<u32>() % dice.sides as u32 + 1;
+ final_rolls.push(roll as i32);
+ }
+ }
_ => {}
}
}
@@ -61,9 +67,19 @@ impl DiceCalculator {
rolls.push(final_rolls);
}
- // handle high/low, discard high/low and unique deduplication
+ // handle keep alias before global aggregation
let mut final_rolls = rolls;
for modifier in &dice.modifiers {
+ if let DiceModifier::KeepAlias(n) = modifier {
+ let all_values: Vec<i32> = final_rolls.iter().flatten().cloned().collect();
+ let mut sorted = all_values;
+ sorted.sort_by(|a, b| b.cmp(a));
+ final_rolls = sorted.iter().take(*n as usize).map(|&v| vec![v]).collect();
+ }
+ }
+
+ // handle high/low, discard high/low and unique/sort/count
+ for modifier in &dice.modifiers {
match modifier {
DiceModifier::KeepHigh(n) => {
let all_values: Vec<i32> = final_rolls.iter().flatten().cloned().collect();
@@ -89,6 +105,13 @@ impl DiceCalculator {
sorted.sort();
final_rolls = sorted.iter().skip(*n as usize).map(|&v| vec![v]).collect();
}
+ DiceModifier::ExplodeKeepHigh(n) => {
+ // equivalent to explode then keep high n
+ let all_values: Vec<i32> = final_rolls.iter().flatten().cloned().collect();
+ let mut sorted = all_values;
+ sorted.sort_by(|a, b| b.cmp(a));
+ final_rolls = sorted.iter().take(*n as usize).map(|&v| vec![v]).collect();
+ }
DiceModifier::Unique => {
use std::collections::HashSet;
let mut seen = HashSet::new();
@@ -100,6 +123,16 @@ impl DiceCalculator {
}
final_rolls = uniques.into_iter().map(|v| vec![v]).collect();
}
+ DiceModifier::Sort => {
+ let mut values: Vec<i32> = final_rolls.iter().flatten().cloned().collect();
+ values.sort();
+ final_rolls = values.into_iter().map(|v| vec![v]).collect();
+ }
+ DiceModifier::Count(target) => {
+ let values: Vec<i32> = final_rolls.iter().flatten().cloned().collect();
+ let count = values.iter().filter(|&&v| v == *target).count() as i32;
+ final_rolls = vec![vec![count]];
+ }
_ => {}
}
}
@@ -209,13 +242,18 @@ impl DiceCalculator {
for modifier in modifiers {
match modifier {
DiceModifier::Explode => result.push('!'),
+ DiceModifier::ExplodeAlias => result.push('e'),
+ DiceModifier::ExplodeKeepHigh(n) => result.push_str(&format!("K{}", n)),
DiceModifier::Reroll(n) => result.push_str(&format!("r{}", n)),
DiceModifier::RerollOnce(n) => result.push_str(&format!("ro{}", n)),
+ DiceModifier::KeepAlias(n) => result.push_str(&format!("k{}", n)),
DiceModifier::KeepHigh(n) => result.push_str(&format!("kh{}", n)),
DiceModifier::KeepLow(n) => result.push_str(&format!("kl{}", n)),
DiceModifier::DropHigh(n) => result.push_str(&format!("dh{}", n)),
DiceModifier::DropLow(n) => result.push_str(&format!("dl{}", n)),
DiceModifier::Unique => result.push('u'),
+ DiceModifier::Sort => result.push('s'),
+ DiceModifier::Count(v) => result.push_str(&format!("c{}", v)),
}
}
result
diff --git a/src/oneroll/grammar.pest b/src/oneroll/grammar.pest
index 27d1c5e..09b86f8 100644
--- a/src/oneroll/grammar.pest
+++ b/src/oneroll/grammar.pest
@@ -22,23 +22,33 @@ dice_sides = @{ number }
modifiers = { modifier+ }
modifier = {
explode
+ | explode_alias
+ | explode_keep_high
| reroll
| reroll_once
+ | keep_alias
| keep_high
| keep_low
| drop_high
| drop_low
| unique
+ | sort
+ | count
}
explode = { "!" }
+explode_alias = { "e" }
+explode_keep_high = { "K" ~ number }
reroll = { "r" ~ number }
reroll_once = { "ro" ~ number }
+keep_alias = { "k" ~ number }
keep_high = { "kh" ~ number }
keep_low = { "kl" ~ number }
drop_high = { "dh" ~ number }
drop_low = { "dl" ~ number }
unique = { "u" }
+sort = { "s" }
+count = { "c" ~ number }
op = { "+" | "-" | "*" | "/" | "^" }
diff --git a/src/parser.rs b/src/parser.rs
index a1800a2..50231c0 100644
--- a/src/parser.rs
+++ b/src/parser.rs
@@ -110,6 +110,12 @@ impl DiceParser {
let inner = pair.into_inner().next().unwrap();
match inner.as_rule() {
Rule::explode => Ok(DiceModifier::Explode),
+ Rule::explode_alias => Ok(DiceModifier::ExplodeAlias),
+ Rule::explode_keep_high => {
+ let num = inner.into_inner().next().unwrap().as_str().parse::<i32>()
+ .map_err(|_| DiceError::ParseError("无效的ExplodeKeepHigh数值".to_string()))?;
+ Ok(DiceModifier::ExplodeKeepHigh(num))
+ }
Rule::reroll => {
let num = inner.into_inner().next().unwrap().as_str().parse::<i32>()
.map_err(|_| DiceError::ParseError("无效的重投数值".to_string()))?;
@@ -120,6 +126,11 @@ impl DiceParser {
.map_err(|_| DiceError::ParseError("无效的条件重投数值".to_string()))?;
Ok(DiceModifier::RerollOnce(num))
}
+ Rule::keep_alias => {
+ let num = inner.into_inner().next().unwrap().as_str().parse::<i32>()
+ .map_err(|_| DiceError::ParseError("无效的取高数值".to_string()))?;
+ Ok(DiceModifier::KeepAlias(num))
+ }
Rule::keep_high => {
let num = inner.into_inner().next().unwrap().as_str().parse::<i32>()
.map_err(|_| DiceError::ParseError("无效的取高数值".to_string()))?;
@@ -140,8 +151,12 @@ impl DiceParser {
.map_err(|_| DiceError::ParseError("无效的丢弃低数值".to_string()))?;
Ok(DiceModifier::DropLow(num))
}
- Rule::unique => {
- Ok(DiceModifier::Unique)
+ Rule::unique => Ok(DiceModifier::Unique),
+ Rule::sort => Ok(DiceModifier::Sort),
+ Rule::count => {
+ let num = inner.into_inner().next().unwrap().as_str().parse::<i32>()
+ .map_err(|_| DiceError::ParseError("无效的计数数值".to_string()))?;
+ Ok(DiceModifier::Count(num))
}
_ => Err(DiceError::ParseError("未知的修饰符".to_string())),
}
diff --git a/src/types.rs b/src/types.rs
index 6f4c54c..aad0080 100644
--- a/src/types.rs
+++ b/src/types.rs
@@ -20,13 +20,18 @@ pub struct DiceRoll {
#[derive(Debug, Clone, PartialEq)]
pub enum DiceModifier {
Explode, // !
+ ExplodeAlias, // e (alias of !)
+ ExplodeKeepHigh(i32), // KX == explode then keep high X
Reroll(i32), // rX
RerollOnce(i32), // roX
+ KeepAlias(i32), // kX == khX
KeepHigh(i32), // khX
KeepLow(i32), // klX
DropHigh(i32), // dhX
DropLow(i32), // dlX
Unique, // u
+ Sort, // s (sort results)
+ Count(i32), // cV (count value V)
}