@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 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
+ }