pdrng 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/CONTRIBUTING.md +15 -0
- package/LICENSE +21 -0
- package/README.md +260 -0
- package/index.js +716 -0
- package/package.json +50 -0
package/CONTRIBUTING.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
When contributing to this repository, please first discuss the change you wish to make via issue,
|
|
4
|
+
email, or any other method with the owners of this repository before making a change.
|
|
5
|
+
|
|
6
|
+
## Pull Request Process
|
|
7
|
+
|
|
8
|
+
1. Ensure any install or build dependencies are removed before the end of the layer when doing a
|
|
9
|
+
build.
|
|
10
|
+
2. Update the README.md with details of changes to the interface, this includes new environment
|
|
11
|
+
variables, exposed ports, useful file locations and container parameters.
|
|
12
|
+
3. Increase the version numbers in any examples files and the README.md to the new version that this
|
|
13
|
+
Pull Request would represent. The versioning scheme we use is [SemVer](http://semver.org/).
|
|
14
|
+
4. You may merge the Pull Request in once you have the sign-off of two other developers, or if you
|
|
15
|
+
do not have permission to do that, you may request the second reviewer to merge it for you.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Brian Funk
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
[](https://github.com/brianfunk/pdrng)
|
|
2
|
+
[](https://www.npmjs.com/package/pdrng)
|
|
3
|
+
[](https://www.npmjs.com/package/pdrng)
|
|
4
|
+
[](https://github.com/brianfunk/pdrng/actions/workflows/ci.yml)
|
|
5
|
+
[](https://github.com/ellerbrock/open-source-badge/)
|
|
6
|
+
[](http://semver.org/spec/v2.0.0.html)
|
|
7
|
+
[](https://opensource.org/licenses/MIT)
|
|
8
|
+
[](https://www.linkedin.com/in/brianrandyfunk)
|
|
9
|
+
|
|
10
|
+
# pdrng
|
|
11
|
+
|
|
12
|
+
> Pseudo Deterministic Random Number Generator — seed-based deterministic number generation.
|
|
13
|
+
|
|
14
|
+
A JavaScript library for generating deterministic outputs based on a numeric seed. Given the same seed, every function produces the same result every time. Useful for testing, simulations, reproducible demos, and seeded content generation.
|
|
15
|
+
|
|
16
|
+
**Default seed: 814**
|
|
17
|
+
|
|
18
|
+
## Install
|
|
19
|
+
|
|
20
|
+
```bash
|
|
21
|
+
npm install pdrng
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Quick Start
|
|
25
|
+
|
|
26
|
+
```javascript
|
|
27
|
+
import pdrng from 'pdrng';
|
|
28
|
+
|
|
29
|
+
pdrng() // 814
|
|
30
|
+
pdrng(1) // 8
|
|
31
|
+
pdrng(6) // 814814
|
|
32
|
+
pdrng.coin() // "tails"
|
|
33
|
+
pdrng.dice() // 4
|
|
34
|
+
pdrng.card() // "8 of Diamonds"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## API
|
|
38
|
+
|
|
39
|
+
### Core
|
|
40
|
+
|
|
41
|
+
#### `pdrng(digits?, options?)`
|
|
42
|
+
|
|
43
|
+
Generate a deterministic number with the specified number of digits.
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
pdrng() // 814 (default: 3 digits)
|
|
47
|
+
pdrng(1) // 8
|
|
48
|
+
pdrng(2) // 14
|
|
49
|
+
pdrng(4) // 8148
|
|
50
|
+
pdrng(5) // 81414
|
|
51
|
+
pdrng(6) // 814814
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
### Utilities
|
|
55
|
+
|
|
56
|
+
#### `float(precision?, options?)`
|
|
57
|
+
|
|
58
|
+
```javascript
|
|
59
|
+
float() // 0.814814
|
|
60
|
+
float(3) // 0.814
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
#### `range(min, max, options?)`
|
|
64
|
+
|
|
65
|
+
```javascript
|
|
66
|
+
range(1, 100) // 14
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
#### `array(count, digits?, options?)`
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
array(3, 2) // [14, 46, 78] (sub-seeds per element)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
#### `uuid(options?)`
|
|
76
|
+
|
|
77
|
+
```javascript
|
|
78
|
+
uuid() // deterministic UUID v4 format
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
#### `oddOrEven(options?)`
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
oddOrEven() // "even"
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
#### `redOrBlack(options?)`
|
|
88
|
+
|
|
89
|
+
```javascript
|
|
90
|
+
redOrBlack() // "red"
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
#### `randomSeed()`
|
|
94
|
+
|
|
95
|
+
Generate a random seed using `crypto.getRandomValues` (with `Math.random` fallback).
|
|
96
|
+
|
|
97
|
+
```javascript
|
|
98
|
+
randomSeed() // e.g. 3847291056 (different each call)
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Simulation Functions
|
|
102
|
+
|
|
103
|
+
#### `coin(options?)`
|
|
104
|
+
|
|
105
|
+
Deterministic coin flip.
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
coin() // "tails"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
#### `dice(sides?, options?)`
|
|
112
|
+
|
|
113
|
+
Deterministic die result.
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
dice() // 4 (6-sided)
|
|
117
|
+
dice(20) // 14
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
#### `card(options?)`
|
|
121
|
+
|
|
122
|
+
Deterministic playing card draw.
|
|
123
|
+
|
|
124
|
+
```javascript
|
|
125
|
+
card() // "8 of Diamonds"
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
#### `rps(options?)`
|
|
129
|
+
|
|
130
|
+
Deterministic rock-paper-scissors.
|
|
131
|
+
|
|
132
|
+
```javascript
|
|
133
|
+
rps() // "scissors"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
#### `magic8(options?)`
|
|
137
|
+
|
|
138
|
+
Deterministic 8-ball response.
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
magic8() // "Reply hazy, try again."
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
#### `zodiac(options?)`
|
|
145
|
+
|
|
146
|
+
Deterministic zodiac sign.
|
|
147
|
+
|
|
148
|
+
```javascript
|
|
149
|
+
zodiac() // "Gemini"
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
#### `tarot(options?)`
|
|
153
|
+
|
|
154
|
+
Deterministic tarot card.
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
tarot() // "The Magician"
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
#### `fortune(options?)`
|
|
161
|
+
|
|
162
|
+
Deterministic fortune message.
|
|
163
|
+
|
|
164
|
+
```javascript
|
|
165
|
+
fortune() // "The answer you seek was never in doubt."
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
#### `spin(array, options?)`
|
|
169
|
+
|
|
170
|
+
Deterministic selection from an array.
|
|
171
|
+
|
|
172
|
+
```javascript
|
|
173
|
+
spin(['a', 'b', 'c', 'd']) // "c"
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
#### `roll(notation, options?)`
|
|
177
|
+
|
|
178
|
+
Deterministic dice notation result (tabletop RPG style).
|
|
179
|
+
|
|
180
|
+
```javascript
|
|
181
|
+
roll('2d6+3') // { rolls: [5, 1], modifier: 3, total: 9 }
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
#### `bingo(options?)`
|
|
185
|
+
|
|
186
|
+
Deterministic bingo call.
|
|
187
|
+
|
|
188
|
+
```javascript
|
|
189
|
+
bingo() // "B-14"
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
#### `color(options?)`
|
|
193
|
+
|
|
194
|
+
Deterministic hex color.
|
|
195
|
+
|
|
196
|
+
```javascript
|
|
197
|
+
color() // "#a81414"
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
#### `roulette(options?)`
|
|
201
|
+
|
|
202
|
+
Deterministic number with color and parity.
|
|
203
|
+
|
|
204
|
+
```javascript
|
|
205
|
+
roulette() // { number: 14, color: "red", parity: "even" }
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
## Custom Seeds
|
|
209
|
+
|
|
210
|
+
Every function accepts an `options` object with a `seed` property:
|
|
211
|
+
|
|
212
|
+
```javascript
|
|
213
|
+
import { coin, dice, card } from 'pdrng';
|
|
214
|
+
|
|
215
|
+
coin({ seed: 42 }) // "heads"
|
|
216
|
+
dice(6, { seed: 42 }) // 4
|
|
217
|
+
card({ seed: 'brian' }) // deterministic card from text seed
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
Text seeds are converted using a rolling XOR hash, designed so that `"brian"` maps to seed 814:
|
|
221
|
+
|
|
222
|
+
```javascript
|
|
223
|
+
pdrng(3, { seed: 'brian' }) // 814 (same as default!)
|
|
224
|
+
pdrng(4, { seed: 'brian' }) // 8148
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
## Random Seeds
|
|
228
|
+
|
|
229
|
+
While pdrng is deterministic by design, you can use random input to get non-deterministic behavior. Pass `Math.random()`, `crypto.getRandomValues()`, or use the built-in `randomSeed()` helper:
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
import { coin, dice, randomSeed } from 'pdrng';
|
|
233
|
+
|
|
234
|
+
// Built-in helper (uses crypto.getRandomValues for real entropy)
|
|
235
|
+
coin({ seed: randomSeed() }) // different each call
|
|
236
|
+
|
|
237
|
+
// Math.random() works directly as a seed
|
|
238
|
+
dice(6, { seed: Math.random() }) // different each call
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## Imports
|
|
242
|
+
|
|
243
|
+
```javascript
|
|
244
|
+
// Default import (with all methods attached)
|
|
245
|
+
import pdrng from 'pdrng';
|
|
246
|
+
pdrng.coin();
|
|
247
|
+
|
|
248
|
+
// Named imports
|
|
249
|
+
import { coin, dice, card, roll } from 'pdrng';
|
|
250
|
+
coin();
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
## Requirements
|
|
254
|
+
|
|
255
|
+
- Node.js 18+
|
|
256
|
+
- ESM only (`import`/`export`)
|
|
257
|
+
|
|
258
|
+
## License
|
|
259
|
+
|
|
260
|
+
MIT
|
package/index.js
ADDED
|
@@ -0,0 +1,716 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* _
|
|
3
|
+
* _ __ __| |_ __ _ __ __ _
|
|
4
|
+
* | '_ \ / _` | '__| '_ \ / _` |
|
|
5
|
+
* | |_) | (_| | | | | | | (_| |
|
|
6
|
+
* | .__/ \__,_|_| |_| |_|\__, |
|
|
7
|
+
* |_| |___/
|
|
8
|
+
*
|
|
9
|
+
* pdrng - Pseudo Deterministic Random Number Generator
|
|
10
|
+
*
|
|
11
|
+
* A seed-based deterministic number generator.
|
|
12
|
+
* All outputs are fully reproducible given the same seed (default: 814).
|
|
13
|
+
*
|
|
14
|
+
* @module pdrng
|
|
15
|
+
* @version 1.0.0
|
|
16
|
+
* @license MIT
|
|
17
|
+
* @author Brian Funk
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// ─── Default Seed ────────────────────────────────────────────────────────────
|
|
21
|
+
|
|
22
|
+
const DEFAULT_SEED = 814;
|
|
23
|
+
|
|
24
|
+
// ─── Private Helpers ─────────────────────────────────────────────────────────
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Convert a text string to a numeric seed.
|
|
28
|
+
* Rolling XOR hash: each character mixes with the running hash via XOR shift.
|
|
29
|
+
* Formula: 2 * (rollingHash + length), where rollingHash starts at 3.
|
|
30
|
+
* Designed so that "brian" → 814.
|
|
31
|
+
* @param {string} text
|
|
32
|
+
* @returns {number}
|
|
33
|
+
*/
|
|
34
|
+
const _textToSeed = (text) => {
|
|
35
|
+
const str = String(text);
|
|
36
|
+
let hash = 3;
|
|
37
|
+
for (let i = 0; i < str.length; i++) {
|
|
38
|
+
hash = hash + (str.charCodeAt(i) ^ (hash >> 2));
|
|
39
|
+
}
|
|
40
|
+
return 2 * (hash + str.length);
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Normalize a seed value to a positive integer.
|
|
45
|
+
* Strings are converted via _textToSeed, numbers are floored and abs'd.
|
|
46
|
+
* @param {number|string} seed
|
|
47
|
+
* @returns {number}
|
|
48
|
+
*/
|
|
49
|
+
const _normalizeSeed = (seed) => {
|
|
50
|
+
if (seed === undefined || seed === null) return DEFAULT_SEED;
|
|
51
|
+
if (typeof seed === 'string') return _textToSeed(seed);
|
|
52
|
+
const num = Number(seed);
|
|
53
|
+
if (!Number.isFinite(num)) return DEFAULT_SEED;
|
|
54
|
+
const abs = Math.abs(num);
|
|
55
|
+
// Handle floats between 0 and 1 (e.g. Math.random()) by scaling up
|
|
56
|
+
if (abs > 0 && abs < 1) {
|
|
57
|
+
const str = String(abs).replace('0.', '');
|
|
58
|
+
return Number(str) || DEFAULT_SEED;
|
|
59
|
+
}
|
|
60
|
+
const n = Math.floor(abs);
|
|
61
|
+
return n === 0 ? DEFAULT_SEED : n;
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get the array of digits from a seed.
|
|
66
|
+
* @param {number} seed
|
|
67
|
+
* @returns {number[]}
|
|
68
|
+
*/
|
|
69
|
+
const _digits = (seed) => String(seed).split('').map(Number);
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Sum of all digits.
|
|
73
|
+
* @param {number} seed
|
|
74
|
+
* @returns {number}
|
|
75
|
+
*/
|
|
76
|
+
const _digitSum = (seed) => _digits(seed).reduce((a, b) => a + b, 0);
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Product of all digits.
|
|
80
|
+
* @param {number} seed
|
|
81
|
+
* @returns {number}
|
|
82
|
+
*/
|
|
83
|
+
const _digitProduct = (seed) => _digits(seed).reduce((a, b) => a * b, 1);
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* First digit of the seed.
|
|
87
|
+
* @param {number} seed
|
|
88
|
+
* @returns {number}
|
|
89
|
+
*/
|
|
90
|
+
const _firstDigit = (seed) => _digits(seed)[0];
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Last digit of the seed.
|
|
94
|
+
* @param {number} seed
|
|
95
|
+
* @returns {number}
|
|
96
|
+
*/
|
|
97
|
+
const _lastDigit = (seed) => {
|
|
98
|
+
const d = _digits(seed);
|
|
99
|
+
return d[d.length - 1];
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Last N digits of the seed as a number.
|
|
104
|
+
* @param {number} seed
|
|
105
|
+
* @param {number} n
|
|
106
|
+
* @returns {number}
|
|
107
|
+
*/
|
|
108
|
+
const _lastN = (seed, n) => {
|
|
109
|
+
const str = String(seed);
|
|
110
|
+
if (n >= str.length) return seed;
|
|
111
|
+
return Number(str.slice(-n));
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Digit-fill algorithm: produce a number with exactly `count` digits
|
|
116
|
+
* derived deterministically from the seed.
|
|
117
|
+
*
|
|
118
|
+
* Rules:
|
|
119
|
+
* - count < seed length: 1 digit → first digit, else last N digits
|
|
120
|
+
* - count = seed length: full seed
|
|
121
|
+
* - count > seed length: repeat full seed, remainder uses 1 char = first digit, 2+ chars = last N
|
|
122
|
+
*
|
|
123
|
+
* @param {number} seed
|
|
124
|
+
* @param {number} count
|
|
125
|
+
* @returns {number}
|
|
126
|
+
*/
|
|
127
|
+
const _fillDigits = (seed, count) => {
|
|
128
|
+
const seedStr = String(seed);
|
|
129
|
+
const seedLen = seedStr.length;
|
|
130
|
+
|
|
131
|
+
if (count <= 0) return 0;
|
|
132
|
+
|
|
133
|
+
if (count < seedLen) {
|
|
134
|
+
if (count === 1) return _firstDigit(seed);
|
|
135
|
+
return _lastN(seed, count);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (count === seedLen) return seed;
|
|
139
|
+
|
|
140
|
+
// count > seedLen: repeat seed, then fill remainder
|
|
141
|
+
let result = '';
|
|
142
|
+
const fullRepeats = Math.floor(count / seedLen);
|
|
143
|
+
const remainder = count % seedLen;
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i < fullRepeats; i++) {
|
|
146
|
+
result += seedStr;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (remainder > 0) {
|
|
150
|
+
if (remainder === 1) {
|
|
151
|
+
result += String(_firstDigit(seed));
|
|
152
|
+
} else {
|
|
153
|
+
result += String(_lastN(seed, remainder));
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return Number(result);
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* Build a priority list of values derived from the seed.
|
|
162
|
+
* Order: full seed, last N-1 digits ... last 2 digits, first digit, last digit, middle digits L→R.
|
|
163
|
+
*
|
|
164
|
+
* @param {number} seed
|
|
165
|
+
* @returns {number[]}
|
|
166
|
+
*/
|
|
167
|
+
const _seedPriority = (seed) => {
|
|
168
|
+
const seedStr = String(seed);
|
|
169
|
+
const n = seedStr.length;
|
|
170
|
+
const values = [seed];
|
|
171
|
+
|
|
172
|
+
// Last N-1 down to last 2 digits
|
|
173
|
+
for (let i = n - 1; i >= 2; i--) {
|
|
174
|
+
values.push(Number(seedStr.slice(-i)));
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// First digit
|
|
178
|
+
values.push(Number(seedStr[0]));
|
|
179
|
+
|
|
180
|
+
// Last digit
|
|
181
|
+
if (n > 1) {
|
|
182
|
+
values.push(Number(seedStr[n - 1]));
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Remaining middle digits, left to right
|
|
186
|
+
for (let i = 1; i < n - 1; i++) {
|
|
187
|
+
values.push(Number(seedStr[i]));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return values;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Select a value from [min, max] using the seed priority algorithm.
|
|
195
|
+
* Returns the first priority value that falls within [min, max].
|
|
196
|
+
* Falls back to modulo-based selection if no priority value fits.
|
|
197
|
+
*
|
|
198
|
+
* @param {number} seed
|
|
199
|
+
* @param {number} min
|
|
200
|
+
* @param {number} max
|
|
201
|
+
* @returns {number}
|
|
202
|
+
*/
|
|
203
|
+
const _selectFromRange = (seed, min, max) => {
|
|
204
|
+
const priorities = _seedPriority(seed);
|
|
205
|
+
for (const val of priorities) {
|
|
206
|
+
if (val >= min && val <= max) {
|
|
207
|
+
return val;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
return min + (seed % (max - min + 1));
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// ─── Data Constants ──────────────────────────────────────────────────────────
|
|
214
|
+
|
|
215
|
+
const MAGIC_8_RESPONSES = Object.freeze([
|
|
216
|
+
'It is certain.',
|
|
217
|
+
'It is decidedly so.',
|
|
218
|
+
'Without a doubt.',
|
|
219
|
+
'Yes — definitely.',
|
|
220
|
+
'You may rely on it.',
|
|
221
|
+
'As I see it, yes.',
|
|
222
|
+
'Most likely.',
|
|
223
|
+
'Outlook good.',
|
|
224
|
+
'Yes.',
|
|
225
|
+
'Signs point to yes.',
|
|
226
|
+
'Reply hazy, try again.',
|
|
227
|
+
'Ask again later.',
|
|
228
|
+
'Better not tell you now.',
|
|
229
|
+
'Cannot predict now.',
|
|
230
|
+
'Reply hazy, try again.',
|
|
231
|
+
'Don\'t count on it.',
|
|
232
|
+
'My reply is no.',
|
|
233
|
+
'My sources say no.',
|
|
234
|
+
'Outlook not so good.',
|
|
235
|
+
'Very doubtful.'
|
|
236
|
+
]);
|
|
237
|
+
|
|
238
|
+
const MAJOR_ARCANA = Object.freeze([
|
|
239
|
+
'The Magician',
|
|
240
|
+
'The High Priestess',
|
|
241
|
+
'The Empress',
|
|
242
|
+
'The Emperor',
|
|
243
|
+
'The Hierophant',
|
|
244
|
+
'The Lovers',
|
|
245
|
+
'The Chariot',
|
|
246
|
+
'Strength',
|
|
247
|
+
'The Hermit',
|
|
248
|
+
'Wheel of Fortune',
|
|
249
|
+
'Justice',
|
|
250
|
+
'The Hanged Man',
|
|
251
|
+
'Death',
|
|
252
|
+
'Temperance',
|
|
253
|
+
'The Devil',
|
|
254
|
+
'The Tower',
|
|
255
|
+
'The Star',
|
|
256
|
+
'The Moon',
|
|
257
|
+
'The Sun',
|
|
258
|
+
'Judgement',
|
|
259
|
+
'The World',
|
|
260
|
+
'The Fool'
|
|
261
|
+
]);
|
|
262
|
+
|
|
263
|
+
const FORTUNES = Object.freeze([
|
|
264
|
+
'A beautiful, smart, and loving person will be coming into your life.',
|
|
265
|
+
'A dubious friend may be an enemy in camouflage.',
|
|
266
|
+
'A faithful friend is a strong defense.',
|
|
267
|
+
'A feather in the hand is better than a bird in the air.',
|
|
268
|
+
'A fresh start will put you on your way.',
|
|
269
|
+
'A golden egg of opportunity falls into your lap this month.',
|
|
270
|
+
'A good friendship is often more important than a passionate romance.',
|
|
271
|
+
'A good time to finish up old tasks.',
|
|
272
|
+
'A lifetime friend shall soon be made.',
|
|
273
|
+
'A lifetime of happiness lies ahead of you.',
|
|
274
|
+
'A light heart carries you through all the hard times.',
|
|
275
|
+
'A new perspective will come with the new year.',
|
|
276
|
+
'A pleasant surprise is waiting for you.',
|
|
277
|
+
'The answer you seek was never in doubt.',
|
|
278
|
+
'A smile is your passport into the hearts of others.',
|
|
279
|
+
'A smooth long journey! Great expectations.',
|
|
280
|
+
'A soft voice may be awfully persuasive.',
|
|
281
|
+
'A true friend is the best possession.',
|
|
282
|
+
'Accept something that you cannot change, and you will feel better.',
|
|
283
|
+
'All the effort you are making will ultimately pay off.'
|
|
284
|
+
]);
|
|
285
|
+
|
|
286
|
+
const SUITS = Object.freeze(['Spades', 'Diamonds', 'Hearts', 'Clubs']);
|
|
287
|
+
|
|
288
|
+
const RANKS = Object.freeze([
|
|
289
|
+
'Ace', '2', '3', '4', '5', '6', '7', '8', '9', '10',
|
|
290
|
+
'Jack', 'Queen', 'King'
|
|
291
|
+
]);
|
|
292
|
+
|
|
293
|
+
const RPS_OPTIONS = Object.freeze(['rock', 'paper', 'scissors']);
|
|
294
|
+
|
|
295
|
+
const COIN_SIDES = Object.freeze(['heads', 'tails']);
|
|
296
|
+
|
|
297
|
+
const BINGO_LETTERS = Object.freeze(['B', 'I', 'N', 'G', 'O']);
|
|
298
|
+
|
|
299
|
+
const RED_NUMBERS = Object.freeze([
|
|
300
|
+
1, 3, 5, 7, 9, 12, 14, 16, 18, 19, 21, 23, 25, 27, 30, 32, 34, 36
|
|
301
|
+
]);
|
|
302
|
+
|
|
303
|
+
const ZODIAC_SIGNS = Object.freeze([
|
|
304
|
+
{ name: 'Aries', month: 3, startDay: 21 },
|
|
305
|
+
{ name: 'Taurus', month: 4, startDay: 20 },
|
|
306
|
+
{ name: 'Gemini', month: 5, startDay: 21 },
|
|
307
|
+
{ name: 'Cancer', month: 6, startDay: 21 },
|
|
308
|
+
{ name: 'Leo', month: 7, startDay: 23 },
|
|
309
|
+
{ name: 'Virgo', month: 8, startDay: 23 },
|
|
310
|
+
{ name: 'Libra', month: 9, startDay: 23 },
|
|
311
|
+
{ name: 'Scorpio', month: 10, startDay: 23 },
|
|
312
|
+
{ name: 'Sagittarius', month: 11, startDay: 22 },
|
|
313
|
+
{ name: 'Capricorn', month: 12, startDay: 22 },
|
|
314
|
+
{ name: 'Aquarius', month: 1, startDay: 20 },
|
|
315
|
+
{ name: 'Pisces', month: 2, startDay: 19 }
|
|
316
|
+
]);
|
|
317
|
+
|
|
318
|
+
// ─── Core Function ───────────────────────────────────────────────────────────
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Generate a deterministic "random" number with the specified number of digits.
|
|
322
|
+
*
|
|
323
|
+
* @param {number} [digits=3] - Number of digits in the result
|
|
324
|
+
* @param {Object} [options={}] - Options
|
|
325
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
326
|
+
* @returns {number}
|
|
327
|
+
*/
|
|
328
|
+
const pdrng = (digits = 3, options = {}) => {
|
|
329
|
+
const seed = _normalizeSeed(options.seed);
|
|
330
|
+
return _fillDigits(seed, digits);
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// ─── Utility Functions ───────────────────────────────────────────────────────
|
|
334
|
+
|
|
335
|
+
/**
|
|
336
|
+
* Generate a deterministic float between 0 and 1.
|
|
337
|
+
*
|
|
338
|
+
* @param {number} [precision=6] - Number of decimal places
|
|
339
|
+
* @param {Object} [options={}] - Options
|
|
340
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
341
|
+
* @returns {number}
|
|
342
|
+
*/
|
|
343
|
+
const float = (precision = 6, options = {}) => {
|
|
344
|
+
const seed = _normalizeSeed(options.seed);
|
|
345
|
+
const filled = _fillDigits(seed, precision);
|
|
346
|
+
return Number('0.' + String(filled).padStart(precision, '0'));
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Generate a deterministic integer within a range (inclusive).
|
|
351
|
+
*
|
|
352
|
+
* @param {number} min - Minimum value
|
|
353
|
+
* @param {number} max - Maximum value
|
|
354
|
+
* @param {Object} [options={}] - Options
|
|
355
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
356
|
+
* @returns {number}
|
|
357
|
+
*/
|
|
358
|
+
const range = (min, max, options = {}) => {
|
|
359
|
+
const seed = _normalizeSeed(options.seed);
|
|
360
|
+
return _selectFromRange(seed, min, max);
|
|
361
|
+
};
|
|
362
|
+
|
|
363
|
+
/**
|
|
364
|
+
* Generate an array of deterministic numbers.
|
|
365
|
+
* Each element uses a sub-seed derived from the main seed + index.
|
|
366
|
+
*
|
|
367
|
+
* @param {number} count - Number of elements
|
|
368
|
+
* @param {number} [digits=3] - Digits per element
|
|
369
|
+
* @param {Object} [options={}] - Options
|
|
370
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
371
|
+
* @returns {number[]}
|
|
372
|
+
*/
|
|
373
|
+
const array = (count, digits = 3, options = {}) => {
|
|
374
|
+
const seed = _normalizeSeed(options.seed);
|
|
375
|
+
const result = [];
|
|
376
|
+
for (let i = 0; i < count; i++) {
|
|
377
|
+
const subSeed = seed + i * _digitProduct(seed);
|
|
378
|
+
result.push(_fillDigits(_normalizeSeed(subSeed), digits));
|
|
379
|
+
}
|
|
380
|
+
return result;
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
/**
|
|
384
|
+
* Generate a deterministic UUID (v4 format).
|
|
385
|
+
*
|
|
386
|
+
* @param {Object} [options={}] - Options
|
|
387
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
388
|
+
* @returns {string}
|
|
389
|
+
*/
|
|
390
|
+
const uuid = (options = {}) => {
|
|
391
|
+
const seed = _normalizeSeed(options.seed);
|
|
392
|
+
const ds = _digitSum(seed);
|
|
393
|
+
const dp = _digitProduct(seed);
|
|
394
|
+
const fd = _firstDigit(seed);
|
|
395
|
+
const ld = _lastDigit(seed);
|
|
396
|
+
|
|
397
|
+
// Build 32 hex chars deterministically
|
|
398
|
+
const sources = [seed, ds, dp, fd, ld, seed + ds, seed + dp, seed * fd];
|
|
399
|
+
let hex = '';
|
|
400
|
+
for (const src of sources) {
|
|
401
|
+
let val = Math.abs(src);
|
|
402
|
+
for (let i = 0; i < 4; i++) {
|
|
403
|
+
hex += ((val + i * 7) % 16).toString(16);
|
|
404
|
+
}
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
// Format as UUID v4: xxxxxxxx-xxxx-4xxx-Nxxx-xxxxxxxxxxxx
|
|
408
|
+
hex = hex.slice(0, 32);
|
|
409
|
+
const chars = hex.split('');
|
|
410
|
+
chars[12] = '4'; // version 4
|
|
411
|
+
const n = (8 + (_digitSum(seed) % 4)); // variant: 8, 9, a, or b
|
|
412
|
+
chars[16] = n.toString(16);
|
|
413
|
+
|
|
414
|
+
return [
|
|
415
|
+
chars.slice(0, 8).join(''),
|
|
416
|
+
chars.slice(8, 12).join(''),
|
|
417
|
+
chars.slice(12, 16).join(''),
|
|
418
|
+
chars.slice(16, 20).join(''),
|
|
419
|
+
chars.slice(20, 32).join('')
|
|
420
|
+
].join('-');
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Determine if the seed is odd or even.
|
|
425
|
+
*
|
|
426
|
+
* @param {Object} [options={}] - Options
|
|
427
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
428
|
+
* @returns {string} "odd" or "even"
|
|
429
|
+
*/
|
|
430
|
+
const oddOrEven = (options = {}) => {
|
|
431
|
+
const seed = _normalizeSeed(options.seed);
|
|
432
|
+
return seed % 2 === 0 ? 'even' : 'odd';
|
|
433
|
+
};
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Determine red or black based on digit sum.
|
|
437
|
+
*
|
|
438
|
+
* @param {Object} [options={}] - Options
|
|
439
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
440
|
+
* @returns {string} "red" or "black"
|
|
441
|
+
*/
|
|
442
|
+
const redOrBlack = (options = {}) => {
|
|
443
|
+
const seed = _normalizeSeed(options.seed);
|
|
444
|
+
return _digitSum(seed) % 2 === 1 ? 'red' : 'black';
|
|
445
|
+
};
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Generate a random seed using crypto for true entropy.
|
|
449
|
+
* Pass the result as { seed: randomSeed() } to any pdrng function
|
|
450
|
+
* for non-deterministic behavior.
|
|
451
|
+
*
|
|
452
|
+
* @returns {number}
|
|
453
|
+
*/
|
|
454
|
+
const randomSeed = () => {
|
|
455
|
+
if (typeof globalThis.crypto !== 'undefined' && globalThis.crypto.getRandomValues) {
|
|
456
|
+
const buffer = new Uint32Array(1);
|
|
457
|
+
globalThis.crypto.getRandomValues(buffer);
|
|
458
|
+
return buffer[0] || DEFAULT_SEED;
|
|
459
|
+
}
|
|
460
|
+
// Fallback for environments without Web Crypto API
|
|
461
|
+
return Math.floor(Math.random() * 2147483647) || DEFAULT_SEED;
|
|
462
|
+
};
|
|
463
|
+
|
|
464
|
+
// ─── Simulation Functions ────────────────────────────────────────────────────
|
|
465
|
+
|
|
466
|
+
/**
|
|
467
|
+
* Flip a deterministic coin.
|
|
468
|
+
*
|
|
469
|
+
* @param {Object} [options={}] - Options
|
|
470
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
471
|
+
* @returns {string} "heads" or "tails"
|
|
472
|
+
*/
|
|
473
|
+
const coin = (options = {}) => {
|
|
474
|
+
const seed = _normalizeSeed(options.seed);
|
|
475
|
+
return COIN_SIDES[_digitSum(seed) % 2];
|
|
476
|
+
};
|
|
477
|
+
|
|
478
|
+
/**
|
|
479
|
+
* Roll a deterministic die.
|
|
480
|
+
*
|
|
481
|
+
* @param {number} [sides=6] - Number of sides
|
|
482
|
+
* @param {Object} [options={}] - Options
|
|
483
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
484
|
+
* @returns {number} 1 to sides
|
|
485
|
+
*/
|
|
486
|
+
const dice = (sides = 6, options = {}) => {
|
|
487
|
+
const seed = _normalizeSeed(options.seed);
|
|
488
|
+
return _selectFromRange(seed, 1, sides);
|
|
489
|
+
};
|
|
490
|
+
|
|
491
|
+
/**
|
|
492
|
+
* Draw a deterministic playing card.
|
|
493
|
+
*
|
|
494
|
+
* @param {Object} [options={}] - Options
|
|
495
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
496
|
+
* @returns {string} e.g. "8 of Diamonds"
|
|
497
|
+
*/
|
|
498
|
+
const card = (options = {}) => {
|
|
499
|
+
const seed = _normalizeSeed(options.seed);
|
|
500
|
+
const rankIndex = (seed - 1) % 13;
|
|
501
|
+
const suitIndex = _digitSum(seed) % 4;
|
|
502
|
+
return `${RANKS[rankIndex]} of ${SUITS[suitIndex]}`;
|
|
503
|
+
};
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Spin the roulette wheel deterministically.
|
|
507
|
+
*
|
|
508
|
+
* @param {Object} [options={}] - Options
|
|
509
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
510
|
+
* @returns {Object} { number, color, parity }
|
|
511
|
+
*/
|
|
512
|
+
const roulette = (options = {}) => {
|
|
513
|
+
const seed = _normalizeSeed(options.seed);
|
|
514
|
+
const num = _lastN(seed, 2) % 37;
|
|
515
|
+
|
|
516
|
+
let color;
|
|
517
|
+
if (num === 0) {
|
|
518
|
+
color = 'green';
|
|
519
|
+
} else if (RED_NUMBERS.includes(num)) {
|
|
520
|
+
color = 'red';
|
|
521
|
+
} else {
|
|
522
|
+
color = 'black';
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
const parity = num === 0 ? 'zero' : (num % 2 === 0 ? 'even' : 'odd');
|
|
526
|
+
|
|
527
|
+
return { number: num, color, parity };
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
/**
|
|
531
|
+
* Play rock, paper, scissors deterministically.
|
|
532
|
+
*
|
|
533
|
+
* @param {Object} [options={}] - Options
|
|
534
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
535
|
+
* @returns {string} "rock", "paper", or "scissors"
|
|
536
|
+
*/
|
|
537
|
+
const rps = (options = {}) => {
|
|
538
|
+
const seed = _normalizeSeed(options.seed);
|
|
539
|
+
return RPS_OPTIONS[_digitProduct(seed) % 3];
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
/**
|
|
543
|
+
* Shake the Magic 8-Ball deterministically.
|
|
544
|
+
*
|
|
545
|
+
* @param {Object} [options={}] - Options
|
|
546
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
547
|
+
* @returns {string}
|
|
548
|
+
*/
|
|
549
|
+
const magic8 = (options = {}) => {
|
|
550
|
+
const seed = _normalizeSeed(options.seed);
|
|
551
|
+
return MAGIC_8_RESPONSES[seed % 20];
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
/**
|
|
555
|
+
* Determine your deterministic zodiac sign.
|
|
556
|
+
*
|
|
557
|
+
* @param {Object} [options={}] - Options
|
|
558
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
559
|
+
* @returns {string} e.g. "Gemini"
|
|
560
|
+
*/
|
|
561
|
+
const zodiac = (options = {}) => {
|
|
562
|
+
const seed = _normalizeSeed(options.seed);
|
|
563
|
+
const signIndex = _lastN(seed, 2) % 12;
|
|
564
|
+
return ZODIAC_SIGNS[signIndex].name;
|
|
565
|
+
};
|
|
566
|
+
|
|
567
|
+
/**
|
|
568
|
+
* Draw a deterministic tarot card (Major Arcana).
|
|
569
|
+
*
|
|
570
|
+
* @param {Object} [options={}] - Options
|
|
571
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
572
|
+
* @returns {string}
|
|
573
|
+
*/
|
|
574
|
+
const tarot = (options = {}) => {
|
|
575
|
+
const seed = _normalizeSeed(options.seed);
|
|
576
|
+
return MAJOR_ARCANA[seed % 22];
|
|
577
|
+
};
|
|
578
|
+
|
|
579
|
+
/**
|
|
580
|
+
* Receive a deterministic fortune.
|
|
581
|
+
*
|
|
582
|
+
* @param {Object} [options={}] - Options
|
|
583
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
584
|
+
* @returns {string}
|
|
585
|
+
*/
|
|
586
|
+
const fortune = (options = {}) => {
|
|
587
|
+
const seed = _normalizeSeed(options.seed);
|
|
588
|
+
return FORTUNES[_digitSum(seed) % 20];
|
|
589
|
+
};
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Spin a wheel (pick from an array) deterministically.
|
|
593
|
+
*
|
|
594
|
+
* @param {Array} arr - Array of choices
|
|
595
|
+
* @param {Object} [options={}] - Options
|
|
596
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
597
|
+
* @returns {*}
|
|
598
|
+
*/
|
|
599
|
+
const spin = (arr, options = {}) => {
|
|
600
|
+
if (!Array.isArray(arr) || arr.length === 0) {
|
|
601
|
+
throw new Error('spin() requires a non-empty array');
|
|
602
|
+
}
|
|
603
|
+
const seed = _normalizeSeed(options.seed);
|
|
604
|
+
return arr[seed % arr.length];
|
|
605
|
+
};
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Roll dice using standard notation (e.g. "2d6+3").
|
|
609
|
+
*
|
|
610
|
+
* @param {string} notation - Dice notation like "2d6", "1d20+5", "3d8-2"
|
|
611
|
+
* @param {Object} [options={}] - Options
|
|
612
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
613
|
+
* @returns {Object} { rolls, modifier, total }
|
|
614
|
+
*/
|
|
615
|
+
const roll = (notation, options = {}) => {
|
|
616
|
+
const seed = _normalizeSeed(options.seed);
|
|
617
|
+
const match = String(notation).match(/^(\d+)d(\d+)([+-]\d+)?$/);
|
|
618
|
+
if (!match) {
|
|
619
|
+
throw new Error(`Invalid dice notation: "${notation}"`);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
const count = parseInt(match[1], 10);
|
|
623
|
+
const sides = parseInt(match[2], 10);
|
|
624
|
+
const modifier = match[3] ? parseInt(match[3], 10) : 0;
|
|
625
|
+
const dp = _digitProduct(seed);
|
|
626
|
+
|
|
627
|
+
const rolls = [];
|
|
628
|
+
for (let i = 0; i < count; i++) {
|
|
629
|
+
const val = ((seed + i * dp) % sides) + 1;
|
|
630
|
+
rolls.push(val);
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const total = rolls.reduce((a, b) => a + b, 0) + modifier;
|
|
634
|
+
return { rolls, modifier, total };
|
|
635
|
+
};
|
|
636
|
+
|
|
637
|
+
/**
|
|
638
|
+
* Call a bingo number deterministically.
|
|
639
|
+
*
|
|
640
|
+
* @param {Object} [options={}] - Options
|
|
641
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
642
|
+
* @returns {string} e.g. "B-14"
|
|
643
|
+
*/
|
|
644
|
+
const bingo = (options = {}) => {
|
|
645
|
+
const seed = _normalizeSeed(options.seed);
|
|
646
|
+
const num = ((_lastN(seed, 2) - 1) % 75 + 75) % 75 + 1;
|
|
647
|
+
const letterIndex = Math.floor((num - 1) / 15);
|
|
648
|
+
return `${BINGO_LETTERS[letterIndex]}-${num}`;
|
|
649
|
+
};
|
|
650
|
+
|
|
651
|
+
/**
|
|
652
|
+
* Generate a deterministic hex color.
|
|
653
|
+
*
|
|
654
|
+
* @param {Object} [options={}] - Options
|
|
655
|
+
* @param {number|string} [options.seed] - Custom seed (default: 814)
|
|
656
|
+
* @returns {string} e.g. "#a81414"
|
|
657
|
+
*/
|
|
658
|
+
const color = (options = {}) => {
|
|
659
|
+
const seed = _normalizeSeed(options.seed);
|
|
660
|
+
const prefix = (_firstDigit(seed) + 2).toString(16);
|
|
661
|
+
const fill = String(_fillDigits(seed, 5));
|
|
662
|
+
return '#' + prefix + fill;
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
// ─── Attach Methods ──────────────────────────────────────────────────────────
|
|
666
|
+
|
|
667
|
+
pdrng.float = float;
|
|
668
|
+
pdrng.range = range;
|
|
669
|
+
pdrng.array = array;
|
|
670
|
+
pdrng.uuid = uuid;
|
|
671
|
+
pdrng.oddOrEven = oddOrEven;
|
|
672
|
+
pdrng.redOrBlack = redOrBlack;
|
|
673
|
+
pdrng.coin = coin;
|
|
674
|
+
pdrng.dice = dice;
|
|
675
|
+
pdrng.card = card;
|
|
676
|
+
pdrng.roulette = roulette;
|
|
677
|
+
pdrng.rps = rps;
|
|
678
|
+
pdrng.magic8 = magic8;
|
|
679
|
+
pdrng.zodiac = zodiac;
|
|
680
|
+
pdrng.tarot = tarot;
|
|
681
|
+
pdrng.fortune = fortune;
|
|
682
|
+
pdrng.spin = spin;
|
|
683
|
+
pdrng.roll = roll;
|
|
684
|
+
pdrng.bingo = bingo;
|
|
685
|
+
pdrng.color = color;
|
|
686
|
+
pdrng.randomSeed = randomSeed;
|
|
687
|
+
pdrng.DEFAULT_SEED = DEFAULT_SEED;
|
|
688
|
+
|
|
689
|
+
// ─── Exports ─────────────────────────────────────────────────────────────────
|
|
690
|
+
|
|
691
|
+
export default pdrng;
|
|
692
|
+
|
|
693
|
+
export {
|
|
694
|
+
pdrng,
|
|
695
|
+
float,
|
|
696
|
+
range,
|
|
697
|
+
array,
|
|
698
|
+
uuid,
|
|
699
|
+
oddOrEven,
|
|
700
|
+
redOrBlack,
|
|
701
|
+
coin,
|
|
702
|
+
dice,
|
|
703
|
+
card,
|
|
704
|
+
roulette,
|
|
705
|
+
rps,
|
|
706
|
+
magic8,
|
|
707
|
+
zodiac,
|
|
708
|
+
tarot,
|
|
709
|
+
fortune,
|
|
710
|
+
spin,
|
|
711
|
+
roll,
|
|
712
|
+
bingo,
|
|
713
|
+
color,
|
|
714
|
+
randomSeed,
|
|
715
|
+
DEFAULT_SEED
|
|
716
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "pdrng",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Pseudo Deterministic Random Number Generator - seed-based deterministic number generation",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "index.js",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./index.js"
|
|
10
|
+
}
|
|
11
|
+
},
|
|
12
|
+
"scripts": {
|
|
13
|
+
"test": "vitest run",
|
|
14
|
+
"test:watch": "vitest",
|
|
15
|
+
"test:coverage": "vitest run --coverage",
|
|
16
|
+
"lint": "eslint . --format stylish",
|
|
17
|
+
"lint:report": "eslint . --format html -o coverage/lint-report.html"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/brianfunk/pdrng.git"
|
|
22
|
+
},
|
|
23
|
+
"keywords": [
|
|
24
|
+
"random",
|
|
25
|
+
"rng",
|
|
26
|
+
"prng",
|
|
27
|
+
"deterministic",
|
|
28
|
+
"seed",
|
|
29
|
+
"dice",
|
|
30
|
+
"coin",
|
|
31
|
+
"uuid",
|
|
32
|
+
"pseudo",
|
|
33
|
+
"number",
|
|
34
|
+
"generator"
|
|
35
|
+
],
|
|
36
|
+
"author": "Brian Funk",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/brianfunk/pdrng/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/brianfunk/pdrng#readme",
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=18.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@vitest/coverage-v8": "^4.0.18",
|
|
47
|
+
"eslint": "^9.17.0",
|
|
48
|
+
"vitest": "^4.0.18"
|
|
49
|
+
}
|
|
50
|
+
}
|