games_dice 0.4.2 → 0.6.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.
- checksums.yaml +4 -4
- data/.github/workflows/ci.yml +69 -5
- data/.gitignore +0 -1
- data/.rubocop.yml +13 -48
- data/CHANGELOG.md +26 -0
- data/Gemfile +6 -3
- data/README.md +46 -5
- data/Rakefile +90 -2
- data/ext/games_dice/extconf.rb +26 -0
- data/ext/games_dice/games_dice.c +1 -1
- data/ext/games_dice/probabilities.c +341 -211
- data/ext/games_dice/probabilities.h +3 -29
- data/games_dice.gemspec +1 -1
- data/lib/games_dice/bunch.rb +50 -36
- data/lib/games_dice/bunch_helpers.rb +35 -14
- data/lib/games_dice/complex_die.rb +64 -22
- data/lib/games_dice/complex_die_helpers.rb +122 -144
- data/lib/games_dice/dice.rb +61 -17
- data/lib/games_dice/dice_helpers.rb +37 -0
- data/lib/games_dice/die_result.rb +6 -8
- data/lib/games_dice/marshal.rb +2 -7
- data/lib/games_dice/parser.rb +30 -0
- data/lib/games_dice/probability_budget.rb +90 -0
- data/lib/games_dice/probability_calculation.rb +95 -0
- data/lib/games_dice/probability_shortcuts.rb +65 -0
- data/lib/games_dice/recipe_cache.rb +155 -0
- data/lib/games_dice/version.rb +1 -1
- data/lib/games_dice.rb +4 -0
- data/script/check_documentation.rb +10 -0
- data/spec/games_dice/bunch_regressions_spec.rb +46 -0
- data/spec/{bunch_spec.rb → games_dice/bunch_spec.rb} +65 -112
- data/spec/games_dice/complex_die_budget_spec.rb +60 -0
- data/spec/games_dice/complex_die_limits_spec.rb +119 -0
- data/spec/games_dice/complex_die_spec.rb +228 -0
- data/spec/games_dice/dice_mutation_spec.rb +165 -0
- data/spec/games_dice/dice_regressions_spec.rb +50 -0
- data/spec/games_dice/dice_spec.rb +61 -0
- data/spec/games_dice/die_result_spec.rb +221 -0
- data/spec/{die_spec.rb → games_dice/die_spec.rb} +15 -14
- data/spec/{map_rule_spec.rb → games_dice/map_rule_spec.rb} +8 -1
- data/spec/{parser_spec.rb → games_dice/parser_spec.rb} +12 -2
- data/spec/games_dice/probabilities_boundaries_spec.rb +161 -0
- data/spec/games_dice/probabilities_marshal_spec.rb +62 -0
- data/spec/games_dice/probabilities_spec.rb +484 -0
- data/spec/games_dice/probability_budget_spec.rb +17 -0
- data/spec/{reroll_rule_spec.rb → games_dice/reroll_rule_spec.rb} +5 -1
- data/spec/games_dice_spec.rb +382 -0
- data/spec/helpers.rb +64 -2
- data/spec/simple_cov_spec.rb +42 -0
- data/spec/support/coverage.rb +12 -0
- data/spec/support/validation.rb +14 -0
- data/spec/yard/cli/stats_spec.rb +43 -0
- metadata +30 -13
- data/spec/complex_die_spec.rb +0 -314
- data/spec/dice_spec.rb +0 -33
- data/spec/die_result_spec.rb +0 -254
- data/spec/fixtures/probs_fair_die_6.dat +0 -2
- data/spec/probability_spec.rb +0 -522
- data/spec/readme_spec.rb +0 -410
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
// ext/games_dice/probabilities.c
|
|
2
2
|
|
|
3
3
|
#include "probabilities.h"
|
|
4
|
+
#include <limits.h>
|
|
5
|
+
#include <stdint.h>
|
|
6
|
+
#include <math.h>
|
|
4
7
|
|
|
5
8
|
// Ruby 1.8.7 compatibility patch
|
|
6
9
|
#ifndef DBL2NUM
|
|
@@ -21,22 +24,68 @@ VALUE Probabilities = Qnil;
|
|
|
21
24
|
// General utils
|
|
22
25
|
//
|
|
23
26
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
27
|
+
/*
|
|
28
|
+
* Owns the temporary distributions created during one native calculation.
|
|
29
|
+
*
|
|
30
|
+
* A local C pointer does not automatically free its allocation when the
|
|
31
|
+
* function returns. Ruby exceptions can also leave a calculation before
|
|
32
|
+
* ordinary cleanup code runs, including when a later allocation fails.
|
|
33
|
+
*
|
|
34
|
+
* Run the calculation through rb_ensure with cleanup_operation as its
|
|
35
|
+
* cleanup callback. create_probability_list adds each new distribution
|
|
36
|
+
* to owned before allocating its buffers. Its fields start at zero, so
|
|
37
|
+
* cleanup can safely free a partially constructed distribution.
|
|
38
|
+
*
|
|
39
|
+
* owned is the head of a linked list. Each ProbabilityList stores next
|
|
40
|
+
* and owner_link, which points to the pointer that currently refers to
|
|
41
|
+
* that entry. This lets destroy_probability_list unlink an entry when
|
|
42
|
+
* it is freed early, preventing cleanup from freeing it again.
|
|
43
|
+
*
|
|
44
|
+
* When returning a distribution to Ruby, perform_operation wraps it in
|
|
45
|
+
* a Ruby object before detaching it from owned. Ruby then handles its
|
|
46
|
+
* lifetime. If wrapping fails, the operation still owns the distribution
|
|
47
|
+
* and cleanup frees it.
|
|
48
|
+
*
|
|
49
|
+
* Iteration uses the same owner for its snapshot, keeping that storage
|
|
50
|
+
* alive even if a Ruby callback replaces the original distribution.
|
|
51
|
+
* Cleanup frees the snapshot after iteration, including exceptions and
|
|
52
|
+
* early block exits.
|
|
53
|
+
*/
|
|
54
|
+
typedef struct {
|
|
55
|
+
ProbabilityList *owned; /* Temporary distributions requiring cleanup. */
|
|
56
|
+
ProbabilityList *a, *b; /* Borrowed inputs, not owned by these pointers. */
|
|
57
|
+
int x, y, z; /* Arguments such as counts, weights and thresholds. */
|
|
58
|
+
int operation; /* Selects the calculation in perform_operation. */
|
|
59
|
+
} ProbabilityOperation;
|
|
60
|
+
|
|
61
|
+
static int checked_result( int64_t value ) {
|
|
62
|
+
if ( value < INT_MIN || value > INT_MAX ) {
|
|
63
|
+
rb_raise( rb_eRangeError, "Probability result is outside native integer range" );
|
|
29
64
|
}
|
|
30
|
-
return
|
|
65
|
+
return (int)value;
|
|
31
66
|
}
|
|
32
67
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
for ( i=0; i < n; i++ ) {
|
|
37
|
-
m = a[i] < m ? a[i] : m;
|
|
68
|
+
static int checked_slots( int64_t slots ) {
|
|
69
|
+
if ( slots < 1 || slots > 1000000 ) {
|
|
70
|
+
rb_raise( rb_eArgError, "Bad number of probability slots" );
|
|
38
71
|
}
|
|
39
|
-
return
|
|
72
|
+
return (int)slots;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
static int weighted_result( int a, int x, int b, int y ) {
|
|
76
|
+
int64_t ax = (int64_t)a * x, by = (int64_t)b * y;
|
|
77
|
+
if ( (by > 0 && ax > INT64_MAX - by) || (by < 0 && ax < INT64_MIN - by) ) {
|
|
78
|
+
rb_raise( rb_eRangeError, "Probability result is outside native integer range" );
|
|
79
|
+
}
|
|
80
|
+
return checked_result( ax + by );
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
static void detach_probability_list( ProbabilityList *pl ) {
|
|
84
|
+
if ( !pl->owner_link ) return;
|
|
85
|
+
*pl->owner_link = pl->next;
|
|
86
|
+
if ( pl->next ) pl->next->owner_link = pl->owner_link;
|
|
87
|
+
pl->next = NULL;
|
|
88
|
+
pl->owner_link = NULL;
|
|
40
89
|
}
|
|
41
90
|
|
|
42
91
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
@@ -110,20 +159,19 @@ double num_arrangements( int *args, int nargs ) {
|
|
|
110
159
|
// Probability List basics - create, delete, copy
|
|
111
160
|
//
|
|
112
161
|
|
|
113
|
-
ProbabilityList *create_probability_list() {
|
|
114
|
-
ProbabilityList *pl;
|
|
115
|
-
pl
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
pl->cumulative = NULL;
|
|
121
|
-
pl->slots = 0;
|
|
122
|
-
pl->offset = 0;
|
|
162
|
+
ProbabilityList *create_probability_list( ProbabilityOperation *op ) {
|
|
163
|
+
ProbabilityList *pl = ALLOC( ProbabilityList );
|
|
164
|
+
memset( pl, 0, sizeof(ProbabilityList) );
|
|
165
|
+
pl->next = op->owned;
|
|
166
|
+
pl->owner_link = &op->owned;
|
|
167
|
+
if ( op->owned ) op->owned->owner_link = &pl->next;
|
|
168
|
+
op->owned = pl;
|
|
123
169
|
return pl;
|
|
124
170
|
}
|
|
125
171
|
|
|
126
172
|
void destroy_probability_list( ProbabilityList *pl ) {
|
|
173
|
+
if ( !pl ) return;
|
|
174
|
+
detach_probability_list( pl );
|
|
127
175
|
xfree( pl->cumulative );
|
|
128
176
|
xfree( pl->probs );
|
|
129
177
|
xfree( pl );
|
|
@@ -173,8 +221,8 @@ double *alloc_probs_iv( ProbabilityList *pl, int slots, double iv ) {
|
|
|
173
221
|
return pr;
|
|
174
222
|
}
|
|
175
223
|
|
|
176
|
-
ProbabilityList *copy_probability_list( ProbabilityList *orig ) {
|
|
177
|
-
ProbabilityList *pl = create_probability_list();
|
|
224
|
+
ProbabilityList *copy_probability_list( ProbabilityOperation *op, ProbabilityList *orig ) {
|
|
225
|
+
ProbabilityList *pl = create_probability_list( op );
|
|
178
226
|
double *pr;
|
|
179
227
|
pr = alloc_probs( pl, orig->slots );
|
|
180
228
|
pl->offset = orig->offset;
|
|
@@ -183,8 +231,8 @@ ProbabilityList *copy_probability_list( ProbabilityList *orig ) {
|
|
|
183
231
|
return pl;
|
|
184
232
|
}
|
|
185
233
|
|
|
186
|
-
|
|
187
|
-
ProbabilityList *pl = create_probability_list();
|
|
234
|
+
static ProbabilityList *new_basic_pl( ProbabilityOperation *op, int nslots, double iv, int o ) {
|
|
235
|
+
ProbabilityList *pl = create_probability_list( op );
|
|
188
236
|
alloc_probs_iv( pl, nslots, iv );
|
|
189
237
|
pl->offset = o;
|
|
190
238
|
return pl;
|
|
@@ -195,21 +243,24 @@ inline ProbabilityList *new_basic_pl( int nslots, double iv, int o ) {
|
|
|
195
243
|
// Probability List core "native" methods
|
|
196
244
|
//
|
|
197
245
|
|
|
198
|
-
|
|
246
|
+
static double pl_p_le( ProbabilityList *pl, int target );
|
|
247
|
+
|
|
248
|
+
static int pl_min( ProbabilityList *pl ) {
|
|
199
249
|
return pl->offset;
|
|
200
250
|
}
|
|
201
251
|
|
|
202
|
-
|
|
203
|
-
return pl->offset + pl->slots - 1;
|
|
252
|
+
static int pl_max( ProbabilityList *pl ) {
|
|
253
|
+
return checked_result( (int64_t)pl->offset + pl->slots - 1 );
|
|
204
254
|
}
|
|
205
255
|
|
|
206
|
-
ProbabilityList *pl_add_distributions( ProbabilityList *pl_a, ProbabilityList *pl_b ) {
|
|
256
|
+
ProbabilityList *pl_add_distributions( ProbabilityOperation *op, ProbabilityList *pl_a, ProbabilityList *pl_b ) {
|
|
207
257
|
double *pr;
|
|
208
|
-
int s = pl_a->slots + pl_b->slots - 1;
|
|
209
|
-
int o = pl_a->offset + pl_b->offset;
|
|
258
|
+
int s = checked_slots( (int64_t)pl_a->slots + pl_b->slots - 1 );
|
|
259
|
+
int o = checked_result( (int64_t)pl_a->offset + pl_b->offset );
|
|
260
|
+
checked_result( (int64_t)o + s - 1 );
|
|
210
261
|
int i,j;
|
|
211
262
|
|
|
212
|
-
ProbabilityList *pl = create_probability_list();
|
|
263
|
+
ProbabilityList *pl = create_probability_list( op );
|
|
213
264
|
pl->offset = o;
|
|
214
265
|
pr = alloc_probs_iv( pl, s, 0.0 );
|
|
215
266
|
for ( i=0; i < pl_a->slots; i++ ) { for ( j=0; j < pl_b->slots; j++ ) {
|
|
@@ -219,48 +270,47 @@ ProbabilityList *pl_add_distributions( ProbabilityList *pl_a, ProbabilityList *p
|
|
|
219
270
|
return pl;
|
|
220
271
|
}
|
|
221
272
|
|
|
222
|
-
ProbabilityList *pl_add_distributions_mult( int mul_a, ProbabilityList *pl_a, int mul_b, ProbabilityList *pl_b ) {
|
|
273
|
+
ProbabilityList *pl_add_distributions_mult( ProbabilityOperation *op, int mul_a, ProbabilityList *pl_a, int mul_b, ProbabilityList *pl_b ) {
|
|
223
274
|
int pts[4] = {
|
|
224
|
-
mul_a
|
|
225
|
-
mul_a
|
|
226
|
-
mul_a
|
|
227
|
-
mul_a
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
int
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
} }
|
|
275
|
+
weighted_result( mul_a, pl_min(pl_a), mul_b, pl_min(pl_b) ),
|
|
276
|
+
weighted_result( mul_a, pl_max(pl_a), mul_b, pl_min(pl_b) ),
|
|
277
|
+
weighted_result( mul_a, pl_min(pl_a), mul_b, pl_max(pl_b) ),
|
|
278
|
+
weighted_result( mul_a, pl_max(pl_a), mul_b, pl_max(pl_b) ) };
|
|
279
|
+
int low = pts[0], high = pts[0];
|
|
280
|
+
for ( int i = 1; i < 4; i++ ) {
|
|
281
|
+
if ( pts[i] < low ) low = pts[i];
|
|
282
|
+
if ( pts[i] > high ) high = pts[i];
|
|
283
|
+
}
|
|
284
|
+
int slots = checked_slots( 1 + (int64_t)high - low );
|
|
285
|
+
ProbabilityList *pl = new_basic_pl( op, slots, 0.0, low );
|
|
286
|
+
for ( int i = 0; i < pl_a->slots; i++ ) {
|
|
287
|
+
for ( int j = 0; j < pl_b->slots; j++ ) {
|
|
288
|
+
int64_t value = (int64_t)mul_a * (i + pl_a->offset) + (int64_t)mul_b * (j + pl_b->offset);
|
|
289
|
+
pl->probs[value - low] += pl_a->probs[i] * pl_b->probs[j];
|
|
290
|
+
}
|
|
291
|
+
}
|
|
242
292
|
calc_cumulative( pl );
|
|
243
293
|
return pl;
|
|
244
294
|
}
|
|
245
295
|
|
|
246
|
-
|
|
247
|
-
|
|
296
|
+
static double pl_p_eql( ProbabilityList *pl, int target ) {
|
|
297
|
+
int64_t idx = (int64_t)target - pl->offset;
|
|
248
298
|
if ( idx < 0 || idx >= pl->slots ) {
|
|
249
299
|
return 0.0;
|
|
250
300
|
}
|
|
251
301
|
return (pl->probs)[idx];
|
|
252
302
|
}
|
|
253
303
|
|
|
254
|
-
|
|
304
|
+
static double pl_p_gt( ProbabilityList *pl, int target ) {
|
|
255
305
|
return 1.0 - pl_p_le( pl, target );
|
|
256
306
|
}
|
|
257
307
|
|
|
258
|
-
|
|
259
|
-
return pl_p_le( pl, target - 1 );
|
|
308
|
+
static double pl_p_lt( ProbabilityList *pl, int target ) {
|
|
309
|
+
return target == INT_MIN ? 0.0 : pl_p_le( pl, target - 1 );
|
|
260
310
|
}
|
|
261
311
|
|
|
262
|
-
|
|
263
|
-
|
|
312
|
+
static double pl_p_le( ProbabilityList *pl, int target ) {
|
|
313
|
+
int64_t idx = (int64_t)target - pl->offset;
|
|
264
314
|
if ( idx < 0 ) {
|
|
265
315
|
return 0.0;
|
|
266
316
|
}
|
|
@@ -270,11 +320,11 @@ inline double pl_p_le( ProbabilityList *pl, int target ) {
|
|
|
270
320
|
return (pl->cumulative)[idx];
|
|
271
321
|
}
|
|
272
322
|
|
|
273
|
-
|
|
274
|
-
return 1.0 -
|
|
323
|
+
static double pl_p_ge( ProbabilityList *pl, int target ) {
|
|
324
|
+
return 1.0 - pl_p_lt( pl, target );
|
|
275
325
|
}
|
|
276
326
|
|
|
277
|
-
|
|
327
|
+
static double pl_expected( ProbabilityList *pl ) {
|
|
278
328
|
double t = 0.0;
|
|
279
329
|
int o = pl->offset;
|
|
280
330
|
int s = pl->slots;
|
|
@@ -286,7 +336,7 @@ inline double pl_expected( ProbabilityList *pl ) {
|
|
|
286
336
|
return t;
|
|
287
337
|
}
|
|
288
338
|
|
|
289
|
-
ProbabilityList *pl_given_ge( ProbabilityList *pl, int target ) {
|
|
339
|
+
ProbabilityList *pl_given_ge( ProbabilityOperation *op, ProbabilityList *pl, int target ) {
|
|
290
340
|
int m = pl_min( pl );
|
|
291
341
|
double p, mult;
|
|
292
342
|
double *pr;
|
|
@@ -302,10 +352,10 @@ ProbabilityList *pl_given_ge( ProbabilityList *pl, int target ) {
|
|
|
302
352
|
rb_raise( rb_eRuntimeError, "Cannot calculate given probabilities, divide by zero" );
|
|
303
353
|
}
|
|
304
354
|
mult = 1.0/p;
|
|
305
|
-
s = pl->slots + pl->offset - target;
|
|
355
|
+
s = checked_slots( (int64_t)pl->slots + pl->offset - target );
|
|
306
356
|
pr = pl->probs;
|
|
307
357
|
|
|
308
|
-
new_pl = create_probability_list();
|
|
358
|
+
new_pl = create_probability_list( op );
|
|
309
359
|
new_pl->offset = target;
|
|
310
360
|
new_pr = alloc_probs( new_pl, s );
|
|
311
361
|
o = target - pl->offset;
|
|
@@ -317,7 +367,7 @@ ProbabilityList *pl_given_ge( ProbabilityList *pl, int target ) {
|
|
|
317
367
|
return new_pl;
|
|
318
368
|
}
|
|
319
369
|
|
|
320
|
-
ProbabilityList *pl_given_le( ProbabilityList *pl, int target ) {
|
|
370
|
+
ProbabilityList *pl_given_le( ProbabilityOperation *op, ProbabilityList *pl, int target ) {
|
|
321
371
|
int m = pl_max( pl );
|
|
322
372
|
double p, mult;
|
|
323
373
|
double *pr;
|
|
@@ -336,7 +386,7 @@ ProbabilityList *pl_given_le( ProbabilityList *pl, int target ) {
|
|
|
336
386
|
s = target - pl->offset + 1;
|
|
337
387
|
pr = pl->probs;
|
|
338
388
|
|
|
339
|
-
new_pl = create_probability_list();
|
|
389
|
+
new_pl = create_probability_list( op );
|
|
340
390
|
new_pl->offset = pl->offset;
|
|
341
391
|
new_pr = alloc_probs( new_pl, s );
|
|
342
392
|
|
|
@@ -347,34 +397,36 @@ ProbabilityList *pl_given_le( ProbabilityList *pl, int target ) {
|
|
|
347
397
|
return new_pl;
|
|
348
398
|
}
|
|
349
399
|
|
|
350
|
-
ProbabilityList *pl_repeat_sum( ProbabilityList *pl, int n ) {
|
|
400
|
+
ProbabilityList *pl_repeat_sum( ProbabilityOperation *op, ProbabilityList *pl, int n ) {
|
|
351
401
|
ProbabilityList *pd_power = NULL;
|
|
352
402
|
ProbabilityList *pd_result = NULL;
|
|
353
403
|
ProbabilityList *pd_next = NULL;
|
|
354
|
-
int
|
|
404
|
+
unsigned int remaining = (unsigned int)n;
|
|
355
405
|
|
|
356
406
|
if ( n < 1 ) {
|
|
357
407
|
rb_raise( rb_eRuntimeError, "Cannot calculate repeat_sum when n < 1" );
|
|
358
408
|
}
|
|
359
|
-
if ( n * pl->slots -
|
|
409
|
+
if ( (int64_t)n * (pl->slots - 1) >= 1000000 ) {
|
|
360
410
|
rb_raise( rb_eRuntimeError, "Too many probability slots" );
|
|
361
411
|
}
|
|
362
412
|
|
|
363
|
-
|
|
413
|
+
checked_result( (int64_t)n * pl_min(pl) );
|
|
414
|
+
checked_result( (int64_t)n * pl_max(pl) );
|
|
415
|
+
pd_power = copy_probability_list( op, pl );
|
|
364
416
|
|
|
365
417
|
while ( 1 ) {
|
|
366
|
-
if (
|
|
418
|
+
if ( remaining & 1 ) {
|
|
367
419
|
if ( pd_result ) {
|
|
368
|
-
pd_next = pl_add_distributions( pd_result, pd_power );
|
|
420
|
+
pd_next = pl_add_distributions( op, pd_result, pd_power );
|
|
369
421
|
destroy_probability_list( pd_result );
|
|
370
422
|
pd_result = pd_next;
|
|
371
423
|
} else {
|
|
372
|
-
pd_result = copy_probability_list( pd_power );
|
|
424
|
+
pd_result = copy_probability_list( op, pd_power );
|
|
373
425
|
}
|
|
374
426
|
}
|
|
375
|
-
|
|
376
|
-
if (
|
|
377
|
-
pd_next = pl_add_distributions( pd_power, pd_power );
|
|
427
|
+
remaining >>= 1;
|
|
428
|
+
if ( remaining == 0 ) break;
|
|
429
|
+
pd_next = pl_add_distributions( op, pd_power, pd_power );
|
|
378
430
|
destroy_probability_list( pd_power );
|
|
379
431
|
pd_power = pd_next;
|
|
380
432
|
}
|
|
@@ -398,35 +450,37 @@ void calc_p_table( ProbabilityList *pl, int q, int kbest, double *buffer ) {
|
|
|
398
450
|
}
|
|
399
451
|
|
|
400
452
|
// Assigns a list of pl variants to a buffer
|
|
401
|
-
void calc_keep_distributions( ProbabilityList *pl, int k, int q, int kbest, ProbabilityList **pl_array ) {
|
|
453
|
+
void calc_keep_distributions( ProbabilityOperation *op, ProbabilityList *pl, int k, int q, int kbest, ProbabilityList **pl_array ) {
|
|
402
454
|
ProbabilityList *pl_kd;
|
|
403
455
|
|
|
404
456
|
int n;
|
|
405
457
|
for ( n=0; n<k; n++) { pl_array[n] = NULL; }
|
|
406
|
-
pl_array[0] = new_basic_pl( 1, 1.0, q * k );
|
|
458
|
+
pl_array[0] = new_basic_pl( op, 1, 1.0, checked_result( (int64_t)q * k ) );
|
|
407
459
|
|
|
408
460
|
if ( kbest ) {
|
|
409
461
|
if ( pl_p_gt( pl, q ) > 0.0 && k > 1 ) {
|
|
410
|
-
pl_kd = pl_given_ge( pl, q + 1 );
|
|
462
|
+
pl_kd = pl_given_ge( op, pl, q + 1 );
|
|
411
463
|
for ( n = 1; n < k; n++ ) {
|
|
412
|
-
pl_array[n] = pl_repeat_sum( pl_kd, n );
|
|
413
|
-
(pl_array[n])->offset
|
|
464
|
+
pl_array[n] = pl_repeat_sum( op, pl_kd, n );
|
|
465
|
+
(pl_array[n])->offset = checked_result( (int64_t)(pl_array[n])->offset + (int64_t)q * (k - n) );
|
|
414
466
|
}
|
|
467
|
+
destroy_probability_list( pl_kd );
|
|
415
468
|
}
|
|
416
469
|
} else {
|
|
417
470
|
if ( pl_p_lt( pl, q ) > 0.0 && k > 1 ) {
|
|
418
|
-
pl_kd = pl_given_le( pl, q - 1 );
|
|
471
|
+
pl_kd = pl_given_le( op, pl, q - 1 );
|
|
419
472
|
for ( n = 1; n < k; n++ ) {
|
|
420
|
-
pl_array[n] = pl_repeat_sum( pl_kd, n );
|
|
421
|
-
(pl_array[n])->offset
|
|
473
|
+
pl_array[n] = pl_repeat_sum( op, pl_kd, n );
|
|
474
|
+
(pl_array[n])->offset = checked_result( (int64_t)(pl_array[n])->offset + (int64_t)q * (k - n) );
|
|
422
475
|
}
|
|
476
|
+
destroy_probability_list( pl_kd );
|
|
423
477
|
}
|
|
424
478
|
}
|
|
425
479
|
|
|
426
480
|
return;
|
|
427
481
|
}
|
|
428
482
|
|
|
429
|
-
|
|
483
|
+
static void clear_pl_array( int k, ProbabilityList **pl_array ) {
|
|
430
484
|
int n;
|
|
431
485
|
for ( n=0; n<k; n++) {
|
|
432
486
|
if ( pl_array[n] != NULL ) {
|
|
@@ -436,17 +490,17 @@ inline void clear_pl_array( int k, ProbabilityList **pl_array ) {
|
|
|
436
490
|
return;
|
|
437
491
|
}
|
|
438
492
|
|
|
439
|
-
ProbabilityList *pl_repeat_n_sum_k( ProbabilityList *pl, int n, int k, int kbest ) {
|
|
493
|
+
ProbabilityList *pl_repeat_n_sum_k( ProbabilityOperation *op, ProbabilityList *pl, int n, int k, int kbest ) {
|
|
440
494
|
// Table of probabilities ( reject, maybe, keep ) for each "pivot point"
|
|
441
495
|
double p_table[3];
|
|
442
496
|
int keep_combos[3];
|
|
443
497
|
// Table of distributions for each count of > pivot point (vs == pivot point)
|
|
444
|
-
ProbabilityList *keep_distributions[171];
|
|
498
|
+
ProbabilityList *keep_distributions[171] = { NULL };
|
|
445
499
|
ProbabilityList *kd;
|
|
446
500
|
ProbabilityList *pl_result = NULL;
|
|
447
501
|
|
|
448
502
|
double *pr;
|
|
449
|
-
int d
|
|
503
|
+
int d;
|
|
450
504
|
int i, j, q, dn, kn, mn, kdq;
|
|
451
505
|
double p_sequence;
|
|
452
506
|
|
|
@@ -457,17 +511,21 @@ ProbabilityList *pl_repeat_n_sum_k( ProbabilityList *pl, int n, int k, int kbest
|
|
|
457
511
|
rb_raise( rb_eRuntimeError, "Cannot calculate repeat_sum_k when k < 1" );
|
|
458
512
|
}
|
|
459
513
|
if ( k >= n ) {
|
|
460
|
-
return pl_repeat_sum( pl, n );
|
|
514
|
+
return pl_repeat_sum( op, pl, n );
|
|
461
515
|
}
|
|
462
|
-
if ( k * pl->slots -
|
|
516
|
+
if ( (int64_t)k * (pl->slots - 1) >= 1000000 ) {
|
|
463
517
|
rb_raise( rb_eRuntimeError, "Too many probability slots" );
|
|
464
518
|
}
|
|
465
519
|
if ( n > 170 ) {
|
|
466
520
|
rb_raise( rb_eRuntimeError, "Too many dice to calculate combinations" );
|
|
467
521
|
}
|
|
468
522
|
|
|
523
|
+
d = n - k;
|
|
524
|
+
checked_result( (int64_t)k * pl_min(pl) );
|
|
525
|
+
checked_result( (int64_t)k * pl_max(pl) );
|
|
526
|
+
|
|
469
527
|
// Init target
|
|
470
|
-
pl_result = create_probability_list();
|
|
528
|
+
pl_result = create_probability_list( op );
|
|
471
529
|
pr = alloc_probs_iv( pl_result, 1 + k * (pl->slots - 1), 0.0 );
|
|
472
530
|
pl_result->offset = pl->offset * k;
|
|
473
531
|
|
|
@@ -475,7 +533,7 @@ ProbabilityList *pl_repeat_n_sum_k( ProbabilityList *pl, int n, int k, int kbest
|
|
|
475
533
|
if ( pl->probs[i] <= 0.0 ) continue;
|
|
476
534
|
|
|
477
535
|
q = i + pl->offset;
|
|
478
|
-
calc_keep_distributions( pl, k, q, kbest, keep_distributions );
|
|
536
|
+
calc_keep_distributions( op, pl, k, q, kbest, keep_distributions );
|
|
479
537
|
calc_p_table( pl, q, kbest, p_table );
|
|
480
538
|
|
|
481
539
|
for ( kn = 0; kn < k; kn++ ) {
|
|
@@ -515,57 +573,129 @@ ProbabilityList *pl_repeat_n_sum_k( ProbabilityList *pl, int n, int k, int kbest
|
|
|
515
573
|
// Ruby integration
|
|
516
574
|
//
|
|
517
575
|
|
|
518
|
-
|
|
519
|
-
|
|
576
|
+
static void free_probability_list( void *ptr ) {
|
|
577
|
+
destroy_probability_list( ptr );
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
static size_t probability_list_memsize( const void *ptr ) {
|
|
581
|
+
const ProbabilityList *pl = ptr;
|
|
582
|
+
size_t size = sizeof(ProbabilityList);
|
|
583
|
+
|
|
584
|
+
if ( pl == NULL ) return 0;
|
|
585
|
+
if ( pl->probs != NULL ) size += pl->slots * sizeof(double);
|
|
586
|
+
if ( pl->cumulative != NULL ) size += pl->slots * sizeof(double);
|
|
587
|
+
return size;
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
static const rb_data_type_t probability_list_type = {
|
|
591
|
+
.wrap_struct_name = "GamesDice::Probabilities",
|
|
592
|
+
.function = {
|
|
593
|
+
.dmark = NULL,
|
|
594
|
+
.dfree = free_probability_list,
|
|
595
|
+
.dsize = probability_list_memsize,
|
|
596
|
+
.dcompact = NULL,
|
|
597
|
+
.reserved = { NULL }
|
|
598
|
+
},
|
|
599
|
+
.parent = NULL,
|
|
600
|
+
.data = NULL,
|
|
601
|
+
.flags = 0
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
static inline VALUE pl_as_ruby_class( ProbabilityList *pl, VALUE klass ) {
|
|
605
|
+
return TypedData_Wrap_Struct( klass, &probability_list_type, pl );
|
|
520
606
|
}
|
|
521
607
|
|
|
522
608
|
VALUE pl_alloc(VALUE klass) {
|
|
523
|
-
|
|
609
|
+
ProbabilityList *pl;
|
|
610
|
+
VALUE obj = TypedData_Make_Struct( klass, ProbabilityList, &probability_list_type, pl );
|
|
611
|
+
return obj;
|
|
524
612
|
}
|
|
525
613
|
|
|
526
|
-
inline ProbabilityList *get_probability_list( VALUE obj ) {
|
|
614
|
+
static inline ProbabilityList *get_probability_list( VALUE obj ) {
|
|
527
615
|
ProbabilityList *pl;
|
|
528
|
-
|
|
616
|
+
TypedData_Get_Struct( obj, ProbabilityList, &probability_list_type, pl );
|
|
529
617
|
return pl;
|
|
530
618
|
}
|
|
531
619
|
|
|
532
620
|
void assert_value_wraps_pl( VALUE obj ) {
|
|
533
|
-
if (
|
|
534
|
-
RDATA(obj)->dfree != (RUBY_DATA_FUNC)destroy_probability_list) {
|
|
621
|
+
if ( ! rb_typeddata_is_kind_of( obj, &probability_list_type ) ) {
|
|
535
622
|
rb_raise( rb_eTypeError, "Expected a Probabilities object, but got something else" );
|
|
536
623
|
}
|
|
537
624
|
}
|
|
538
625
|
|
|
539
|
-
|
|
540
|
-
int validate_key_value( VALUE key, VALUE val, VALUE obj ) {
|
|
541
|
-
int k = NUM2INT( key );
|
|
542
|
-
ProbabilityList *pl = get_probability_list( obj );
|
|
543
|
-
// Not assigned (to avoid "unused" warning), but this throws execption if val cannot be coerced to double
|
|
544
|
-
NUM2DBL( val );
|
|
626
|
+
enum { OP_ADD, OP_WEIGHTED, OP_GE, OP_LE, OP_REPEAT, OP_KEEP, OP_COPY };
|
|
545
627
|
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
628
|
+
static VALUE cleanup_operation( VALUE data ) {
|
|
629
|
+
ProbabilityOperation *op = (ProbabilityOperation *)data;
|
|
630
|
+
while ( op->owned ) destroy_probability_list( op->owned );
|
|
631
|
+
return Qnil;
|
|
632
|
+
}
|
|
549
633
|
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
634
|
+
static VALUE perform_operation( VALUE data ) {
|
|
635
|
+
ProbabilityOperation *op = (ProbabilityOperation *)data;
|
|
636
|
+
ProbabilityList *result;
|
|
637
|
+
switch ( op->operation ) {
|
|
638
|
+
case OP_ADD: result = pl_add_distributions( op, op->a, op->b ); break;
|
|
639
|
+
case OP_WEIGHTED: result = pl_add_distributions_mult( op, op->x, op->a, op->y, op->b ); break;
|
|
640
|
+
case OP_GE: result = pl_given_ge( op, op->a, op->x ); break;
|
|
641
|
+
case OP_LE: result = pl_given_le( op, op->a, op->x ); break;
|
|
642
|
+
case OP_REPEAT: result = pl_repeat_sum( op, op->a, op->x ); break;
|
|
643
|
+
case OP_KEEP: result = pl_repeat_n_sum_k( op, op->a, op->x, op->y, op->z ); break;
|
|
644
|
+
default: result = copy_probability_list( op, op->a ); break;
|
|
559
645
|
}
|
|
646
|
+
VALUE obj = pl_as_ruby_class( result, Probabilities );
|
|
647
|
+
detach_probability_list( result );
|
|
648
|
+
return obj;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
static VALUE operate( int operation, ProbabilityList *a, ProbabilityList *b, int x, int y, int z ) {
|
|
652
|
+
ProbabilityOperation op = { .owned = NULL, .a = a, .b = b, .x = x, .y = y, .z = z, .operation = operation };
|
|
653
|
+
return rb_ensure( perform_operation, (VALUE)&op, cleanup_operation, (VALUE)&op );
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
/* A staging Ruby object owns buffers across coercion/allocation exceptions. */
|
|
657
|
+
static VALUE replace_distribution( VALUE receiver, VALUE staged ) {
|
|
658
|
+
ProbabilityList *old = get_probability_list( receiver );
|
|
659
|
+
RTYPEDDATA_DATA(receiver) = get_probability_list( staged );
|
|
660
|
+
RTYPEDDATA_DATA(staged) = NULL;
|
|
661
|
+
destroy_probability_list( old );
|
|
662
|
+
RB_GC_GUARD(staged);
|
|
663
|
+
return receiver;
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
static double probability_value( VALUE value ) {
|
|
667
|
+
double p = NUM2DBL(value);
|
|
668
|
+
if ( p < 0.0 ) rb_raise( rb_eArgError, "Negative probability not allowed" );
|
|
669
|
+
if ( p > 1.0 ) rb_raise( rb_eArgError, "Probability must be in range 0.0..1.0" );
|
|
670
|
+
if ( !isfinite(p) ) rb_raise( rb_eArgError, "Probability must be finite" );
|
|
671
|
+
return p;
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
static void validate_mass( ProbabilityList *pl ) {
|
|
675
|
+
double error = calc_cumulative( pl ) - 1.0;
|
|
676
|
+
if ( error < -1.0e-8 ) rb_raise( rb_eArgError, "Total probabilities are less than 1.0" );
|
|
677
|
+
if ( error > 1.0e-8 ) rb_raise( rb_eArgError, "Total probabilities are greater than 1.0" );
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
static int normalize_pair( VALUE key, VALUE value, VALUE normalized ) {
|
|
681
|
+
int k = NUM2INT(key);
|
|
682
|
+
double p = probability_value(value);
|
|
683
|
+
rb_hash_aset( normalized, INT2NUM(k), DBL2NUM(p) );
|
|
560
684
|
return ST_CONTINUE;
|
|
561
685
|
}
|
|
562
686
|
|
|
563
|
-
|
|
564
|
-
int
|
|
565
|
-
int k = NUM2INT(
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
687
|
+
static int hash_bounds( VALUE key, VALUE value, VALUE data ) {
|
|
688
|
+
int *bounds = (int *)data;
|
|
689
|
+
int k = NUM2INT(key);
|
|
690
|
+
(void)value;
|
|
691
|
+
if ( k < bounds[0] ) bounds[0] = k;
|
|
692
|
+
if ( k > bounds[1] ) bounds[1] = k;
|
|
693
|
+
return ST_CONTINUE;
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
static int copy_pair( VALUE key, VALUE value, VALUE data ) {
|
|
697
|
+
ProbabilityList *pl = (ProbabilityList *)data;
|
|
698
|
+
pl->probs[NUM2INT(key) - pl->offset] = NUM2DBL(value);
|
|
569
699
|
return ST_CONTINUE;
|
|
570
700
|
}
|
|
571
701
|
|
|
@@ -582,33 +712,24 @@ int copy_key_value( VALUE key, VALUE val, VALUE obj ) {
|
|
|
582
712
|
* @return [GamesDice::Probabilities]
|
|
583
713
|
*/
|
|
584
714
|
VALUE probabilities_initialize( VALUE self, VALUE arr, VALUE offset ) {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
ProbabilityList *pl;
|
|
588
|
-
double *pr;
|
|
589
|
-
|
|
590
|
-
o = NUM2INT(offset);
|
|
715
|
+
rb_check_frozen(self);
|
|
716
|
+
int o = NUM2INT(offset);
|
|
591
717
|
Check_Type( arr, T_ARRAY );
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
p_item = NUM2DBL( rb_ary_entry( arr, i ) );
|
|
598
|
-
if ( p_item < 0.0 ) {
|
|
599
|
-
rb_raise( rb_eArgError, "Negative probability not allowed" );
|
|
600
|
-
} else if ( p_item > 1.0 ) {
|
|
601
|
-
rb_raise( rb_eArgError, "Probability must be in range 0.0..1.0" );
|
|
602
|
-
}
|
|
603
|
-
pr[i] = p_item;
|
|
604
|
-
}
|
|
605
|
-
error = calc_cumulative( pl ) - 1.0;
|
|
606
|
-
if ( error < -1.0e-8 ) {
|
|
607
|
-
rb_raise( rb_eArgError, "Total probabilities are less than 1.0" );
|
|
608
|
-
} else if ( error > 1.0e-8 ) {
|
|
609
|
-
rb_raise( rb_eArgError, "Total probabilities are greater than 1.0" );
|
|
718
|
+
int slots = checked_slots( RARRAY_LEN(arr) );
|
|
719
|
+
checked_result( (int64_t)o + slots - 1 );
|
|
720
|
+
VALUE normalized = rb_ary_dup(arr);
|
|
721
|
+
for ( int i = 0; i < slots; i++ ) {
|
|
722
|
+
rb_ary_store( normalized, i, DBL2NUM(probability_value(rb_ary_entry(normalized, i))) );
|
|
610
723
|
}
|
|
611
|
-
|
|
724
|
+
VALUE staged = pl_alloc( Probabilities );
|
|
725
|
+
ProbabilityList *pl = get_probability_list( staged );
|
|
726
|
+
pl->offset = o;
|
|
727
|
+
alloc_probs( pl, slots );
|
|
728
|
+
for ( int i = 0; i < slots; i++ ) pl->probs[i] = NUM2DBL(rb_ary_entry(normalized, i));
|
|
729
|
+
validate_mass( pl );
|
|
730
|
+
RB_GC_GUARD(normalized);
|
|
731
|
+
rb_check_frozen(self);
|
|
732
|
+
return replace_distribution( self, staged );
|
|
612
733
|
}
|
|
613
734
|
|
|
614
735
|
/*
|
|
@@ -617,20 +738,11 @@ VALUE probabilities_initialize( VALUE self, VALUE arr, VALUE offset ) {
|
|
|
617
738
|
* @return [GamesDice::Probabilities]
|
|
618
739
|
*/
|
|
619
740
|
VALUE probabilities_initialize_copy( VALUE copy, VALUE orig ) {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
pl_copy = get_probability_list( copy );
|
|
626
|
-
pl_orig = get_probability_list( orig );
|
|
627
|
-
|
|
628
|
-
pr = alloc_probs( pl_copy, pl_orig->slots );
|
|
629
|
-
pl_copy->offset = pl_orig->offset;
|
|
630
|
-
memcpy( pr, pl_orig->probs, pl_orig->slots * sizeof(double) );
|
|
631
|
-
memcpy( pl_copy->cumulative, pl_orig->cumulative, pl_orig->slots * sizeof(double) );;
|
|
632
|
-
|
|
633
|
-
return copy;
|
|
741
|
+
if ( copy == orig ) return copy;
|
|
742
|
+
rb_check_frozen(copy);
|
|
743
|
+
VALUE staged = operate( OP_COPY, get_probability_list(orig), NULL, 0, 0, 0 );
|
|
744
|
+
RB_GC_GUARD(orig);
|
|
745
|
+
return replace_distribution( copy, staged );
|
|
634
746
|
}
|
|
635
747
|
|
|
636
748
|
/*
|
|
@@ -680,7 +792,8 @@ VALUE probabilities_max( VALUE self ) {
|
|
|
680
792
|
* @return [Float] in range (0.0..1.0)
|
|
681
793
|
*/
|
|
682
794
|
VALUE probabilites_p_eql( VALUE self, VALUE target ) {
|
|
683
|
-
|
|
795
|
+
int t = NUM2INT(target);
|
|
796
|
+
return DBL2NUM( pl_p_eql( get_probability_list( self ), t ) );
|
|
684
797
|
}
|
|
685
798
|
|
|
686
799
|
/*
|
|
@@ -689,7 +802,8 @@ VALUE probabilites_p_eql( VALUE self, VALUE target ) {
|
|
|
689
802
|
* @return [Float] in range (0.0..1.0)
|
|
690
803
|
*/
|
|
691
804
|
VALUE probabilites_p_gt( VALUE self, VALUE target ) {
|
|
692
|
-
|
|
805
|
+
int t = NUM2INT(target);
|
|
806
|
+
return DBL2NUM( pl_p_gt( get_probability_list( self ), t ) );
|
|
693
807
|
}
|
|
694
808
|
|
|
695
809
|
/*
|
|
@@ -698,7 +812,8 @@ VALUE probabilites_p_gt( VALUE self, VALUE target ) {
|
|
|
698
812
|
* @return [Float] in range (0.0..1.0)
|
|
699
813
|
*/
|
|
700
814
|
VALUE probabilites_p_ge( VALUE self, VALUE target ) {
|
|
701
|
-
|
|
815
|
+
int t = NUM2INT(target);
|
|
816
|
+
return DBL2NUM( pl_p_ge( get_probability_list( self ), t ) );
|
|
702
817
|
}
|
|
703
818
|
|
|
704
819
|
/*
|
|
@@ -707,7 +822,8 @@ VALUE probabilites_p_ge( VALUE self, VALUE target ) {
|
|
|
707
822
|
* @return [Float] in range (0.0..1.0)
|
|
708
823
|
*/
|
|
709
824
|
VALUE probabilites_p_le( VALUE self, VALUE target ) {
|
|
710
|
-
|
|
825
|
+
int t = NUM2INT(target);
|
|
826
|
+
return DBL2NUM( pl_p_le( get_probability_list( self ), t ) );
|
|
711
827
|
}
|
|
712
828
|
|
|
713
829
|
/*
|
|
@@ -716,7 +832,8 @@ VALUE probabilites_p_le( VALUE self, VALUE target ) {
|
|
|
716
832
|
* @return [Float] in range (0.0..1.0)
|
|
717
833
|
*/
|
|
718
834
|
VALUE probabilites_p_lt( VALUE self, VALUE target ) {
|
|
719
|
-
|
|
835
|
+
int t = NUM2INT(target);
|
|
836
|
+
return DBL2NUM( pl_p_lt( get_probability_list( self ), t ) );
|
|
720
837
|
}
|
|
721
838
|
|
|
722
839
|
/*
|
|
@@ -738,7 +855,9 @@ VALUE probabilites_expected( VALUE self ) {
|
|
|
738
855
|
VALUE probabilities_given_ge( VALUE self, VALUE target ) {
|
|
739
856
|
int t = NUM2INT(target);
|
|
740
857
|
ProbabilityList *pl = get_probability_list( self );
|
|
741
|
-
|
|
858
|
+
VALUE result = operate( OP_GE, pl, NULL, t, 0, 0 );
|
|
859
|
+
RB_GC_GUARD(self);
|
|
860
|
+
return result;
|
|
742
861
|
}
|
|
743
862
|
|
|
744
863
|
/*
|
|
@@ -750,7 +869,9 @@ VALUE probabilities_given_ge( VALUE self, VALUE target ) {
|
|
|
750
869
|
VALUE probabilities_given_le( VALUE self, VALUE target ) {
|
|
751
870
|
int t = NUM2INT(target);
|
|
752
871
|
ProbabilityList *pl = get_probability_list( self );
|
|
753
|
-
|
|
872
|
+
VALUE result = operate( OP_LE, pl, NULL, t, 0, 0 );
|
|
873
|
+
RB_GC_GUARD(self);
|
|
874
|
+
return result;
|
|
754
875
|
}
|
|
755
876
|
|
|
756
877
|
/*
|
|
@@ -763,7 +884,9 @@ VALUE probabilities_given_le( VALUE self, VALUE target ) {
|
|
|
763
884
|
VALUE probabilities_repeat_sum( VALUE self, VALUE nsum ) {
|
|
764
885
|
int n = NUM2INT(nsum);
|
|
765
886
|
ProbabilityList *pl = get_probability_list( self );
|
|
766
|
-
|
|
887
|
+
VALUE result = operate( OP_REPEAT, pl, NULL, n, 0, 0 );
|
|
888
|
+
RB_GC_GUARD(self);
|
|
889
|
+
return result;
|
|
767
890
|
}
|
|
768
891
|
|
|
769
892
|
/*
|
|
@@ -793,17 +916,15 @@ VALUE probabilities_repeat_n_sum_k( int argc, VALUE* argv, VALUE self ) {
|
|
|
793
916
|
n = NUM2INT(nsum);
|
|
794
917
|
k = NUM2INT(nkeepers);
|
|
795
918
|
pl = get_probability_list( self );
|
|
796
|
-
|
|
919
|
+
VALUE result = operate( OP_KEEP, pl, NULL, n, k, keep_best );
|
|
920
|
+
RB_GC_GUARD(self);
|
|
921
|
+
return result;
|
|
797
922
|
}
|
|
798
923
|
|
|
799
|
-
|
|
800
|
-
*
|
|
801
|
-
|
|
802
|
-
*
|
|
803
|
-
* @return [GamesDice::Probabilities] this object
|
|
804
|
-
*/
|
|
805
|
-
VALUE probabilities_each( VALUE self ) {
|
|
806
|
-
ProbabilityList *pl = get_probability_list( self );
|
|
924
|
+
static VALUE yield_probability_pairs( VALUE data ) {
|
|
925
|
+
ProbabilityOperation *op = (ProbabilityOperation *)data;
|
|
926
|
+
/* Keep the snapshot native-owned until every callback has finished. */
|
|
927
|
+
ProbabilityList *pl = copy_probability_list( op, op->a );
|
|
807
928
|
int i;
|
|
808
929
|
double *pr = pl->probs;
|
|
809
930
|
int o = pl->offset;
|
|
@@ -815,6 +936,19 @@ VALUE probabilities_each( VALUE self ) {
|
|
|
815
936
|
rb_yield( a );
|
|
816
937
|
}
|
|
817
938
|
}
|
|
939
|
+
return Qnil;
|
|
940
|
+
}
|
|
941
|
+
|
|
942
|
+
/*
|
|
943
|
+
* Iterates through value, probability pairs
|
|
944
|
+
* @yieldparam [Integer] result A result that may be possible in the dice scheme
|
|
945
|
+
* @yieldparam [Float] probability Probability of result, in range 0.0..1.0
|
|
946
|
+
* @return [GamesDice::Probabilities] this object
|
|
947
|
+
*/
|
|
948
|
+
VALUE probabilities_each( VALUE self ) {
|
|
949
|
+
/* Ruby callbacks may replace self's storage through marshal_load/initialize. */
|
|
950
|
+
ProbabilityOperation op = { .owned = NULL, .a = get_probability_list(self) };
|
|
951
|
+
rb_ensure( yield_probability_pairs, (VALUE)&op, cleanup_operation, (VALUE)&op );
|
|
818
952
|
return self;
|
|
819
953
|
}
|
|
820
954
|
|
|
@@ -849,32 +983,21 @@ VALUE probabilities_for_fair_die( VALUE self, VALUE sides ) {
|
|
|
849
983
|
* @return [GamesDice::Probabilities]
|
|
850
984
|
*/
|
|
851
985
|
VALUE probabilities_from_h( VALUE self, VALUE hash ) {
|
|
852
|
-
VALUE obj;
|
|
853
|
-
ProbabilityList *pl;
|
|
854
|
-
double error;
|
|
855
|
-
|
|
856
986
|
Check_Type( hash, T_HASH );
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
error = calc_cumulative( pl ) - 1.0;
|
|
873
|
-
if ( error < -1.0e-8 ) {
|
|
874
|
-
rb_raise( rb_eArgError, "Total probabilities are less than 1.0" );
|
|
875
|
-
} else if ( error > 1.0e-8 ) {
|
|
876
|
-
rb_raise( rb_eArgError, "Total probabilities are greater than 1.0" );
|
|
877
|
-
}
|
|
987
|
+
VALUE normalized = rb_hash_new();
|
|
988
|
+
VALUE input = rb_hash_dup(hash);
|
|
989
|
+
rb_hash_foreach( input, normalize_pair, normalized );
|
|
990
|
+
RB_GC_GUARD(input);
|
|
991
|
+
int bounds[2] = { INT_MAX, INT_MIN };
|
|
992
|
+
rb_hash_foreach( normalized, hash_bounds, (VALUE)bounds );
|
|
993
|
+
int slots = checked_slots( 1 + (int64_t)bounds[1] - bounds[0] );
|
|
994
|
+
VALUE obj = pl_alloc( Probabilities );
|
|
995
|
+
ProbabilityList *pl = get_probability_list( obj );
|
|
996
|
+
pl->offset = bounds[0];
|
|
997
|
+
alloc_probs_iv( pl, slots, 0.0 );
|
|
998
|
+
rb_hash_foreach( normalized, copy_pair, (VALUE)pl );
|
|
999
|
+
validate_mass( pl );
|
|
1000
|
+
RB_GC_GUARD(normalized);
|
|
878
1001
|
return obj;
|
|
879
1002
|
}
|
|
880
1003
|
|
|
@@ -887,13 +1010,17 @@ VALUE probabilities_from_h( VALUE self, VALUE hash ) {
|
|
|
887
1010
|
* @return [GamesDice::Probabilities]
|
|
888
1011
|
*/
|
|
889
1012
|
VALUE probabilities_add_distributions( VALUE self, VALUE gdpa, VALUE gdpb ) {
|
|
890
|
-
ProbabilityList *pl_a
|
|
891
|
-
ProbabilityList *pl_b
|
|
1013
|
+
ProbabilityList *pl_a;
|
|
1014
|
+
ProbabilityList *pl_b;
|
|
1015
|
+
|
|
892
1016
|
assert_value_wraps_pl( gdpa );
|
|
893
1017
|
assert_value_wraps_pl( gdpb );
|
|
894
1018
|
pl_a = get_probability_list( gdpa );
|
|
895
1019
|
pl_b = get_probability_list( gdpb );
|
|
896
|
-
|
|
1020
|
+
VALUE result = operate( OP_ADD, pl_a, pl_b, 0, 0, 0 );
|
|
1021
|
+
RB_GC_GUARD(gdpa);
|
|
1022
|
+
RB_GC_GUARD(gdpb);
|
|
1023
|
+
return result;
|
|
897
1024
|
}
|
|
898
1025
|
|
|
899
1026
|
/*
|
|
@@ -914,10 +1041,13 @@ VALUE probabilities_add_distributions_mult( VALUE self, VALUE m_a, VALUE gdpa, V
|
|
|
914
1041
|
assert_value_wraps_pl( gdpa );
|
|
915
1042
|
assert_value_wraps_pl( gdpb );
|
|
916
1043
|
mul_a = NUM2INT( m_a );
|
|
917
|
-
pl_a = get_probability_list( gdpa );
|
|
918
1044
|
mul_b = NUM2INT( m_b );
|
|
1045
|
+
pl_a = get_probability_list( gdpa );
|
|
919
1046
|
pl_b = get_probability_list( gdpb );
|
|
920
|
-
|
|
1047
|
+
VALUE result = operate( OP_WEIGHTED, pl_a, pl_b, mul_a, mul_b, 0 );
|
|
1048
|
+
RB_GC_GUARD(gdpa);
|
|
1049
|
+
RB_GC_GUARD(gdpb);
|
|
1050
|
+
return result;
|
|
921
1051
|
}
|
|
922
1052
|
|
|
923
1053
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
@@ -925,7 +1055,7 @@ VALUE probabilities_add_distributions_mult( VALUE self, VALUE m_a, VALUE gdpa, V
|
|
|
925
1055
|
// Setup Probabilities class for Ruby interpretter
|
|
926
1056
|
//
|
|
927
1057
|
|
|
928
|
-
void init_probabilities_class() {
|
|
1058
|
+
void init_probabilities_class(void) {
|
|
929
1059
|
VALUE GamesDice = rb_define_module("GamesDice");
|
|
930
1060
|
Probabilities = rb_define_class_under( GamesDice, "Probabilities", rb_cObject );
|
|
931
1061
|
rb_define_alloc_func( Probabilities, pl_alloc );
|