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.
@@ -21,7 +21,7 @@ VALUE Probabilities = Qnil;
21
21
  // General utils
22
22
  //
23
23
 
24
- inline int max( int *a, int n ) {
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
- inline int min( int *a, int n ) {
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
- inline ProbabilityList *new_basic_pl( int nslots, double iv, int o ) {
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
- inline int pl_min( ProbabilityList *pl ) {
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
- inline int pl_max( ProbabilityList *pl ) {
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
- inline double pl_p_eql( ProbabilityList *pl, int target ) {
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
- inline double pl_p_gt( ProbabilityList *pl, int target ) {
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
- inline double pl_p_lt( ProbabilityList *pl, int target ) {
260
+ static double pl_p_lt( ProbabilityList *pl, int target ) {
259
261
  return pl_p_le( pl, target - 1 );
260
262
  }
261
263
 
262
- inline double pl_p_le( ProbabilityList *pl, int target ) {
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
- inline double pl_p_ge( ProbabilityList *pl, int target ) {
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
- inline double pl_expected( ProbabilityList *pl ) {
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
- inline void clear_pl_array( int k, ProbabilityList **pl_array ) {
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
- inline VALUE pl_as_ruby_class( ProbabilityList *pl, VALUE klass ) {
519
- return Data_Wrap_Struct( klass, 0, destroy_probability_list, pl );
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
- Data_Get_Struct( obj, ProbabilityList, pl );
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 ( TYPE(obj) != T_DATA ||
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 = get_probability_list( gdpa );
778
- ProbabilityList *pl_b = get_probability_list( gdpb );
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( VALUE ParentModule ) {
810
- Probabilities = rb_define_class_under( ParentModule, "Probabilities", rb_cObject );
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( VALUE ParentModule );
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 = 'A library for simulating dice. Use it to construct dice-rolling systems used in role-playing and board games.'
14
- gem.summary = 'Simulates and explains dice rolls from simple "1d6" to complex "roll 7 ten-sided dice, take best 3,
15
- results of 10 roll again and add on".'
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 = '>= 2.6.0'
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
- # Red Carpet renders README.md, and is optional even when developing the gem.
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.add_dependency 'parslet', '>= 1.5.0'
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
@@ -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 for the die
32
- # @option options [#rand] :prng Optional alternative source of randomness to Ruby's built-in #rand, passed to GamesDice::Die's constructor
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
- @result = 0
142
- @raw_result_details = []
148
+ generate_raw_results
149
+ return @result if !@keep_mode || @keep_number.to_i >= @ndice
143
150
 
144
- @ndice.times do
145
- @result += @single_die.roll
146
- @raw_result_details << @single_die.result
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.inject(0) { |so_far, die_result| so_far + die_result }
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
- unused_dice = []
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
- explanation
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|