@mcptoolshop/roll 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/LICENSE +21 -0
- package/README.es.md +204 -0
- package/README.fr.md +204 -0
- package/README.hi.md +204 -0
- package/README.it.md +204 -0
- package/README.ja.md +204 -0
- package/README.md +204 -0
- package/README.pt-BR.md +204 -0
- package/README.zh.md +204 -0
- package/dist/analyze/distribution.d.ts +6 -0
- package/dist/analyze/distribution.d.ts.map +1 -0
- package/dist/analyze/distribution.js +252 -0
- package/dist/analyze/distribution.js.map +1 -0
- package/dist/analyze/montecarlo.d.ts +5 -0
- package/dist/analyze/montecarlo.d.ts.map +1 -0
- package/dist/analyze/montecarlo.js +19 -0
- package/dist/analyze/montecarlo.js.map +1 -0
- package/dist/analyze/stats.d.ts +16 -0
- package/dist/analyze/stats.d.ts.map +1 -0
- package/dist/analyze/stats.js +66 -0
- package/dist/analyze/stats.js.map +1 -0
- package/dist/bin.d.ts +3 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +267 -0
- package/dist/bin.js.map +1 -0
- package/dist/display/box.d.ts +5 -0
- package/dist/display/box.d.ts.map +1 -0
- package/dist/display/box.js +26 -0
- package/dist/display/box.js.map +1 -0
- package/dist/display/color.d.ts +12 -0
- package/dist/display/color.d.ts.map +1 -0
- package/dist/display/color.js +19 -0
- package/dist/display/color.js.map +1 -0
- package/dist/display/format.d.ts +13 -0
- package/dist/display/format.d.ts.map +1 -0
- package/dist/display/format.js +134 -0
- package/dist/display/format.js.map +1 -0
- package/dist/display/histogram.d.ts +7 -0
- package/dist/display/histogram.d.ts.map +1 -0
- package/dist/display/histogram.js +48 -0
- package/dist/display/histogram.js.map +1 -0
- package/dist/engine/random.d.ts +6 -0
- package/dist/engine/random.d.ts.map +1 -0
- package/dist/engine/random.js +17 -0
- package/dist/engine/random.js.map +1 -0
- package/dist/engine/roller.d.ts +19 -0
- package/dist/engine/roller.d.ts.map +1 -0
- package/dist/engine/roller.js +168 -0
- package/dist/engine/roller.js.map +1 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +37 -0
- package/dist/index.js.map +1 -0
- package/dist/loot/table.d.ts +27 -0
- package/dist/loot/table.d.ts.map +1 -0
- package/dist/loot/table.js +92 -0
- package/dist/loot/table.js.map +1 -0
- package/dist/parser/ast.d.ts +27 -0
- package/dist/parser/ast.d.ts.map +1 -0
- package/dist/parser/ast.js +2 -0
- package/dist/parser/ast.js.map +1 -0
- package/dist/parser/lexer.d.ts +7 -0
- package/dist/parser/lexer.d.ts.map +1 -0
- package/dist/parser/lexer.js +126 -0
- package/dist/parser/lexer.js.map +1 -0
- package/dist/parser/parser.d.ts +7 -0
- package/dist/parser/parser.d.ts.map +1 -0
- package/dist/parser/parser.js +188 -0
- package/dist/parser/parser.js.map +1 -0
- package/dist/parser/tokens.d.ts +25 -0
- package/dist/parser/tokens.d.ts.map +1 -0
- package/dist/parser/tokens.js +21 -0
- package/dist/parser/tokens.js.map +1 -0
- package/package.json +55 -0
package/README.pt-BR.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="README.ja.md">日本語</a> | <a href="README.zh.md">中文</a> | <a href="README.es.md">Español</a> | <a href="README.fr.md">Français</a> | <a href="README.hi.md">हिन्दी</a> | <a href="README.it.md">Italiano</a> | <a href="README.md">English</a>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center"><img src="https://raw.githubusercontent.com/mcp-tool-shop-org/brand/main/logos/roll/readme.png" width="400" alt="Roll"></p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://github.com/mcp-tool-shop-org/roll/actions"><img src="https://github.com/mcp-tool-shop-org/roll/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
9
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License"></a>
|
|
10
|
+
<a href="https://mcp-tool-shop-org.github.io/roll/"><img src="https://img.shields.io/badge/Landing_Page-online-brightgreen" alt="Landing Page"></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@mcptoolshop/roll"><img src="https://img.shields.io/npm/v/@mcptoolshop/roll" alt="npm version"></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">RPG dice engine with probability analysis, loot tables, and beautiful terminal output.</p>
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
npx @mcptoolshop/roll 4d6dl1 --analyze
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
Distribution
|
|
22
|
+
|
|
23
|
+
3 0.08%
|
|
24
|
+
4 █ 0.31%
|
|
25
|
+
5 ███ 0.77%
|
|
26
|
+
6 ███████ 1.62%
|
|
27
|
+
7 █████████████ 2.93%
|
|
28
|
+
8 ██████████████████████ 4.78%
|
|
29
|
+
9 ████████████████████████████████ 7.02%
|
|
30
|
+
10 ███████████████████████████████████████████ 9.41%
|
|
31
|
+
11 ████████████████████████████████████████████████████ 11.42%
|
|
32
|
+
12 ██████████████████████████████████████████████████████████ 12.89%
|
|
33
|
+
13 ████████████████████████████████████████████████████████████ 13.27%
|
|
34
|
+
14 ████████████████████████████████████████████████████████ 12.35%
|
|
35
|
+
15 ██████████████████████████████████████████████ 10.11%
|
|
36
|
+
16 █████████████████████████████████ 7.25%
|
|
37
|
+
17 ███████████████████ 4.17%
|
|
38
|
+
18 ███████ 1.62%
|
|
39
|
+
|
|
40
|
+
┌─ Statistics ─────────────────────────────────┐
|
|
41
|
+
│ Mean: 12.24 │
|
|
42
|
+
│ Median: 12 │
|
|
43
|
+
│ Mode: 13 │
|
|
44
|
+
│ Std Dev: 2.85 │
|
|
45
|
+
│ Range: 3–18 │
|
|
46
|
+
│ Entropy: 3.53 bits │
|
|
47
|
+
│ │
|
|
48
|
+
│ Percentiles: │
|
|
49
|
+
│ p10:8 p25:10 p50:12 p75:14 p90:16 p95:17│
|
|
50
|
+
└───────────────────────────────────────────────┘
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Instalação
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install @mcptoolshop/roll
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Requer Node.js >= 22.
|
|
60
|
+
|
|
61
|
+
## Uso da Linha de Comando
|
|
62
|
+
|
|
63
|
+
### Lançar dados
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
roll 2d6+3
|
|
67
|
+
roll d20+5
|
|
68
|
+
roll 4d6kh3
|
|
69
|
+
roll 1d6!
|
|
70
|
+
roll d%
|
|
71
|
+
roll 4dF
|
|
72
|
+
roll "(2d6+3)*2"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Analisar probabilidade
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
roll 2d6 --analyze # Full distribution + statistics
|
|
79
|
+
roll d20+5 --at-least 15 # P(result >= 15)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### Comparar distribuições
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
roll --compare "4d6dl1" "3d6"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Estatísticas lado a lado (média, mediana, moda, desvio padrão, intervalo, entropia) com coluna de diferença, além de ambos os histogramas.
|
|
89
|
+
|
|
90
|
+
### Tabelas de recompensas
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
roll --loot treasure.json
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Formato JSON:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"tables": [
|
|
101
|
+
{
|
|
102
|
+
"table": "Treasure",
|
|
103
|
+
"items": [
|
|
104
|
+
{ "name": "Gold", "weight": 40, "roll": "2d6*10" },
|
|
105
|
+
{ "name": "Potion of Healing", "weight": 30 },
|
|
106
|
+
{ "name": "Scroll", "weight": 15, "quantity": "1d3" },
|
|
107
|
+
{ "name": "Rare Item", "weight": 5, "table": "Rare Weapons" }
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"table": "Rare Weapons",
|
|
112
|
+
"items": [
|
|
113
|
+
{ "name": "Vorpal Blade", "weight": 5 },
|
|
114
|
+
{ "name": "Frost Brand", "weight": 25 }
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Características: seleção ponderada, referências de tabelas aninhadas, expressões de dados para quantidade e valor.
|
|
122
|
+
|
|
123
|
+
### Outras opções
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
roll 2d6+3 --times 5 # Roll 5 times
|
|
127
|
+
roll 2d6+3 --json # Machine-readable output
|
|
128
|
+
roll --help # Full usage
|
|
129
|
+
roll --version # Version
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Notação de Dados
|
|
133
|
+
|
|
134
|
+
| Notação | Significado |
|
|
135
|
+
|----------|---------|
|
|
136
|
+
| `2d6` | Lançar 2 dados de seis lados |
|
|
137
|
+
| `d20` | Lançar 1 dado de vinte lados |
|
|
138
|
+
| `4d6kh3` | Lançar 4d6, manter os 3 maiores |
|
|
139
|
+
| `4d6dl1` | Lançar 4d6, descartar o menor |
|
|
140
|
+
| `1d6!` | Dado explosivo (relançar no máximo, adicionar) |
|
|
141
|
+
| `1d6!>4` | Explodir em 4 ou mais |
|
|
142
|
+
| `d%` | Dado de percentil (1-100) |
|
|
143
|
+
| `4dF` | Dados Fate/Fudge (-1, 0, +1 cada) |
|
|
144
|
+
| `(2d6+3)*2` | Aritmética com agrupamento |
|
|
145
|
+
| `2d6+1d4+3` | Expressões encadeadas |
|
|
146
|
+
|
|
147
|
+
## API da Biblioteca
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { roll, analyze, parse, evaluate, computeDistribution } from '@mcptoolshop/roll';
|
|
151
|
+
|
|
152
|
+
// Quick roll
|
|
153
|
+
const result = roll('4d6kh3');
|
|
154
|
+
console.log(result.total); // 14
|
|
155
|
+
console.log(result.groups[0].dice); // per-die breakdown
|
|
156
|
+
|
|
157
|
+
// Full analysis
|
|
158
|
+
const analysis = analyze('2d6+3');
|
|
159
|
+
console.log(analysis.stats.mean); // 10
|
|
160
|
+
console.log(analysis.stats.percentiles[95]); // 14
|
|
161
|
+
console.log(analysis.probabilityAtLeast(12)); // 0.2778
|
|
162
|
+
|
|
163
|
+
// Low-level: parse → AST → evaluate
|
|
164
|
+
import { seededRng } from '@mcptoolshop/roll';
|
|
165
|
+
const ast = parse('4d6dl1');
|
|
166
|
+
const r = evaluate(ast, seededRng(42)); // deterministic
|
|
167
|
+
|
|
168
|
+
// Loot tables
|
|
169
|
+
import { rollLootTable } from '@mcptoolshop/roll';
|
|
170
|
+
const tables = [{ table: "Loot", items: [{ name: "Gold", weight: 50, roll: "2d6*10" }] }];
|
|
171
|
+
const drops = rollLootTable(tables);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Motor de Probabilidade
|
|
175
|
+
|
|
176
|
+
- **Distribuições exatas** via convolução polinomial para NdM básicos
|
|
177
|
+
- **Enumeração completa** para mecânicas de manter/descartar (4d6 = 1.296 estados)
|
|
178
|
+
- **Recursão truncada** para dados explosivos (limitado a 10 explosões)
|
|
179
|
+
- **Fallback de Monte Carlo** (100 mil amostras) quando o cálculo exato excede 10 milhões de estados
|
|
180
|
+
|
|
181
|
+
## Sem Dependências
|
|
182
|
+
|
|
183
|
+
Construído inteiramente com os recursos nativos do Node.js 22+:
|
|
184
|
+
- `util.styleText` para cores no terminal
|
|
185
|
+
- `util.parseArgs` para análise de argumentos da linha de comando
|
|
186
|
+
- `crypto.randomInt` para geração de números aleatórios criptograficamente seguros
|
|
187
|
+
|
|
188
|
+
## Segurança e Confiança
|
|
189
|
+
|
|
190
|
+
O `@mcptoolshop/roll` processa apenas expressões de dados e nada mais. Não faz solicitações de rede, não escreve arquivos e não coleta dados. O único acesso ao sistema de arquivos é a flag `--loot`, que lê um único arquivo JSON especificado pelo usuário.
|
|
191
|
+
|
|
192
|
+
Não há telemetria, análise ou rastreamento de qualquer tipo. Nenhum segredo, token ou credencial está envolvido em nenhuma operação.
|
|
193
|
+
|
|
194
|
+
Todos os lançamentos de dados usam `crypto.randomInt` do módulo `crypto` do Node.js, fornecendo aleatoriedade criptograficamente segura, adequada para resultados justos.
|
|
195
|
+
|
|
196
|
+
Consulte [SECURITY.md](./SECURITY.md) para a política de relatório de vulnerabilidades.
|
|
197
|
+
|
|
198
|
+
## Licença
|
|
199
|
+
|
|
200
|
+
MIT
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
Desenvolvido por <a href="https://mcp-tool-shop.github.io/">MCP Tool Shop</a>
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<a href="README.ja.md">日本語</a> | <a href="README.md">English</a> | <a href="README.es.md">Español</a> | <a href="README.fr.md">Français</a> | <a href="README.hi.md">हिन्दी</a> | <a href="README.it.md">Italiano</a> | <a href="README.pt-BR.md">Português (BR)</a>
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<p align="center"><img src="https://raw.githubusercontent.com/mcp-tool-shop-org/brand/main/logos/roll/readme.png" width="400" alt="Roll"></p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<a href="https://github.com/mcp-tool-shop-org/roll/actions"><img src="https://github.com/mcp-tool-shop-org/roll/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
9
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue.svg" alt="MIT License"></a>
|
|
10
|
+
<a href="https://mcp-tool-shop-org.github.io/roll/"><img src="https://img.shields.io/badge/Landing_Page-online-brightgreen" alt="Landing Page"></a>
|
|
11
|
+
<a href="https://www.npmjs.com/package/@mcptoolshop/roll"><img src="https://img.shields.io/npm/v/@mcptoolshop/roll" alt="npm version"></a>
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
<p align="center">RPG dice engine with probability analysis, loot tables, and beautiful terminal output.</p>
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
npx @mcptoolshop/roll 4d6dl1 --analyze
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
Distribution
|
|
22
|
+
|
|
23
|
+
3 0.08%
|
|
24
|
+
4 █ 0.31%
|
|
25
|
+
5 ███ 0.77%
|
|
26
|
+
6 ███████ 1.62%
|
|
27
|
+
7 █████████████ 2.93%
|
|
28
|
+
8 ██████████████████████ 4.78%
|
|
29
|
+
9 ████████████████████████████████ 7.02%
|
|
30
|
+
10 ███████████████████████████████████████████ 9.41%
|
|
31
|
+
11 ████████████████████████████████████████████████████ 11.42%
|
|
32
|
+
12 ██████████████████████████████████████████████████████████ 12.89%
|
|
33
|
+
13 ████████████████████████████████████████████████████████████ 13.27%
|
|
34
|
+
14 ████████████████████████████████████████████████████████ 12.35%
|
|
35
|
+
15 ██████████████████████████████████████████████ 10.11%
|
|
36
|
+
16 █████████████████████████████████ 7.25%
|
|
37
|
+
17 ███████████████████ 4.17%
|
|
38
|
+
18 ███████ 1.62%
|
|
39
|
+
|
|
40
|
+
┌─ Statistics ─────────────────────────────────┐
|
|
41
|
+
│ Mean: 12.24 │
|
|
42
|
+
│ Median: 12 │
|
|
43
|
+
│ Mode: 13 │
|
|
44
|
+
│ Std Dev: 2.85 │
|
|
45
|
+
│ Range: 3–18 │
|
|
46
|
+
│ Entropy: 3.53 bits │
|
|
47
|
+
│ │
|
|
48
|
+
│ Percentiles: │
|
|
49
|
+
│ p10:8 p25:10 p50:12 p75:14 p90:16 p95:17│
|
|
50
|
+
└───────────────────────────────────────────────┘
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## 安装
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npm install @mcptoolshop/roll
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
需要 Node.js >= 22。
|
|
60
|
+
|
|
61
|
+
## 命令行用法
|
|
62
|
+
|
|
63
|
+
### 投骰子
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
roll 2d6+3
|
|
67
|
+
roll d20+5
|
|
68
|
+
roll 4d6kh3
|
|
69
|
+
roll 1d6!
|
|
70
|
+
roll d%
|
|
71
|
+
roll 4dF
|
|
72
|
+
roll "(2d6+3)*2"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### 分析概率
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
roll 2d6 --analyze # Full distribution + statistics
|
|
79
|
+
roll d20+5 --at-least 15 # P(result >= 15)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### 比较分布
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
roll --compare "4d6dl1" "3d6"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
并排显示统计数据(均值、中位数、众数、标准差、范围、熵),包含差异列,以及直方图。
|
|
89
|
+
|
|
90
|
+
### 掉落表
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
roll --loot treasure.json
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
JSON 格式:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"tables": [
|
|
101
|
+
{
|
|
102
|
+
"table": "Treasure",
|
|
103
|
+
"items": [
|
|
104
|
+
{ "name": "Gold", "weight": 40, "roll": "2d6*10" },
|
|
105
|
+
{ "name": "Potion of Healing", "weight": 30 },
|
|
106
|
+
{ "name": "Scroll", "weight": 15, "quantity": "1d3" },
|
|
107
|
+
{ "name": "Rare Item", "weight": 5, "table": "Rare Weapons" }
|
|
108
|
+
]
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"table": "Rare Weapons",
|
|
112
|
+
"items": [
|
|
113
|
+
{ "name": "Vorpal Blade", "weight": 5 },
|
|
114
|
+
{ "name": "Frost Brand", "weight": 25 }
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
]
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
特性:加权选择、嵌套表引用、用于数量和值的骰子表达式。
|
|
122
|
+
|
|
123
|
+
### 其他选项
|
|
124
|
+
|
|
125
|
+
```bash
|
|
126
|
+
roll 2d6+3 --times 5 # Roll 5 times
|
|
127
|
+
roll 2d6+3 --json # Machine-readable output
|
|
128
|
+
roll --help # Full usage
|
|
129
|
+
roll --version # Version
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## 骰子表示法
|
|
133
|
+
|
|
134
|
+
| 表示法 | 含义 |
|
|
135
|
+
|----------|---------|
|
|
136
|
+
| `2d6` | 投掷 2 个六面骰子 |
|
|
137
|
+
| `d20` | 投掷 1 个二十面骰子 |
|
|
138
|
+
| `4d6kh3` | 投掷 4d6,保留最高的 3 个 |
|
|
139
|
+
| `4d6dl1` | 投掷 4d6,舍弃最低的 1 个 |
|
|
140
|
+
| `1d6!` | 爆炸骰子(最大值时重新投掷,并加总) |
|
|
141
|
+
| `1d6!>4` | 当结果为 4 或更高时爆炸 |
|
|
142
|
+
| `d%` | 百分位骰子(1-100) |
|
|
143
|
+
| `4dF` | 命运/模糊骰子(-1、0、+1 各一个) |
|
|
144
|
+
| `(2d6+3)*2` | 带有分组的算术运算 |
|
|
145
|
+
| `2d6+1d4+3` | 链式表达式 |
|
|
146
|
+
|
|
147
|
+
## 库 API
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { roll, analyze, parse, evaluate, computeDistribution } from '@mcptoolshop/roll';
|
|
151
|
+
|
|
152
|
+
// Quick roll
|
|
153
|
+
const result = roll('4d6kh3');
|
|
154
|
+
console.log(result.total); // 14
|
|
155
|
+
console.log(result.groups[0].dice); // per-die breakdown
|
|
156
|
+
|
|
157
|
+
// Full analysis
|
|
158
|
+
const analysis = analyze('2d6+3');
|
|
159
|
+
console.log(analysis.stats.mean); // 10
|
|
160
|
+
console.log(analysis.stats.percentiles[95]); // 14
|
|
161
|
+
console.log(analysis.probabilityAtLeast(12)); // 0.2778
|
|
162
|
+
|
|
163
|
+
// Low-level: parse → AST → evaluate
|
|
164
|
+
import { seededRng } from '@mcptoolshop/roll';
|
|
165
|
+
const ast = parse('4d6dl1');
|
|
166
|
+
const r = evaluate(ast, seededRng(42)); // deterministic
|
|
167
|
+
|
|
168
|
+
// Loot tables
|
|
169
|
+
import { rollLootTable } from '@mcptoolshop/roll';
|
|
170
|
+
const tables = [{ table: "Loot", items: [{ name: "Gold", weight: 50, roll: "2d6*10" }] }];
|
|
171
|
+
const drops = rollLootTable(tables);
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## 概率引擎
|
|
175
|
+
|
|
176
|
+
- **精确分布**:通过多项式卷积计算基本 NdM 概率。
|
|
177
|
+
- **完整枚举**:用于保留/舍弃机制(4d6 = 1296 种状态)。
|
|
178
|
+
- **截断递归**:用于爆炸骰子(最多 10 次爆炸)。
|
|
179
|
+
- **蒙特卡洛方法**(10 万个样本):当精确计算超过 1000 万种状态时使用。
|
|
180
|
+
|
|
181
|
+
## 无外部依赖
|
|
182
|
+
|
|
183
|
+
完全基于 Node.js 22+ 的内置模块:
|
|
184
|
+
- `util.styleText` 用于终端颜色。
|
|
185
|
+
- `util.parseArgs` 用于命令行参数解析。
|
|
186
|
+
- `crypto.randomInt` 用于密码学安全的骰子投掷。
|
|
187
|
+
|
|
188
|
+
## 安全与信任
|
|
189
|
+
|
|
190
|
+
`@mcptoolshop/roll` 只处理骰子表达式,不做其他任何操作。它不进行任何网络请求,不写入任何文件,也不收集任何数据。唯一的系统文件访问是 `--loot` 选项,它读取单个用户指定的 JSON 文件。
|
|
191
|
+
|
|
192
|
+
没有遥测数据,没有分析,也没有任何形式的跟踪。没有涉及任何秘密、令牌或凭据。
|
|
193
|
+
|
|
194
|
+
所有骰子投掷都使用 Node.js `crypto` 模块中的 `crypto.randomInt`,提供密码学安全的随机数,适用于公平的结果。
|
|
195
|
+
|
|
196
|
+
请参阅 [SECURITY.md](./SECURITY.md) 以获取漏洞报告政策。
|
|
197
|
+
|
|
198
|
+
## 许可证
|
|
199
|
+
|
|
200
|
+
MIT
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
由 <a href="https://mcp-tool-shop.github.io/">MCP Tool Shop</a> 构建。
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { ASTNode } from "../parser/ast.js";
|
|
2
|
+
/** A probability distribution: value → probability (0..1) */
|
|
3
|
+
export type Distribution = Map<number, number>;
|
|
4
|
+
/** Compute the full probability distribution for an AST. Falls back to Monte Carlo for complex cases. */
|
|
5
|
+
export declare function computeDistribution(ast: ASTNode): Distribution;
|
|
6
|
+
//# sourceMappingURL=distribution.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"distribution.d.ts","sourceRoot":"","sources":["../../src/analyze/distribution.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAuB,MAAM,kBAAkB,CAAC;AAGrE,6DAA6D;AAC7D,MAAM,MAAM,YAAY,GAAG,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AA+L/C,yGAAyG;AACzG,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,OAAO,GAAG,YAAY,CAI9D"}
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import { monteCarloDistribution } from "./montecarlo.js";
|
|
2
|
+
const MAX_EXACT_STATES = 10_000_000;
|
|
3
|
+
function sideCount(sides) {
|
|
4
|
+
if (sides === "%")
|
|
5
|
+
return 100;
|
|
6
|
+
if (sides === "F")
|
|
7
|
+
return 3;
|
|
8
|
+
return sides;
|
|
9
|
+
}
|
|
10
|
+
/** Distribution for a single die roll. */
|
|
11
|
+
function singleDieDistribution(sides) {
|
|
12
|
+
const dist = new Map();
|
|
13
|
+
if (sides === "F") {
|
|
14
|
+
dist.set(-1, 1 / 3);
|
|
15
|
+
dist.set(0, 1 / 3);
|
|
16
|
+
dist.set(1, 1 / 3);
|
|
17
|
+
}
|
|
18
|
+
else {
|
|
19
|
+
const n = sides === "%" ? 100 : sides;
|
|
20
|
+
const p = 1 / n;
|
|
21
|
+
for (let i = 1; i <= n; i++) {
|
|
22
|
+
dist.set(i, p);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return dist;
|
|
26
|
+
}
|
|
27
|
+
/** Convolve two distributions (sum of independent random variables). */
|
|
28
|
+
function convolve(a, b) {
|
|
29
|
+
const result = new Map();
|
|
30
|
+
for (const [va, pa] of a) {
|
|
31
|
+
for (const [vb, pb] of b) {
|
|
32
|
+
const sum = va + vb;
|
|
33
|
+
result.set(sum, (result.get(sum) ?? 0) + pa * pb);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
/** Distribution for NdM via iterative convolution. */
|
|
39
|
+
function convolveDice(count, sides) {
|
|
40
|
+
const single = singleDieDistribution(sides);
|
|
41
|
+
let dist = new Map([[0, 1]]); // identity: always 0
|
|
42
|
+
for (let i = 0; i < count; i++) {
|
|
43
|
+
dist = convolve(dist, single);
|
|
44
|
+
}
|
|
45
|
+
return dist;
|
|
46
|
+
}
|
|
47
|
+
/** Distribution with keep/drop via full enumeration. */
|
|
48
|
+
function enumerateKeepDrop(node) {
|
|
49
|
+
const s = sideCount(node.sides);
|
|
50
|
+
const totalStates = Math.pow(s, node.count);
|
|
51
|
+
if (totalStates > MAX_EXACT_STATES)
|
|
52
|
+
return null;
|
|
53
|
+
const isFate = node.sides === "F";
|
|
54
|
+
const dist = new Map();
|
|
55
|
+
const prob = 1 / totalStates;
|
|
56
|
+
// Enumerate all outcomes
|
|
57
|
+
const rolls = new Array(node.count);
|
|
58
|
+
function enumerate(depth) {
|
|
59
|
+
if (depth === node.count) {
|
|
60
|
+
// Apply keep/drop
|
|
61
|
+
const sorted = [...rolls].sort((a, b) => a - b);
|
|
62
|
+
let kept = sorted.slice(); // start with all
|
|
63
|
+
for (const mod of node.modifiers) {
|
|
64
|
+
if (mod.kind === "explode")
|
|
65
|
+
continue;
|
|
66
|
+
const n = mod.value ?? 1;
|
|
67
|
+
switch (mod.kind) {
|
|
68
|
+
case "kh":
|
|
69
|
+
kept = kept.slice(kept.length - n);
|
|
70
|
+
break;
|
|
71
|
+
case "kl":
|
|
72
|
+
kept = kept.slice(0, n);
|
|
73
|
+
break;
|
|
74
|
+
case "dh":
|
|
75
|
+
kept = kept.slice(0, kept.length - n);
|
|
76
|
+
break;
|
|
77
|
+
case "dl":
|
|
78
|
+
kept = kept.slice(n);
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const total = kept.reduce((a, b) => a + b, 0);
|
|
83
|
+
dist.set(total, (dist.get(total) ?? 0) + prob);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (isFate) {
|
|
87
|
+
for (let v = -1; v <= 1; v++) {
|
|
88
|
+
rolls[depth] = v;
|
|
89
|
+
enumerate(depth + 1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
const max = sideCount(node.sides);
|
|
94
|
+
for (let v = 1; v <= max; v++) {
|
|
95
|
+
rolls[depth] = v;
|
|
96
|
+
enumerate(depth + 1);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
enumerate(0);
|
|
101
|
+
return dist;
|
|
102
|
+
}
|
|
103
|
+
/** Distribution for exploding dice via iterative depth expansion. */
|
|
104
|
+
function explodingDistribution(node) {
|
|
105
|
+
if (node.sides === "F")
|
|
106
|
+
return null;
|
|
107
|
+
const s = sideCount(node.sides);
|
|
108
|
+
const explodeMod = node.modifiers.find((m) => m.kind === "explode");
|
|
109
|
+
if (!explodeMod)
|
|
110
|
+
return null;
|
|
111
|
+
const threshold = explodeMod.value ?? s;
|
|
112
|
+
const maxExplosions = 10;
|
|
113
|
+
function singleExploding() {
|
|
114
|
+
const dist = new Map();
|
|
115
|
+
const pFace = 1 / s;
|
|
116
|
+
// Build depth-by-depth: accumulated tracks (sum → probability) for chains
|
|
117
|
+
// that haven't terminated yet
|
|
118
|
+
let accumulated = new Map([[0, 1]]);
|
|
119
|
+
for (let depth = 0; depth <= maxExplosions; depth++) {
|
|
120
|
+
const nextAccumulated = new Map();
|
|
121
|
+
for (const [accSum, accProb] of accumulated) {
|
|
122
|
+
for (let face = 1; face <= s; face++) {
|
|
123
|
+
const total = accSum + face;
|
|
124
|
+
const p = accProb * pFace;
|
|
125
|
+
if (face < threshold || depth === maxExplosions) {
|
|
126
|
+
// Terminating roll (or forced cap)
|
|
127
|
+
dist.set(total, (dist.get(total) ?? 0) + p);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
// Exploding — carry forward
|
|
131
|
+
nextAccumulated.set(total, (nextAccumulated.get(total) ?? 0) + p);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
accumulated = nextAccumulated;
|
|
136
|
+
if (accumulated.size === 0)
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
return dist;
|
|
140
|
+
}
|
|
141
|
+
const single = singleExploding();
|
|
142
|
+
let dist = new Map([[0, 1]]);
|
|
143
|
+
for (let i = 0; i < node.count; i++) {
|
|
144
|
+
dist = convolve(dist, single);
|
|
145
|
+
}
|
|
146
|
+
return dist;
|
|
147
|
+
}
|
|
148
|
+
/** Shift distribution by constant. */
|
|
149
|
+
function shiftDistribution(dist, offset) {
|
|
150
|
+
const result = new Map();
|
|
151
|
+
for (const [v, p] of dist) {
|
|
152
|
+
result.set(v + offset, (result.get(v + offset) ?? 0) + p);
|
|
153
|
+
}
|
|
154
|
+
return result;
|
|
155
|
+
}
|
|
156
|
+
/** Scale distribution by constant. */
|
|
157
|
+
function scaleDistribution(dist, factor) {
|
|
158
|
+
const result = new Map();
|
|
159
|
+
for (const [v, p] of dist) {
|
|
160
|
+
const scaled = factor >= 0 ? v * factor : v * factor;
|
|
161
|
+
result.set(scaled, (result.get(scaled) ?? 0) + p);
|
|
162
|
+
}
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
/** Floor-divide distribution by constant. */
|
|
166
|
+
function divideDistribution(dist, divisor) {
|
|
167
|
+
if (divisor === 0)
|
|
168
|
+
return new Map([[0, 1]]);
|
|
169
|
+
const result = new Map();
|
|
170
|
+
for (const [v, p] of dist) {
|
|
171
|
+
const divided = Math.floor(v / divisor);
|
|
172
|
+
result.set(divided, (result.get(divided) ?? 0) + p);
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
/** Compute the full probability distribution for an AST. Falls back to Monte Carlo for complex cases. */
|
|
177
|
+
export function computeDistribution(ast) {
|
|
178
|
+
const result = tryExact(ast);
|
|
179
|
+
if (result)
|
|
180
|
+
return result;
|
|
181
|
+
return monteCarloDistribution(ast);
|
|
182
|
+
}
|
|
183
|
+
function tryExact(node) {
|
|
184
|
+
switch (node.type) {
|
|
185
|
+
case "number":
|
|
186
|
+
return new Map([[node.value, 1]]);
|
|
187
|
+
case "dice": {
|
|
188
|
+
const hasKeepDrop = node.modifiers.some((m) => m.kind === "kh" || m.kind === "kl" || m.kind === "dh" || m.kind === "dl");
|
|
189
|
+
const hasExplode = node.modifiers.some((m) => m.kind === "explode");
|
|
190
|
+
if (hasExplode && !hasKeepDrop) {
|
|
191
|
+
return explodingDistribution(node);
|
|
192
|
+
}
|
|
193
|
+
if (hasKeepDrop) {
|
|
194
|
+
return enumerateKeepDrop(node);
|
|
195
|
+
}
|
|
196
|
+
// Plain NdM — use convolution
|
|
197
|
+
return convolveDice(node.count, node.sides);
|
|
198
|
+
}
|
|
199
|
+
case "binary": {
|
|
200
|
+
const left = tryExact(node.left);
|
|
201
|
+
const right = tryExact(node.right);
|
|
202
|
+
if (!left || !right)
|
|
203
|
+
return null;
|
|
204
|
+
// Optimize: if one side is a constant
|
|
205
|
+
if (right.size === 1) {
|
|
206
|
+
const [rv] = [...right.keys()];
|
|
207
|
+
switch (node.op) {
|
|
208
|
+
case "+":
|
|
209
|
+
return shiftDistribution(left, rv);
|
|
210
|
+
case "-":
|
|
211
|
+
return shiftDistribution(left, -rv);
|
|
212
|
+
case "*":
|
|
213
|
+
return scaleDistribution(left, rv);
|
|
214
|
+
case "/":
|
|
215
|
+
return divideDistribution(left, rv);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
if (left.size === 1) {
|
|
219
|
+
const [lv] = [...left.keys()];
|
|
220
|
+
switch (node.op) {
|
|
221
|
+
case "+":
|
|
222
|
+
return shiftDistribution(right, lv);
|
|
223
|
+
case "-": {
|
|
224
|
+
// lv - right → negate right, shift by lv
|
|
225
|
+
const negated = scaleDistribution(right, -1);
|
|
226
|
+
return shiftDistribution(negated, lv);
|
|
227
|
+
}
|
|
228
|
+
case "*":
|
|
229
|
+
return scaleDistribution(right, lv);
|
|
230
|
+
case "/":
|
|
231
|
+
return null; // constant / distribution is unusual, use MC
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
// Both sides are distributions — convolve for +/-, fall back for */÷
|
|
235
|
+
if (node.op === "+")
|
|
236
|
+
return convolve(left, right);
|
|
237
|
+
if (node.op === "-") {
|
|
238
|
+
const negated = scaleDistribution(right, -1);
|
|
239
|
+
return convolve(left, negated);
|
|
240
|
+
}
|
|
241
|
+
// Multiplication/division of two distributions — Monte Carlo
|
|
242
|
+
return null;
|
|
243
|
+
}
|
|
244
|
+
case "unary_minus": {
|
|
245
|
+
const inner = tryExact(node.operand);
|
|
246
|
+
if (!inner)
|
|
247
|
+
return null;
|
|
248
|
+
return scaleDistribution(inner, -1);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
//# sourceMappingURL=distribution.js.map
|