#骰子 #骰子投掷器 #桌面游戏 #DnD #RPG #骰子投掷 #d20

bin+lib tyche

骰子投掷和骰子表达式(语法类似于 FoundryVTT)解析库

2 个不稳定版本

0.2.0 2024年4月29日
0.1.0 2024年4月24日

455解析实现

Download history 268/week @ 2024-04-23 48/week @ 2024-04-30

每月 54 次下载

LGPL-3.0-or-later

99KB
1.5K SLoC

Tyche

Crates.io Version docs.rs

Tyche 是一个用于解析、投掷和解释桌面骰子结果的库。
它还包含一个简单的 CLI 应用程序二进制文件,可以评估给定的表达式。

最终目标是与 FoundryVTT 的骰子语法 完全兼容,并有一些方便的扩展。

功能

  • 解析简单或复杂的骰子表达式
  • 算术
    • 加法: 2d6 + 2
    • 减法: 2d6 - 2
    • 乘法: 2d6 * 2
    • 除法(整数,向下取整): 2d6 / 2
    • 除法(整数,向上取整): 2d6 \ 2
    • 标准的数学运算顺序
    • 括号分组: (2d6 + 2) * 2
  • 骰子修正
    • 保留最高值(优势): 2d20kh2d20k
      • 保留指定数量的值: 3d20kh2
    • 保留最低值(劣势): 2d20kl
      • 保留指定数量的值: 3d20kl2
    • 重新投掷(一次): 4d6r
      • 在特定条件下:4d6r>44d6r>=54d6r<34d6r<=24d6r4
    • 重掷(递归):4d6rr
      • 在特定条件下:4d6rr>44d6rr>=54d6rr<34d6rr<=24d6rr4
    • 爆炸(递归):4d6x
      • 在特定条件下:4d6x>44d6x>=54d6x<34d6x<=24d6x4
    • 爆炸(一次性):4d6xo
      • 在特定条件下:4d6xo>44d6xo>=54d6xo<34d6xo<=24d6xo4
    • 最小值:4d8min3
    • 最大值:4d8max6
  • 骰子修改器链(按指定顺序应用):4d6rr1x>48d8min2kh4xo
  • 轮投抽象,允许自定义骰子投掷行为

安装

运行 cargo add tyche 或者将以下内容添加到项目的 Cargo.toml 文件中

[dependencies]
tyche = "0.2.0"

二进制(命令行应用程序)

运行 cargo install tyche --features build-binary
假设 Cargo 的 bin 目录在您的 $PATH 中,使用 tychetyche <骰子表达式> 运行应用程序。

库使用

使用 Tyche 时,您将开始使用三种主要类型

  • Dice:包含骰子数量、每个骰子的面数以及应用于任何结果的修改器的结构体,表示一组骰子,例如 4d8x
  • Expr:类似 AST 的树结构枚举,表示骰子表达式的各个组成部分,能够表示包括骰子在内的复杂数学运算集,例如 (2d6 + 2) * 2
  • Roller:用于掷单个骰子数值和整个Dice集合的特质。有时为了简洁,简称为“RNG”。有几种Roller实现可供使用。
    • FastRand:使用fastrand crate进行随机数生成。
    • Max:总是掷出每个骰子的最大可能值。
    • Val:总是掷出一个特定的值,忽略骰子面数。
    • Iter:从迭代器中掷出一系列特定的骰子数值,忽略骰子面数(用于测试目的)。

解析

所有解析都需要启用crate的parse特性(默认情况下已启用)。

Tyche使用chumsky解析器生成器以近乎零复制和非常快的速度解析所有字符串。

最方便的是,可以通过使用相关类型的标准FromStr实现来进行解析(DiceExprModifierCondition)。

use tyche::{
	dice::modifier::{Condition, Modifier},
	Dice, Expr,
};

let expr: Expr = "4d6rr<3 + 2d8 - 4".parse()?;
let dice: Dice = "4d6rr<3".parse()?;
let modifier: Modifier = "rr<3".parse()?;
let cond: Condition = "<3".parse()?;

或者,您可以直接通过其关联的GenParser实现或tyche::parse模块中的函数来使用每个类型的解析器。

手动创建骰子

使用构建者模式,即使有大量的链式修饰符,也可以轻松地通过编程构造用于掷骰子的Dice

use tyche::{dice::modifier::Condition, Dice};

// Simple set of dice, no modifiers: 2d20
let d2d20 = Dice::new(2, 20);

// Exploding dice: 4d6x
let d4d6x = Dice::builder()
	.count(4)
	.sides(6)
	.explode(None, true)
	.build();

// Chained modifiers: 6d6rr1x
let d6d6rr1x = Dice::builder()
	.count(6)
	.sides(6)
	.reroll(Condition::Eq(1), true)
	.explode(None, true)
	.build();

掷骰子

所有掷骰子的操作都由一个Roller实现来完成。
最合适的“默认”掷骰子实现是FastRand,它使用fastrand::Rng实例生成骰子数值的随机数。

