@capsuleer/dice 1.0.1
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/README.md +112 -0
- package/capsuleer.manifest.json +224 -0
- package/index.ts +117 -0
- package/package.json +22 -0
package/README.md
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# @capsuleer/dice
|
|
2
|
+
|
|
3
|
+
Dice roller and randomness module for Capsuleer agents. Roll dice in standard RPG notation, flip coins, and pick random items from lists — with every roll traced so you always know what fate decided.
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
capsuleer install dice
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
Genuinely useful. Suspiciously fun.
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## API
|
|
14
|
+
|
|
15
|
+
### Rolling dice
|
|
16
|
+
|
|
17
|
+
```typescript
|
|
18
|
+
// Standard RPG notation: NdX[+/-modifier]
|
|
19
|
+
const attack = await dice.roll("1d20+5")
|
|
20
|
+
// → { notation: "1d20+5", rolls: [14], modifier: 5, total: 19, min: 6, max: 25 }
|
|
21
|
+
|
|
22
|
+
const damage = await dice.roll("2d6+3")
|
|
23
|
+
// → { notation: "2d6+3", rolls: [4, 6], modifier: 3, total: 13, min: 5, max: 15 }
|
|
24
|
+
|
|
25
|
+
// Just a d20
|
|
26
|
+
const check = await dice.roll("d20")
|
|
27
|
+
|
|
28
|
+
// Handful of d6s
|
|
29
|
+
const fireball = await dice.roll("8d6")
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Simulations
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// Roll the same notation many times and get summary stats
|
|
36
|
+
const sim = await dice.rollMany("2d6", 1000)
|
|
37
|
+
// → {
|
|
38
|
+
// rolls: [...], // all 1000 results
|
|
39
|
+
// totals: [7, 4, 11, ...],
|
|
40
|
+
// stats: { mean: 7.02, min: 2, max: 12 }
|
|
41
|
+
// }
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
Great for probability exploration — ask an agent "what's the average damage of 3d8+5 over 500 attacks?"
|
|
45
|
+
|
|
46
|
+
### Coin flips
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
// Single flip
|
|
50
|
+
const flip = await dice.coin()
|
|
51
|
+
// → { flips: 1, results: ["heads"], heads: 1, tails: 0 }
|
|
52
|
+
|
|
53
|
+
// Many flips
|
|
54
|
+
const trial = await dice.coin(100)
|
|
55
|
+
// → { flips: 100, results: [...], heads: 53, tails: 47 }
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Random selection
|
|
59
|
+
|
|
60
|
+
```typescript
|
|
61
|
+
// Pick one random item
|
|
62
|
+
const winner = await dice.pick(["Alice", "Bob", "Carol", "Dave"])
|
|
63
|
+
// → ["Carol"]
|
|
64
|
+
|
|
65
|
+
// Pick multiple (without replacement)
|
|
66
|
+
const team = await dice.pick(["Alice", "Bob", "Carol", "Dave", "Eve"], 3)
|
|
67
|
+
// → ["Eve", "Alice", "Carol"]
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
## Observability
|
|
73
|
+
|
|
74
|
+
```jsonl
|
|
75
|
+
{ "ok": true, "op": "dice.roll", "data": { "notation": "2d6+3", "rolls": [4, 6], "modifier": 3, "total": 13, "min": 5, "max": 15 } }
|
|
76
|
+
{ "ok": true, "op": "dice.rollMany", "data": { "notation": "2d6", "times": 1000, "stats": { "mean": 7.02, "min": 2, "max": 12 } } }
|
|
77
|
+
{ "ok": true, "op": "dice.coin", "data": { "flips": 100, "heads": 53, "tails": 47 } }
|
|
78
|
+
{ "ok": true, "op": "dice.pick", "data": { "from": 4, "count": 1, "result": ["Carol"] } }
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## Notation reference
|
|
84
|
+
|
|
85
|
+
| Notation | Meaning |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `d20` | One twenty-sided die |
|
|
88
|
+
| `2d6` | Two six-sided dice, summed |
|
|
89
|
+
| `1d20+5` | One d20 plus a +5 modifier |
|
|
90
|
+
| `4d6-1` | Four d6s minus 1 |
|
|
91
|
+
| `100d1` | Deeply philosophical |
|
|
92
|
+
|
|
93
|
+
Limits: 1–1000 dice, 2–10000 sides, modifier is any integer.
|
|
94
|
+
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
## Policy
|
|
98
|
+
|
|
99
|
+
```typescript
|
|
100
|
+
const capsule = await Capsule({
|
|
101
|
+
policy: {
|
|
102
|
+
dice: {
|
|
103
|
+
roll: true,
|
|
104
|
+
rollMany: true,
|
|
105
|
+
coin: true,
|
|
106
|
+
pick: true,
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
See the [policy docs](https://axon.arclabs.it/docs/capsuleer/policy) for the full rule syntax.
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "dice",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Dice roller and randomness module — roll dice in standard notation, flip coins, and pick random items",
|
|
5
|
+
"exports": [
|
|
6
|
+
{
|
|
7
|
+
"name": "roll",
|
|
8
|
+
"declaration": "declare function roll(notation: string): Promise<{ notation: string; rolls: Array<number>; modifier: number; total: number; min: number; max: number }>",
|
|
9
|
+
"schema": {
|
|
10
|
+
"parameters": {
|
|
11
|
+
"type": "object",
|
|
12
|
+
"properties": {
|
|
13
|
+
"notation": {
|
|
14
|
+
"type": "string"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"required": [
|
|
18
|
+
"notation"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
"returns": {
|
|
22
|
+
"type": "object",
|
|
23
|
+
"properties": {
|
|
24
|
+
"notation": {
|
|
25
|
+
"type": "string"
|
|
26
|
+
},
|
|
27
|
+
"rolls": {
|
|
28
|
+
"type": "array",
|
|
29
|
+
"items": {
|
|
30
|
+
"type": "number"
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"modifier": {
|
|
34
|
+
"type": "number"
|
|
35
|
+
},
|
|
36
|
+
"total": {
|
|
37
|
+
"type": "number"
|
|
38
|
+
},
|
|
39
|
+
"min": {
|
|
40
|
+
"type": "number"
|
|
41
|
+
},
|
|
42
|
+
"max": {
|
|
43
|
+
"type": "number"
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"required": [
|
|
47
|
+
"notation",
|
|
48
|
+
"rolls",
|
|
49
|
+
"modifier",
|
|
50
|
+
"total",
|
|
51
|
+
"min",
|
|
52
|
+
"max"
|
|
53
|
+
]
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"name": "rollMany",
|
|
59
|
+
"declaration": "declare function rollMany(notation: string, times: number): Promise<{ rolls: Array<{ notation: string; rolls: Array<number>; modifier: number; total: number; min: number; max: number }>; totals: Array<number>; stats: { mean: number; min: number; max: number } }>",
|
|
60
|
+
"schema": {
|
|
61
|
+
"parameters": {
|
|
62
|
+
"type": "object",
|
|
63
|
+
"properties": {
|
|
64
|
+
"notation": {
|
|
65
|
+
"type": "string"
|
|
66
|
+
},
|
|
67
|
+
"times": {
|
|
68
|
+
"type": "number"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"required": [
|
|
72
|
+
"notation",
|
|
73
|
+
"times"
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"returns": {
|
|
77
|
+
"type": "object",
|
|
78
|
+
"properties": {
|
|
79
|
+
"rolls": {
|
|
80
|
+
"type": "array",
|
|
81
|
+
"items": {
|
|
82
|
+
"type": "object",
|
|
83
|
+
"properties": {
|
|
84
|
+
"notation": {
|
|
85
|
+
"type": "string"
|
|
86
|
+
},
|
|
87
|
+
"rolls": {
|
|
88
|
+
"type": "array",
|
|
89
|
+
"items": {
|
|
90
|
+
"type": "number"
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
"modifier": {
|
|
94
|
+
"type": "number"
|
|
95
|
+
},
|
|
96
|
+
"total": {
|
|
97
|
+
"type": "number"
|
|
98
|
+
},
|
|
99
|
+
"min": {
|
|
100
|
+
"type": "number"
|
|
101
|
+
},
|
|
102
|
+
"max": {
|
|
103
|
+
"type": "number"
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
"required": [
|
|
107
|
+
"notation",
|
|
108
|
+
"rolls",
|
|
109
|
+
"modifier",
|
|
110
|
+
"total",
|
|
111
|
+
"min",
|
|
112
|
+
"max"
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
"totals": {
|
|
117
|
+
"type": "array",
|
|
118
|
+
"items": {
|
|
119
|
+
"type": "number"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
"stats": {
|
|
123
|
+
"type": "object",
|
|
124
|
+
"properties": {
|
|
125
|
+
"mean": {
|
|
126
|
+
"type": "number"
|
|
127
|
+
},
|
|
128
|
+
"min": {
|
|
129
|
+
"type": "number"
|
|
130
|
+
},
|
|
131
|
+
"max": {
|
|
132
|
+
"type": "number"
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
"required": [
|
|
136
|
+
"mean",
|
|
137
|
+
"min",
|
|
138
|
+
"max"
|
|
139
|
+
]
|
|
140
|
+
}
|
|
141
|
+
},
|
|
142
|
+
"required": [
|
|
143
|
+
"rolls",
|
|
144
|
+
"totals",
|
|
145
|
+
"stats"
|
|
146
|
+
]
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
},
|
|
150
|
+
{
|
|
151
|
+
"name": "coin",
|
|
152
|
+
"declaration": "declare function coin(flips: number): Promise<{ flips: number; results: Array<\"heads\" | \"tails\">; heads: number; tails: number }>",
|
|
153
|
+
"schema": {
|
|
154
|
+
"parameters": {
|
|
155
|
+
"type": "object",
|
|
156
|
+
"properties": {
|
|
157
|
+
"flips": {
|
|
158
|
+
"type": "number"
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
"required": [
|
|
162
|
+
"flips"
|
|
163
|
+
]
|
|
164
|
+
},
|
|
165
|
+
"returns": {
|
|
166
|
+
"type": "object",
|
|
167
|
+
"properties": {
|
|
168
|
+
"flips": {
|
|
169
|
+
"type": "number"
|
|
170
|
+
},
|
|
171
|
+
"results": {
|
|
172
|
+
"type": "array",
|
|
173
|
+
"items": {
|
|
174
|
+
"type": "string",
|
|
175
|
+
"enum": [
|
|
176
|
+
"heads",
|
|
177
|
+
"tails"
|
|
178
|
+
]
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
"heads": {
|
|
182
|
+
"type": "number"
|
|
183
|
+
},
|
|
184
|
+
"tails": {
|
|
185
|
+
"type": "number"
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
"required": [
|
|
189
|
+
"flips",
|
|
190
|
+
"results",
|
|
191
|
+
"heads",
|
|
192
|
+
"tails"
|
|
193
|
+
]
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
},
|
|
197
|
+
{
|
|
198
|
+
"name": "pick",
|
|
199
|
+
"declaration": "declare function pick(items: Array<unknown>, count: number): Promise<Array<unknown>>",
|
|
200
|
+
"schema": {
|
|
201
|
+
"parameters": {
|
|
202
|
+
"type": "object",
|
|
203
|
+
"properties": {
|
|
204
|
+
"items": {
|
|
205
|
+
"type": "array",
|
|
206
|
+
"items": {}
|
|
207
|
+
},
|
|
208
|
+
"count": {
|
|
209
|
+
"type": "number"
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
"required": [
|
|
213
|
+
"items",
|
|
214
|
+
"count"
|
|
215
|
+
]
|
|
216
|
+
},
|
|
217
|
+
"returns": {
|
|
218
|
+
"type": "array",
|
|
219
|
+
"items": {}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
]
|
|
224
|
+
}
|
package/index.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { defineModule } from "@capsuleer/core"
|
|
2
|
+
|
|
3
|
+
type RollResult = {
|
|
4
|
+
notation: string
|
|
5
|
+
rolls: number[]
|
|
6
|
+
modifier: number
|
|
7
|
+
total: number
|
|
8
|
+
min: number
|
|
9
|
+
max: number
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
type CoinResult = {
|
|
13
|
+
flips: number
|
|
14
|
+
results: Array<"heads" | "tails">
|
|
15
|
+
heads: number
|
|
16
|
+
tails: number
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Parse standard dice notation: NdX[+/-M]
|
|
20
|
+
// Examples: "2d6", "1d20+5", "4d6-1", "d20"
|
|
21
|
+
function parseNotation(notation: string): { count: number; sides: number; modifier: number } {
|
|
22
|
+
const match = notation.trim().toLowerCase().match(/^(\d*)d(\d+)([+-]\d+)?$/)
|
|
23
|
+
if (!match) throw new Error(`Invalid dice notation: "${notation}". Expected format: NdX, NdX+M, or NdX-M (e.g. 2d6, 1d20+3, d8-1)`)
|
|
24
|
+
const count = match[1] === "" ? 1 : parseInt(match[1], 10)
|
|
25
|
+
const sides = parseInt(match[2], 10)
|
|
26
|
+
const modifier = match[3] ? parseInt(match[3], 10) : 0
|
|
27
|
+
if (count < 1 || count > 1000) throw new Error(`Dice count must be between 1 and 1000, got ${count}`)
|
|
28
|
+
if (sides < 2 || sides > 10000) throw new Error(`Dice sides must be between 2 and 10000, got ${sides}`)
|
|
29
|
+
return { count, sides, modifier }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function rollDie(sides: number): number {
|
|
33
|
+
return Math.floor(Math.random() * sides) + 1
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const dice = {
|
|
37
|
+
async roll(notation: string): Promise<RollResult> {
|
|
38
|
+
try {
|
|
39
|
+
const { count, sides, modifier } = parseNotation(notation)
|
|
40
|
+
const rolls = Array.from({ length: count }, () => rollDie(sides))
|
|
41
|
+
const total = rolls.reduce((a, b) => a + b, 0) + modifier
|
|
42
|
+
const result: RollResult = {
|
|
43
|
+
notation,
|
|
44
|
+
rolls,
|
|
45
|
+
modifier,
|
|
46
|
+
total,
|
|
47
|
+
min: count + modifier,
|
|
48
|
+
max: count * sides + modifier,
|
|
49
|
+
}
|
|
50
|
+
console.log(JSON.stringify({ ok: true, op: "dice.roll", data: result }))
|
|
51
|
+
return result
|
|
52
|
+
} catch (err: any) {
|
|
53
|
+
console.log(JSON.stringify({ ok: false, op: "dice.roll", data: { notation }, error: err.message }))
|
|
54
|
+
throw err
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
|
|
58
|
+
async rollMany(notation: string, times: number): Promise<{ rolls: RollResult[]; totals: number[]; stats: { mean: number; min: number; max: number } }> {
|
|
59
|
+
try {
|
|
60
|
+
if (times < 1 || times > 10000) throw new Error(`times must be between 1 and 10000, got ${times}`)
|
|
61
|
+
const { count, sides, modifier } = parseNotation(notation)
|
|
62
|
+
const rolls: RollResult[] = Array.from({ length: times }, () => {
|
|
63
|
+
const r = Array.from({ length: count }, () => rollDie(sides))
|
|
64
|
+
const total = r.reduce((a, b) => a + b, 0) + modifier
|
|
65
|
+
return { notation, rolls: r, modifier, total, min: count + modifier, max: count * sides + modifier }
|
|
66
|
+
})
|
|
67
|
+
const totals = rolls.map(r => r.total)
|
|
68
|
+
const mean = totals.reduce((a, b) => a + b, 0) / totals.length
|
|
69
|
+
const result = {
|
|
70
|
+
rolls,
|
|
71
|
+
totals,
|
|
72
|
+
stats: { mean, min: Math.min(...totals), max: Math.max(...totals) },
|
|
73
|
+
}
|
|
74
|
+
console.log(JSON.stringify({ ok: true, op: "dice.rollMany", data: { notation, times, stats: result.stats } }))
|
|
75
|
+
return result
|
|
76
|
+
} catch (err: any) {
|
|
77
|
+
console.log(JSON.stringify({ ok: false, op: "dice.rollMany", data: { notation, times }, error: err.message }))
|
|
78
|
+
throw err
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
async coin(flips = 1): Promise<CoinResult> {
|
|
83
|
+
try {
|
|
84
|
+
if (flips < 1 || flips > 10000) throw new Error(`flips must be between 1 and 10000, got ${flips}`)
|
|
85
|
+
const results: Array<"heads" | "tails"> = Array.from({ length: flips }, () =>
|
|
86
|
+
Math.random() < 0.5 ? "heads" : "tails"
|
|
87
|
+
)
|
|
88
|
+
const heads = results.filter(r => r === "heads").length
|
|
89
|
+
const result: CoinResult = { flips, results, heads, tails: flips - heads }
|
|
90
|
+
console.log(JSON.stringify({ ok: true, op: "dice.coin", data: { flips, heads, tails: result.tails } }))
|
|
91
|
+
return result
|
|
92
|
+
} catch (err: any) {
|
|
93
|
+
console.log(JSON.stringify({ ok: false, op: "dice.coin", data: { flips }, error: err.message }))
|
|
94
|
+
throw err
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
async pick(items: unknown[], count = 1): Promise<unknown[]> {
|
|
99
|
+
try {
|
|
100
|
+
if (count < 1 || count > items.length) throw new Error(`count must be between 1 and ${items.length}, got ${count}`)
|
|
101
|
+
const shuffled = [...items].sort(() => Math.random() - 0.5)
|
|
102
|
+
const result = shuffled.slice(0, count)
|
|
103
|
+
console.log(JSON.stringify({ ok: true, op: "dice.pick", data: { from: items.length, count, result } }))
|
|
104
|
+
return result
|
|
105
|
+
} catch (err: any) {
|
|
106
|
+
console.log(JSON.stringify({ ok: false, op: "dice.pick", data: { count }, error: err.message }))
|
|
107
|
+
throw err
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export default defineModule({
|
|
113
|
+
name: "dice",
|
|
114
|
+
version: "1.0.0",
|
|
115
|
+
description: "Dice roller and randomness module — roll dice in standard notation, flip coins, and pick random items",
|
|
116
|
+
api: dice,
|
|
117
|
+
})
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@capsuleer/dice",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Dice roller and randomness module for capsuleer — standard dice notation, coin flips, and random selection",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./index.ts",
|
|
7
|
+
"types": "./index.ts",
|
|
8
|
+
"keywords": [
|
|
9
|
+
"capsuleer",
|
|
10
|
+
"dice",
|
|
11
|
+
"random",
|
|
12
|
+
"rpg"
|
|
13
|
+
],
|
|
14
|
+
"author": "cody",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@capsuleer/core": "latest"
|
|
18
|
+
},
|
|
19
|
+
"peerDependencies": {
|
|
20
|
+
"bun": ">=1.0.0"
|
|
21
|
+
}
|
|
22
|
+
}
|