games_dice 0.4.0 → 0.5.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 +92 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +41 -3
- data/.yardopts +1 -1
- data/CHANGELOG.md +19 -0
- data/Gemfile +11 -0
- data/README.md +11 -33
- data/Rakefile +79 -14
- data/ext/games_dice/extconf.rb +29 -0
- data/ext/games_dice/games_dice.c +2 -2
- data/ext/games_dice/probabilities.c +178 -29
- data/ext/games_dice/probabilities.h +1 -17
- data/games_dice.gemspec +11 -19
- data/lib/games_dice/bunch.rb +27 -65
- data/lib/games_dice/bunch_helpers.rb +68 -0
- data/lib/games_dice/complex_die.rb +10 -146
- data/lib/games_dice/complex_die_helpers.rb +238 -46
- data/lib/games_dice/dice.rb +17 -10
- data/lib/games_dice/die.rb +2 -2
- data/lib/games_dice/die_result.rb +84 -71
- data/lib/games_dice/marshal.rb +26 -3
- data/lib/games_dice/parser.rb +128 -102
- data/lib/games_dice/version.rb +2 -1
- data/lib/games_dice.rb +7 -9
- data/spec/bunch_spec.rb +72 -72
- data/spec/complex_die_spec.rb +158 -158
- data/spec/dice_spec.rb +5 -5
- data/spec/die_result_spec.rb +98 -98
- data/spec/die_spec.rb +19 -19
- data/spec/helpers.rb +2 -4
- data/spec/map_rule_spec.rb +17 -17
- data/spec/parser_spec.rb +7 -7
- data/spec/probability_spec.rb +248 -194
- data/spec/readme_spec.rb +28 -22
- data/spec/reroll_rule_spec.rb +15 -15
- metadata +16 -139
- data/.travis.yml +0 -11
|
@@ -21,7 +21,7 @@ VALUE Probabilities = Qnil;
|
|
|
21
21
|
// General utils
|
|
22
22
|
//
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
static int max( int *a, int n ) {
|
|
25
25
|
int m = -1000000000;
|
|
26
26
|
int i;
|
|
27
27
|
for ( i=0; i < n; i++ ) {
|
|
@@ -30,7 +30,7 @@ inline int max( int *a, int n ) {
|
|
|
30
30
|
return m;
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
static int min( int *a, int n ) {
|
|
34
34
|
int m = 1000000000;
|
|
35
35
|
int i;
|
|
36
36
|
for ( i=0; i < n; i++ ) {
|
|
@@ -110,7 +110,7 @@ double num_arrangements( int *args, int nargs ) {
|
|
|
110
110
|
// Probability List basics - create, delete, copy
|
|
111
111
|
//
|
|
112
112
|
|
|
113
|
-
ProbabilityList *create_probability_list() {
|
|
113
|
+
ProbabilityList *create_probability_list(void) {
|
|
114
114
|
ProbabilityList *pl;
|
|
115
115
|
pl = malloc (sizeof(ProbabilityList));
|
|
116
116
|
if ( pl == NULL ) {
|
|
@@ -183,7 +183,7 @@ ProbabilityList *copy_probability_list( ProbabilityList *orig ) {
|
|
|
183
183
|
return pl;
|
|
184
184
|
}
|
|
185
185
|
|
|
186
|
-
|
|
186
|
+
static ProbabilityList *new_basic_pl( int nslots, double iv, int o ) {
|
|
187
187
|
ProbabilityList *pl = create_probability_list();
|
|
188
188
|
alloc_probs_iv( pl, nslots, iv );
|
|
189
189
|
pl->offset = o;
|
|
@@ -195,11 +195,13 @@ inline ProbabilityList *new_basic_pl( int nslots, double iv, int o ) {
|
|
|
195
195
|
// Probability List core "native" methods
|
|
196
196
|
//
|
|
197
197
|
|
|
198
|
-
|
|
198
|
+
static double pl_p_le( ProbabilityList *pl, int target );
|
|
199
|
+
|
|
200
|
+
static int pl_min( ProbabilityList *pl ) {
|
|
199
201
|
return pl->offset;
|
|
200
202
|
}
|
|
201
203
|
|
|
202
|
-
|
|
204
|
+
static int pl_max( ProbabilityList *pl ) {
|
|
203
205
|
return pl->offset + pl->slots - 1;
|
|
204
206
|
}
|
|
205
207
|
|
|
@@ -243,7 +245,7 @@ ProbabilityList *pl_add_distributions_mult( int mul_a, ProbabilityList *pl_a, in
|
|
|
243
245
|
return pl;
|
|
244
246
|
}
|
|
245
247
|
|
|
246
|
-
|
|
248
|
+
static double pl_p_eql( ProbabilityList *pl, int target ) {
|
|
247
249
|
int idx = target - pl->offset;
|
|
248
250
|
if ( idx < 0 || idx >= pl->slots ) {
|
|
249
251
|
return 0.0;
|
|
@@ -251,15 +253,15 @@ inline double pl_p_eql( ProbabilityList *pl, int target ) {
|
|
|
251
253
|
return (pl->probs)[idx];
|
|
252
254
|
}
|
|
253
255
|
|
|
254
|
-
|
|
256
|
+
static double pl_p_gt( ProbabilityList *pl, int target ) {
|
|
255
257
|
return 1.0 - pl_p_le( pl, target );
|
|
256
258
|
}
|
|
257
259
|
|
|
258
|
-
|
|
260
|
+
static double pl_p_lt( ProbabilityList *pl, int target ) {
|
|
259
261
|
return pl_p_le( pl, target - 1 );
|
|
260
262
|
}
|
|
261
263
|
|
|
262
|
-
|
|
264
|
+
static double pl_p_le( ProbabilityList *pl, int target ) {
|
|
263
265
|
int idx = target - pl->offset;
|
|
264
266
|
if ( idx < 0 ) {
|
|
265
267
|
return 0.0;
|
|
@@ -270,11 +272,11 @@ inline double pl_p_le( ProbabilityList *pl, int target ) {
|
|
|
270
272
|
return (pl->cumulative)[idx];
|
|
271
273
|
}
|
|
272
274
|
|
|
273
|
-
|
|
275
|
+
static double pl_p_ge( ProbabilityList *pl, int target ) {
|
|
274
276
|
return 1.0 - pl_p_le( pl, target - 1 );
|
|
275
277
|
}
|
|
276
278
|
|
|
277
|
-
|
|
279
|
+
static double pl_expected( ProbabilityList *pl ) {
|
|
278
280
|
double t = 0.0;
|
|
279
281
|
int o = pl->offset;
|
|
280
282
|
int s = pl->slots;
|
|
@@ -426,7 +428,7 @@ void calc_keep_distributions( ProbabilityList *pl, int k, int q, int kbest, Prob
|
|
|
426
428
|
return;
|
|
427
429
|
}
|
|
428
430
|
|
|
429
|
-
|
|
431
|
+
static void clear_pl_array( int k, ProbabilityList **pl_array ) {
|
|
430
432
|
int n;
|
|
431
433
|
for ( n=0; n<k; n++) {
|
|
432
434
|
if ( pl_array[n] != NULL ) {
|
|
@@ -515,23 +517,50 @@ ProbabilityList *pl_repeat_n_sum_k( ProbabilityList *pl, int n, int k, int kbest
|
|
|
515
517
|
// Ruby integration
|
|
516
518
|
//
|
|
517
519
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
+
static void free_probability_list( void *ptr ) {
|
|
521
|
+
destroy_probability_list( ptr );
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
static size_t probability_list_memsize( const void *ptr ) {
|
|
525
|
+
const ProbabilityList *pl = ptr;
|
|
526
|
+
size_t size = sizeof(ProbabilityList);
|
|
527
|
+
|
|
528
|
+
if ( pl == NULL ) return 0;
|
|
529
|
+
if ( pl->probs != NULL ) size += pl->slots * sizeof(double);
|
|
530
|
+
if ( pl->cumulative != NULL ) size += pl->slots * sizeof(double);
|
|
531
|
+
return size;
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
static const rb_data_type_t probability_list_type = {
|
|
535
|
+
.wrap_struct_name = "GamesDice::Probabilities",
|
|
536
|
+
.function = {
|
|
537
|
+
.dmark = NULL,
|
|
538
|
+
.dfree = free_probability_list,
|
|
539
|
+
.dsize = probability_list_memsize,
|
|
540
|
+
.dcompact = NULL,
|
|
541
|
+
.reserved = { NULL }
|
|
542
|
+
},
|
|
543
|
+
.parent = NULL,
|
|
544
|
+
.data = NULL,
|
|
545
|
+
.flags = 0
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
static inline VALUE pl_as_ruby_class( ProbabilityList *pl, VALUE klass ) {
|
|
549
|
+
return TypedData_Wrap_Struct( klass, &probability_list_type, pl );
|
|
520
550
|
}
|
|
521
551
|
|
|
522
552
|
VALUE pl_alloc(VALUE klass) {
|
|
523
553
|
return pl_as_ruby_class( create_probability_list(), klass );
|
|
524
554
|
}
|
|
525
555
|
|
|
526
|
-
inline ProbabilityList *get_probability_list( VALUE obj ) {
|
|
556
|
+
static inline ProbabilityList *get_probability_list( VALUE obj ) {
|
|
527
557
|
ProbabilityList *pl;
|
|
528
|
-
|
|
558
|
+
TypedData_Get_Struct( obj, ProbabilityList, &probability_list_type, pl );
|
|
529
559
|
return pl;
|
|
530
560
|
}
|
|
531
561
|
|
|
532
562
|
void assert_value_wraps_pl( VALUE obj ) {
|
|
533
|
-
if (
|
|
534
|
-
RDATA(obj)->dfree != (RUBY_DATA_FUNC)destroy_probability_list) {
|
|
563
|
+
if ( ! rb_typeddata_is_kind_of( obj, &probability_list_type ) ) {
|
|
535
564
|
rb_raise( rb_eTypeError, "Expected a Probabilities object, but got something else" );
|
|
536
565
|
}
|
|
537
566
|
}
|
|
@@ -574,6 +603,13 @@ int copy_key_value( VALUE key, VALUE val, VALUE obj ) {
|
|
|
574
603
|
// Ruby class and instance methods for Probabilities
|
|
575
604
|
//
|
|
576
605
|
|
|
606
|
+
/*
|
|
607
|
+
* @overload initialize(probs, offset)
|
|
608
|
+
* Creates new instance of GamesDice::Probabilities.
|
|
609
|
+
* @param [Array<Float>] probs Each entry in the array is the probability of getting a result
|
|
610
|
+
* @param [Integer] offset The result associated with index of 0 in the array
|
|
611
|
+
* @return [GamesDice::Probabilities]
|
|
612
|
+
*/
|
|
577
613
|
VALUE probabilities_initialize( VALUE self, VALUE arr, VALUE offset ) {
|
|
578
614
|
int i, o, s;
|
|
579
615
|
double error, p_item;
|
|
@@ -604,7 +640,11 @@ VALUE probabilities_initialize( VALUE self, VALUE arr, VALUE offset ) {
|
|
|
604
640
|
return self;
|
|
605
641
|
}
|
|
606
642
|
|
|
607
|
-
|
|
643
|
+
/*
|
|
644
|
+
* @overload clone
|
|
645
|
+
* Cloning an object of this class creates a deep copy of the probabilities hash.
|
|
646
|
+
* @return [GamesDice::Probabilities]
|
|
647
|
+
*/
|
|
608
648
|
VALUE probabilities_initialize_copy( VALUE copy, VALUE orig ) {
|
|
609
649
|
ProbabilityList *pl_copy;
|
|
610
650
|
ProbabilityList *pl_orig;
|
|
@@ -622,6 +662,12 @@ VALUE probabilities_initialize_copy( VALUE copy, VALUE orig ) {
|
|
|
622
662
|
return copy;
|
|
623
663
|
}
|
|
624
664
|
|
|
665
|
+
/*
|
|
666
|
+
* A hash representation of the distribution. Each key is an integer result,
|
|
667
|
+
* and the matching value is probability of getting that result. A new hash is generated on each
|
|
668
|
+
* call to this method.
|
|
669
|
+
* @return [Hash]
|
|
670
|
+
*/
|
|
625
671
|
VALUE probabilities_to_h( VALUE self ) {
|
|
626
672
|
ProbabilityList *pl = get_probability_list( self );
|
|
627
673
|
VALUE h = rb_hash_new();
|
|
@@ -637,56 +683,126 @@ VALUE probabilities_to_h( VALUE self ) {
|
|
|
637
683
|
return h;
|
|
638
684
|
}
|
|
639
685
|
|
|
686
|
+
/*
|
|
687
|
+
* @overload min
|
|
688
|
+
* @!attribute [r] min
|
|
689
|
+
* Minimum result in the distribution
|
|
690
|
+
* @return [Integer]
|
|
691
|
+
*/
|
|
640
692
|
VALUE probabilities_min( VALUE self ) {
|
|
641
693
|
return INT2NUM( pl_min( get_probability_list( self ) ) );
|
|
642
694
|
}
|
|
643
695
|
|
|
696
|
+
/*
|
|
697
|
+
* @overload max
|
|
698
|
+
* @!attribute [r] max
|
|
699
|
+
* Maximum result in the distribution
|
|
700
|
+
* @return [Integer]
|
|
701
|
+
*/
|
|
644
702
|
VALUE probabilities_max( VALUE self ) {
|
|
645
703
|
return INT2NUM( pl_max( get_probability_list( self ) ) );
|
|
646
704
|
}
|
|
647
705
|
|
|
706
|
+
/*
|
|
707
|
+
* Probability of result equalling specific target
|
|
708
|
+
* @param [Integer] target
|
|
709
|
+
* @return [Float] in range (0.0..1.0)
|
|
710
|
+
*/
|
|
648
711
|
VALUE probabilites_p_eql( VALUE self, VALUE target ) {
|
|
649
712
|
return DBL2NUM( pl_p_eql( get_probability_list( self ), NUM2INT(target) ) );
|
|
650
713
|
}
|
|
651
714
|
|
|
715
|
+
/*
|
|
716
|
+
* Probability of result being greater than specific target
|
|
717
|
+
* @param [Integer] target
|
|
718
|
+
* @return [Float] in range (0.0..1.0)
|
|
719
|
+
*/
|
|
652
720
|
VALUE probabilites_p_gt( VALUE self, VALUE target ) {
|
|
653
721
|
return DBL2NUM( pl_p_gt( get_probability_list( self ), NUM2INT(target) ) );
|
|
654
722
|
}
|
|
655
723
|
|
|
724
|
+
/*
|
|
725
|
+
* Probability of result being equal to or greater than specific target
|
|
726
|
+
* @param [Integer] target
|
|
727
|
+
* @return [Float] in range (0.0..1.0)
|
|
728
|
+
*/
|
|
656
729
|
VALUE probabilites_p_ge( VALUE self, VALUE target ) {
|
|
657
730
|
return DBL2NUM( pl_p_ge( get_probability_list( self ), NUM2INT(target) ) );
|
|
658
731
|
}
|
|
659
732
|
|
|
733
|
+
/*
|
|
734
|
+
* Probability of result being equal to or less than specific target
|
|
735
|
+
* @param [Integer] target
|
|
736
|
+
* @return [Float] in range (0.0..1.0)
|
|
737
|
+
*/
|
|
660
738
|
VALUE probabilites_p_le( VALUE self, VALUE target ) {
|
|
661
739
|
return DBL2NUM( pl_p_le( get_probability_list( self ), NUM2INT(target) ) );
|
|
662
740
|
}
|
|
663
741
|
|
|
742
|
+
/*
|
|
743
|
+
* Probability of result being less than specific target
|
|
744
|
+
* @param [Integer] target
|
|
745
|
+
* @return [Float] in range (0.0..1.0)
|
|
746
|
+
*/
|
|
664
747
|
VALUE probabilites_p_lt( VALUE self, VALUE target ) {
|
|
665
748
|
return DBL2NUM( pl_p_lt( get_probability_list( self ), NUM2INT(target) ) );
|
|
666
749
|
}
|
|
667
750
|
|
|
751
|
+
/*
|
|
752
|
+
* @overload expected
|
|
753
|
+
* @!attribute [r] expected
|
|
754
|
+
* Expected value of distribution.
|
|
755
|
+
* @return [Float]
|
|
756
|
+
*/
|
|
668
757
|
VALUE probabilites_expected( VALUE self ) {
|
|
669
758
|
return DBL2NUM( pl_expected( get_probability_list( self ) ) );
|
|
670
759
|
}
|
|
671
760
|
|
|
761
|
+
/*
|
|
762
|
+
* Probability distribution derived from this one, where we know (or are only interested in
|
|
763
|
+
* situations where) the result is greater than or equal to target.
|
|
764
|
+
* @param [Integer] target
|
|
765
|
+
* @return [GamesDice::Probabilities] new distribution.
|
|
766
|
+
*/
|
|
672
767
|
VALUE probabilities_given_ge( VALUE self, VALUE target ) {
|
|
673
768
|
int t = NUM2INT(target);
|
|
674
769
|
ProbabilityList *pl = get_probability_list( self );
|
|
675
770
|
return pl_as_ruby_class( pl_given_ge( pl, t ), Probabilities );
|
|
676
771
|
}
|
|
677
772
|
|
|
773
|
+
/*
|
|
774
|
+
* Probability distribution derived from this one, where we know (or are only interested in
|
|
775
|
+
* situations where) the result is less than or equal to target.
|
|
776
|
+
* @param [Integer] target
|
|
777
|
+
* @return [GamesDice::Probabilities] new distribution.
|
|
778
|
+
*/
|
|
678
779
|
VALUE probabilities_given_le( VALUE self, VALUE target ) {
|
|
679
780
|
int t = NUM2INT(target);
|
|
680
781
|
ProbabilityList *pl = get_probability_list( self );
|
|
681
782
|
return pl_as_ruby_class( pl_given_le( pl, t ), Probabilities );
|
|
682
783
|
}
|
|
683
784
|
|
|
785
|
+
/*
|
|
786
|
+
* @overload repeat_sum(n)
|
|
787
|
+
* Adds a distribution to itself repeatedly, to simulate a number of dice
|
|
788
|
+
* results being summed.
|
|
789
|
+
* @param [Integer] n Number of repetitions, must be at least 1
|
|
790
|
+
* @return [GamesDice::Probabilities] new distribution
|
|
791
|
+
*/
|
|
684
792
|
VALUE probabilities_repeat_sum( VALUE self, VALUE nsum ) {
|
|
685
793
|
int n = NUM2INT(nsum);
|
|
686
794
|
ProbabilityList *pl = get_probability_list( self );
|
|
687
795
|
return pl_as_ruby_class( pl_repeat_sum( pl, n ), Probabilities );
|
|
688
796
|
}
|
|
689
797
|
|
|
798
|
+
/*
|
|
799
|
+
* @overload repeat_n_sum_k(n, k, kmode = :keep_best)
|
|
800
|
+
* Calculates distribution generated by summing best k results of n iterations
|
|
801
|
+
* of the distribution.
|
|
802
|
+
* @param [Integer] n Number of repetitions, must be at least 1
|
|
803
|
+
* @param [Integer] k Number of best results to keep and sum
|
|
804
|
+
* @return [GamesDice::Probabilities] new distribution
|
|
805
|
+
*/
|
|
690
806
|
VALUE probabilities_repeat_n_sum_k( int argc, VALUE* argv, VALUE self ) {
|
|
691
807
|
VALUE nsum, nkeepers, kmode;
|
|
692
808
|
int keep_best, n, k;
|
|
@@ -709,6 +825,12 @@ VALUE probabilities_repeat_n_sum_k( int argc, VALUE* argv, VALUE self ) {
|
|
|
709
825
|
return pl_as_ruby_class( pl_repeat_n_sum_k( pl, n, k, keep_best ), Probabilities );
|
|
710
826
|
}
|
|
711
827
|
|
|
828
|
+
/*
|
|
829
|
+
* Iterates through value, probability pairs
|
|
830
|
+
* @yieldparam [Integer] result A result that may be possible in the dice scheme
|
|
831
|
+
* @yieldparam [Float] probability Probability of result, in range 0.0..1.0
|
|
832
|
+
* @return [GamesDice::Probabilities] this object
|
|
833
|
+
*/
|
|
712
834
|
VALUE probabilities_each( VALUE self ) {
|
|
713
835
|
ProbabilityList *pl = get_probability_list( self );
|
|
714
836
|
int i;
|
|
@@ -725,6 +847,11 @@ VALUE probabilities_each( VALUE self ) {
|
|
|
725
847
|
return self;
|
|
726
848
|
}
|
|
727
849
|
|
|
850
|
+
/*
|
|
851
|
+
* Distribution for a die with equal chance of rolling 1..N
|
|
852
|
+
* @param [Integer] sides Number of sides on die
|
|
853
|
+
* @return [GamesDice::Probabilities]
|
|
854
|
+
*/
|
|
728
855
|
VALUE probabilities_for_fair_die( VALUE self, VALUE sides ) {
|
|
729
856
|
int s = NUM2INT( sides );
|
|
730
857
|
VALUE obj;
|
|
@@ -743,6 +870,13 @@ VALUE probabilities_for_fair_die( VALUE self, VALUE sides ) {
|
|
|
743
870
|
return obj;
|
|
744
871
|
}
|
|
745
872
|
|
|
873
|
+
/*
|
|
874
|
+
* @overload from_h(prob_hash)
|
|
875
|
+
* Creates new instance of GamesDice::Probabilities.
|
|
876
|
+
* @param [Hash] prob_hash A hash representation of the distribution, each key is an integer result,
|
|
877
|
+
* and the matching value is probability of getting that result
|
|
878
|
+
* @return [GamesDice::Probabilities]
|
|
879
|
+
*/
|
|
746
880
|
VALUE probabilities_from_h( VALUE self, VALUE hash ) {
|
|
747
881
|
VALUE obj;
|
|
748
882
|
ProbabilityList *pl;
|
|
@@ -773,9 +907,18 @@ VALUE probabilities_from_h( VALUE self, VALUE hash ) {
|
|
|
773
907
|
return obj;
|
|
774
908
|
}
|
|
775
909
|
|
|
910
|
+
/*
|
|
911
|
+
* @overload add_distributions(pd_a, pd_b)
|
|
912
|
+
* Combines two distributions to create a third, that represents the distribution created when adding
|
|
913
|
+
* results together.
|
|
914
|
+
* @param [GamesDice::Probabilities] pd_a First distribution
|
|
915
|
+
* @param [GamesDice::Probabilities] pd_b Second distribution
|
|
916
|
+
* @return [GamesDice::Probabilities]
|
|
917
|
+
*/
|
|
776
918
|
VALUE probabilities_add_distributions( VALUE self, VALUE gdpa, VALUE gdpb ) {
|
|
777
|
-
ProbabilityList *pl_a
|
|
778
|
-
ProbabilityList *pl_b
|
|
919
|
+
ProbabilityList *pl_a;
|
|
920
|
+
ProbabilityList *pl_b;
|
|
921
|
+
|
|
779
922
|
assert_value_wraps_pl( gdpa );
|
|
780
923
|
assert_value_wraps_pl( gdpb );
|
|
781
924
|
pl_a = get_probability_list( gdpa );
|
|
@@ -783,6 +926,16 @@ VALUE probabilities_add_distributions( VALUE self, VALUE gdpa, VALUE gdpb ) {
|
|
|
783
926
|
return pl_as_ruby_class( pl_add_distributions( pl_a, pl_b ), Probabilities );
|
|
784
927
|
}
|
|
785
928
|
|
|
929
|
+
/*
|
|
930
|
+
* @overload add_distributions_mult(m_a, pd_a, m_b, pd_b)
|
|
931
|
+
* Combines two distributions with multipliers to create a third, that represents the distribution
|
|
932
|
+
* created when adding weighted results together.
|
|
933
|
+
* @param [Integer] m_a Weighting for first distribution
|
|
934
|
+
* @param [GamesDice::Probabilities] pd_a First distribution
|
|
935
|
+
* @param [Integer] m_b Weighting for second distribution
|
|
936
|
+
* @param [GamesDice::Probabilities] pd_b Second distribution
|
|
937
|
+
* @return [GamesDice::Probabilities]
|
|
938
|
+
*/
|
|
786
939
|
VALUE probabilities_add_distributions_mult( VALUE self, VALUE m_a, VALUE gdpa, VALUE m_b, VALUE gdpb ) {
|
|
787
940
|
int mul_a, mul_b;
|
|
788
941
|
ProbabilityList *pl_a;
|
|
@@ -797,17 +950,14 @@ VALUE probabilities_add_distributions_mult( VALUE self, VALUE m_a, VALUE gdpa, V
|
|
|
797
950
|
return pl_as_ruby_class( pl_add_distributions_mult( mul_a, pl_a, mul_b, pl_b ), Probabilities );
|
|
798
951
|
}
|
|
799
952
|
|
|
800
|
-
VALUE probabilities_implemented_in( VALUE self ) {
|
|
801
|
-
return ID2SYM( rb_intern("c") );
|
|
802
|
-
}
|
|
803
|
-
|
|
804
953
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
805
954
|
//
|
|
806
955
|
// Setup Probabilities class for Ruby interpretter
|
|
807
956
|
//
|
|
808
957
|
|
|
809
|
-
void init_probabilities_class(
|
|
810
|
-
|
|
958
|
+
void init_probabilities_class(void) {
|
|
959
|
+
VALUE GamesDice = rb_define_module("GamesDice");
|
|
960
|
+
Probabilities = rb_define_class_under( GamesDice, "Probabilities", rb_cObject );
|
|
811
961
|
rb_define_alloc_func( Probabilities, pl_alloc );
|
|
812
962
|
rb_define_method( Probabilities, "initialize", probabilities_initialize, 2 );
|
|
813
963
|
rb_define_method( Probabilities, "initialize_copy", probabilities_initialize_copy, 1 );
|
|
@@ -828,7 +978,6 @@ void init_probabilities_class( VALUE ParentModule ) {
|
|
|
828
978
|
rb_define_singleton_method( Probabilities, "for_fair_die", probabilities_for_fair_die, 1 );
|
|
829
979
|
rb_define_singleton_method( Probabilities, "add_distributions", probabilities_add_distributions, 2 );
|
|
830
980
|
rb_define_singleton_method( Probabilities, "add_distributions_mult", probabilities_add_distributions_mult, 4 );
|
|
831
|
-
rb_define_singleton_method( Probabilities, "implemented_in", probabilities_implemented_in, 0 );
|
|
832
981
|
rb_define_singleton_method( Probabilities, "from_h", probabilities_from_h, 1 );
|
|
833
982
|
return;
|
|
834
983
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
|
|
8
8
|
#include <ruby.h>
|
|
9
9
|
|
|
10
|
-
void init_probabilities_class(
|
|
10
|
+
void init_probabilities_class(void);
|
|
11
11
|
|
|
12
12
|
typedef struct _pd {
|
|
13
13
|
int offset;
|
|
@@ -16,26 +16,10 @@ typedef struct _pd {
|
|
|
16
16
|
double *cumulative;
|
|
17
17
|
} ProbabilityList;
|
|
18
18
|
|
|
19
|
-
inline int pl_min( ProbabilityList *pl );
|
|
20
|
-
|
|
21
|
-
inline int pl_max( ProbabilityList *pl );
|
|
22
|
-
|
|
23
19
|
ProbabilityList *pl_add_distributions( ProbabilityList *pl_a, ProbabilityList *pl_b );
|
|
24
20
|
|
|
25
21
|
ProbabilityList *pl_add_distributions_mult( int mul_a, ProbabilityList *pl_a, int mul_b, ProbabilityList *pl_b );
|
|
26
22
|
|
|
27
|
-
inline double pl_p_eql( ProbabilityList *pl, int target );
|
|
28
|
-
|
|
29
|
-
inline double pl_p_gt( ProbabilityList *pl, int target );
|
|
30
|
-
|
|
31
|
-
inline double pl_p_lt( ProbabilityList *pl, int target );
|
|
32
|
-
|
|
33
|
-
inline double pl_p_le( ProbabilityList *pl, int target );
|
|
34
|
-
|
|
35
|
-
inline double pl_p_ge( ProbabilityList *pl, int target );
|
|
36
|
-
|
|
37
|
-
inline double pl_expected( ProbabilityList *pl );
|
|
38
|
-
|
|
39
23
|
ProbabilityList *pl_given_ge( ProbabilityList *pl, int target );
|
|
40
24
|
|
|
41
25
|
ProbabilityList *pl_given_le( ProbabilityList *pl, int target );
|
data/games_dice.gemspec
CHANGED
|
@@ -10,31 +10,23 @@ Gem::Specification.new do |gem|
|
|
|
10
10
|
gem.version = GamesDice::VERSION
|
|
11
11
|
gem.authors = ['Neil Slater']
|
|
12
12
|
gem.email = ['slobo777@gmail.com']
|
|
13
|
-
gem.description =
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
gem.description = <<~GEMDESC
|
|
14
|
+
A library for simulating dice. Use it to construct dice-rolling systems used in role-playing and board games.
|
|
15
|
+
GEMDESC
|
|
16
|
+
gem.summary = <<~GEMSUMM
|
|
17
|
+
Simulates and explains dice rolls from simple "1d6" to complex "roll 7 ten-sided dice, take best 3,
|
|
18
|
+
results of 10 roll again and add on".
|
|
19
|
+
GEMSUMM
|
|
16
20
|
gem.homepage = 'https://github.com/neilslater/games_dice'
|
|
17
21
|
gem.license = 'MIT'
|
|
18
22
|
|
|
19
|
-
gem.required_ruby_version = '>=
|
|
20
|
-
gem.add_development_dependency 'coveralls', '>= 0.6.7'
|
|
21
|
-
gem.add_development_dependency 'json', '>= 1.7.7'
|
|
22
|
-
gem.add_development_dependency 'rake', '>= 1.9.1'
|
|
23
|
-
gem.add_development_dependency 'rake-compiler', '>= 0.8.3'
|
|
24
|
-
gem.add_development_dependency 'rspec', '>= 2.13.0'
|
|
25
|
-
gem.add_development_dependency 'rubocop', '>= 1.2.1'
|
|
26
|
-
gem.add_development_dependency 'yard', '>= 0.8.6'
|
|
23
|
+
gem.required_ruby_version = '>= 3.3.0'
|
|
27
24
|
|
|
28
|
-
|
|
29
|
-
# However, it has a C extension, which will not work in JRuby. This only affects the gem build process, so
|
|
30
|
-
# is only really used in environments like Travis, and is safe to wrap like this in the gemspec.
|
|
31
|
-
gem.add_development_dependency 'redcarpet', '>=3.5.1' if RUBY_DESCRIPTION !~ /jruby/
|
|
25
|
+
gem.add_dependency 'parslet', '~> 2.0'
|
|
32
26
|
|
|
33
|
-
gem.
|
|
34
|
-
|
|
35
|
-
gem.files = `git ls-files`.split($INPUT_RECORD_SEPARATOR)
|
|
27
|
+
gem.files = `git ls-files -z`.split("\0").select { |file| File.file?(file) }
|
|
36
28
|
gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) }
|
|
37
29
|
gem.extensions = gem.files.grep(%r{/extconf\.rb$})
|
|
38
|
-
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
39
30
|
gem.require_paths = ['lib']
|
|
31
|
+
gem.metadata['rubygems_mfa_required'] = 'true'
|
|
40
32
|
end
|
data/lib/games_dice/bunch.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'games_dice/bunch_helpers'
|
|
4
|
+
|
|
3
5
|
module GamesDice
|
|
4
6
|
# This class models a number of identical dice, which may be either GamesDice::Die or
|
|
5
7
|
# GamesDice::ComplexDie objects.
|
|
@@ -21,6 +23,9 @@ module GamesDice
|
|
|
21
23
|
# d.explain_result # => "4, 9, 2, 9, 1. Keep: 9 + 9 = 18"
|
|
22
24
|
#
|
|
23
25
|
class Bunch
|
|
26
|
+
include KeepHelpers
|
|
27
|
+
include ExplainHelpers
|
|
28
|
+
|
|
24
29
|
# The constructor accepts parameters that are suitable for either GamesDice::Die or GamesDice::ComplexDie
|
|
25
30
|
# and decides which of those classes to instantiate.
|
|
26
31
|
# @param [Hash] options
|
|
@@ -28,8 +33,10 @@ module GamesDice
|
|
|
28
33
|
# @option options [Integer] :sides Number of sides on a single die in the bunch, *mandatory*
|
|
29
34
|
# @option options [String] :name Optional name for the bunch
|
|
30
35
|
# @option options [Array<GamesDice::RerollRule,Array>] :rerolls Optional rules that cause the die to roll again
|
|
31
|
-
# @option options [Array<GamesDice::MapRule,Array>] :maps Optional rules to convert a value into a final result
|
|
32
|
-
#
|
|
36
|
+
# @option options [Array<GamesDice::MapRule,Array>] :maps Optional rules to convert a value into a final result
|
|
37
|
+
# for the die
|
|
38
|
+
# @option options [#rand] :prng Optional alternative source of randomness to Ruby's built-in #rand, passed to
|
|
39
|
+
# GamesDice::Die's constructor
|
|
33
40
|
# @option options [Symbol] :keep_mode Optional, either *:keep_best* or *:keep_worst*
|
|
34
41
|
# @option options [Integer] :keep_number Optional number of dice to keep when :keep_mode is not nil
|
|
35
42
|
# @return [GamesDice::Bunch]
|
|
@@ -138,26 +145,15 @@ module GamesDice
|
|
|
138
145
|
# Simulates rolling the bunch of identical dice
|
|
139
146
|
# @return [Integer] Sum of all rolled dice, or sum of all keepers
|
|
140
147
|
def roll
|
|
141
|
-
|
|
142
|
-
@
|
|
148
|
+
generate_raw_results
|
|
149
|
+
return @result if !@keep_mode || @keep_number.to_i >= @ndice
|
|
143
150
|
|
|
144
|
-
@
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
end
|
|
148
|
-
|
|
149
|
-
return @result unless @keep_mode
|
|
150
|
-
|
|
151
|
-
use_dice = if @keep_mode && @keep_number < @ndice
|
|
152
|
-
case @keep_mode
|
|
153
|
-
when :keep_best then @raw_result_details.sort[-@keep_number..]
|
|
154
|
-
when :keep_worst then @raw_result_details.sort[0..(@keep_number - 1)]
|
|
155
|
-
end
|
|
156
|
-
else
|
|
157
|
-
@raw_result_details
|
|
151
|
+
use_dice = case @keep_mode
|
|
152
|
+
when :keep_best then @raw_result_details.sort[-@keep_number..]
|
|
153
|
+
when :keep_worst then @raw_result_details.sort[0..(@keep_number - 1)]
|
|
158
154
|
end
|
|
159
155
|
|
|
160
|
-
@result = use_dice.
|
|
156
|
+
@result = use_dice.sum
|
|
161
157
|
end
|
|
162
158
|
|
|
163
159
|
# @!attribute [r] explain_result
|
|
@@ -166,44 +162,25 @@ module GamesDice
|
|
|
166
162
|
def explain_result
|
|
167
163
|
return nil unless @result
|
|
168
164
|
|
|
169
|
-
explanation = ''
|
|
170
|
-
|
|
171
165
|
# With #keep_mode, we may need to show unused and used dice separately
|
|
172
166
|
used_dice = result_details
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
# Pick highest numbers and their associated details
|
|
176
|
-
if @keep_mode && @keep_number < @ndice
|
|
177
|
-
full_dice = result_details.sort_by(&:total)
|
|
178
|
-
case @keep_mode
|
|
179
|
-
when :keep_best
|
|
180
|
-
used_dice = full_dice[-@keep_number..]
|
|
181
|
-
unused_dice = full_dice[0..full_dice.length - 1 - @keep_number]
|
|
182
|
-
when :keep_worst
|
|
183
|
-
used_dice = full_dice[0..(@keep_number - 1)]
|
|
184
|
-
unused_dice = full_dice[@keep_number..(full_dice.length - 1)]
|
|
185
|
-
end
|
|
186
|
-
end
|
|
187
|
-
|
|
188
|
-
# Show unused dice (if any)
|
|
189
|
-
if @keep_mode || @single_die.maps
|
|
190
|
-
explanation += result_details.map(&:explain_value).join(', ')
|
|
191
|
-
if @keep_mode
|
|
192
|
-
separator = @single_die.maps ? ', ' : ' + '
|
|
193
|
-
explanation += ". Keep: #{used_dice.map(&:explain_total).join(separator)}"
|
|
194
|
-
end
|
|
195
|
-
explanation += ". Successes: #{@result}" if @single_die.maps
|
|
196
|
-
explanation += " = #{@result}" if @keep_mode && !@single_die.maps && @keep_number > 1
|
|
197
|
-
else
|
|
198
|
-
explanation += used_dice.map(&:explain_value).join(' + ')
|
|
199
|
-
explanation += " = #{@result}" if @ndice > 1
|
|
200
|
-
end
|
|
167
|
+
used_dice, = find_used_dice_due_to_keep_mode(result_details) if @keep_mode && @keep_number < @ndice
|
|
201
168
|
|
|
202
|
-
|
|
169
|
+
build_explanation(used_dice)
|
|
203
170
|
end
|
|
204
171
|
|
|
205
172
|
private
|
|
206
173
|
|
|
174
|
+
def generate_raw_results
|
|
175
|
+
@result = 0
|
|
176
|
+
@raw_result_details = []
|
|
177
|
+
|
|
178
|
+
@ndice.times do
|
|
179
|
+
@result += @single_die.roll
|
|
180
|
+
@raw_result_details << @single_die.result
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
|
|
207
184
|
def name_number_sides_from_hash(options)
|
|
208
185
|
@name = options[:name].to_s
|
|
209
186
|
@ndice = Integer(options[:ndice])
|
|
@@ -213,21 +190,6 @@ module GamesDice
|
|
|
213
190
|
raise ArgumentError, ":sides must be 1 or more, but got #{@sides}" unless @sides.positive?
|
|
214
191
|
end
|
|
215
192
|
|
|
216
|
-
def keep_mode_from_hash(options)
|
|
217
|
-
case options[:keep_mode]
|
|
218
|
-
when nil
|
|
219
|
-
@keep_mode = nil
|
|
220
|
-
when :keep_best
|
|
221
|
-
@keep_mode = :keep_best
|
|
222
|
-
@keep_number = Integer(options[:keep_number] || 1)
|
|
223
|
-
when :keep_worst
|
|
224
|
-
@keep_mode = :keep_worst
|
|
225
|
-
@keep_number = Integer(options[:keep_number] || 1)
|
|
226
|
-
else
|
|
227
|
-
raise ArgumentError, ":keep_mode can be nil, :keep_best or :keep_worst. Got #{options[:keep_mode].inspect}"
|
|
228
|
-
end
|
|
229
|
-
end
|
|
230
|
-
|
|
231
193
|
def complex_die_params_from_hash(options)
|
|
232
194
|
cd_hash = {}
|
|
233
195
|
%i[maps rerolls].each do |k|
|