use tyche::dice::roller::FastRand as FastRandRoller;

// Create a FastRand roller with the default thread-local fastrand::Rng
let mut roller = FastRandRoller::default();

// Create a FastRand roller with a custom-seeded fastrand::Rng
let rng = fastrand::Rng::with_seed(0x750c38d574400);
let mut roller = FastRandRoller::new(rng);

一旦有了掷骰子的工具,您就可以用它一次掷一个骰子,或者用它掷一整套Dice

use tyche::dice::{roller::FastRand as FastRandRoller, Dice, Roller};

let mut roller = FastRandRoller::default();

// Roll a single 20-sided die
let die = roller.roll_die(20);

// Roll two six-sided dice
let dice = Dice::new(2, 6);
let rolled = roller.roll(&dice, true)?;

掷单个骰子会产生一个DieRoll实例,而掷一整套Dice则返回一个Rolled实例。

处理掷出的骰子集合

Rolled是一个结构体,它将多个DieRoll与生成它们的Dice关联起来,以便可以准确地描述掷骰子期间发生的情况以及应用在骰子上的任何修饰符。

使用一个 Rolled 结果,您可以轻松地计算所有掷骰子的结果总和,或者构建一个包含原始骰子组合以及每个骰子掷出结果的字符串。

use tyche::{
	dice::{roller::FastRand as FastRandRoller, Dice, Roller},
	expr::Describe,
};

// Build and roll 4d6kh2
let mut roller = FastRandRoller::default();
let dice = Dice::builder()
	.count(4)
	.sides(6)
	.keep_high(2)
	.build();
let rolled = roller.roll(&dice, true)?;

// Total the results
let total = rolled.total()?;
assert!((2..=12).contains(&total));

// Build a detailed string about the rolls
// The resulting string will look like "4d6kh2[2 (d), 5, 6, 4 (d)]"
let described = rolled.to_string();

// This is the same as the to_string() call above, except only two die rolls will be listed.
// The resulting string will look like "4d6kh2[2 (d), 5, 2 more...]"
let limited = rolled.describe(Some(2));

处理单个骰子掷出结果

DieRoll 包含骰子的最终值以及有关对其进行修改的信息和修改来源。

添加掷骰子

当修改符(而不是原始骰子组合)导致将新的 DieRoll 添加到 Rolled 集合时,掷骰子的 added_by 字段设置为 Some(source_modifier)。添加的掷骰子将在字符串中标记为添加的。
可能导致额外掷骰子的修改符

  • 爆炸
  • 重新掷骰子
丢弃掷骰子

当修改符导致从 Rolled 集合中删除 DieRoll 时,掷骰子的 dropped_by 字段设置为 Some(source_modifier)。丢弃的掷骰子不会受到任何进一步修改的影响,不计入掷骰子集合的总和,并在字符串中标记为丢弃。
可能导致丢弃掷骰子的修改符

  • 重新掷骰子
  • 保留最高分
  • 保留最低分
修改掷骰子

当修改符直接操作 DieRoll 集合中的一个掷骰子的值时,掷骰子的 changes 字段将添加一个 ValChange 项,描述所做的更改和引起更改的修改符。
可能导致修改掷骰子的修改符

  • 最小值
  • 最大值

处理表达式

Expr 树基本上总是从解析表达式字符串获得的,因为手动创建它们将相当繁琐。

一旦您有了 Expr 变体,就可以将其评估为产生一个 Evaled 表达式树。 Evaled 表达式树几乎与其原始的 Expr 树相同,除了任何 Dice 变体都已掷出骰子。这种分离允许以详细的方式描述表达式,无论是在掷出它包含的骰子之前还是之后,以及一些其他实用工具。

use tyche::{
	dice::roller::FastRand as FastRandRoller,
	expr::{Describe, Expr},
};

// Parse a nice dice expression
let expr: Expr = "4d6 + 2d8 - 2".parse()?;

// This expression is definitely not deterministic (it contains dice sets)
assert!(!expr.is_deterministic());

// Build a string for the expression - in this case, it'll be identical to the original parsed string
assert_eq!(expr.to_string(), "4d6 + 2d8 - 2");

// Evaluate the expression, rolling any dice sets it contains
let mut roller = FastRandRoller::default();
let evaled = expr.eval(&mut roller)?;

// Build a detailed string about the evaluated expression
// The resulting string will look like "4d6[3, 2, 6, 2] + 2d8[5, 4] - 2"
let described = evaled.to_string();

// This is the same as the to_string() call above, except only two die rolls in each set will be listed.
// The resulting string will look like "4d6[3, 2, 2 more...] + 2d8[5, 4] - 2"
let limited = evaled.describe(Some(2));

// Calculate the final result of the expression
let total = evaled.calc()?;
assert!((4..=38).contains(&total));

贡献

欢迎所有贡献!请尽量保持 PR 范围相对较小(每次一个功能/修复/重构)并描述性地措辞您的提交。

许可证

Tyche 根据 LGPLv3 许可证授权。

依赖关系

~2.5–4.5MB
~78K SLoC