dicey 0.16.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b590b1e43f4e265f3a8fab12a949de1cdd20cc39b1f4334d997520cdbc9efc4
4
- data.tar.gz: 2c0e3c4577a3f22483296b3a6c238c54937c8b5a360b21c53ca2d0b9aa432fcb
3
+ metadata.gz: 86d934c5cf9455e15c42f84abe397c7c32ada670512969decd4e44c9eef1841f
4
+ data.tar.gz: bcba52a9d0e1bbb0308db1025ba2c723bf963aa02b323b631538bdbc722144e2
5
5
  SHA512:
6
- metadata.gz: bed55910f504316de9f078f938f2a9a56e8dfbe65c3552eee330bc58f7559f699a0906837f4bda30fe098feed58469e8c7039e78864e79f4d263918729222ebb
7
- data.tar.gz: 392b0c5c3b5e547d497092358769260b7b8e1c99bd98f1eb8759c389757386452720a173e619aea5bd2cebe996348c99f314bfc3b1e54fa068583fc083525cd9
6
+ metadata.gz: 46d142b6caed4432f4adede2d34214498e1bbfe02aadca4aaa58709c5fa9ce15d4c71e75ce37f599752c10f334da0acb616c7278ba5076d1ed4746b36fb7b35b
7
+ data.tar.gz: 14ad44e95941c5e7d94c71968a9f84105d1f6a64b50ab551c94ddea247dda77a80e5f83e2566bdea76ec912b799f1a05e3bbb160d0cc1b3af5211c795bc2b415
data/README.md CHANGED
@@ -385,8 +385,8 @@ die.roll
385
385
  ### Distribution calculators
386
386
 
387
387
  Distribution calculators live in `Dicey::SumFrequencyCalculators` module. There are four calculators currently:
388
- - `Dicey::SumFrequencyCalculators::KroneckerSubstitution` is the recommended calculator, able to handle all `Dicey::RegularDie`. It is very fast, calculating distribution for *100d6* in about 0.1 seconds on a laptop.
389
- - `Dicey::SumFrequencyCalculators::MultinomialCoefficients` is specialized for repeated numeric dice, with performance only slightly worse. However, it is currently limited to dice with arithmetic sequences.
388
+ - `Dicey::SumFrequencyCalculators::KroneckerSubstitution` is the recommended calculator, able to handle all `Dicey::RegularDie` and more. It is very fast, though sometimes slower than the next one.
389
+ - `Dicey::SumFrequencyCalculators::MultinomialCoefficients` is specialized for repeated numeric dice, with performance on par with the previous one, depending on exact parameters. However, it is currently limited to dice with arithmetic sequences (this includes regular dice, however).
390
390
  - `Dicey::SumFrequencyCalculators::BruteForce` is the most generic and slowest one, but can work with *any* dice. It needs gem "**vector_number**" to be installed and available to work with non-numeric dice.
391
391
  - `Dicey::SumFrequencyCalculators::Empirical`... this is more of a tool than a calculator. It "calculates" probabilities by performing a large number of rolls and counting frequencies of outcomes. It can be interesting to play around with and see how practical results compare to theoretical ones. Due to its simplicity, it also works with *any* dice.
392
392
 
@@ -396,6 +396,8 @@ Calculators inherit from `Dicey::SumFrequencyCalculators::BaseCalculator` and pr
396
396
 
397
397
  See [Diving deeper](#diving-deeper) for more details on limitations and complexity considerations of different algorithms.
398
398
 
399
+ When in doubt which calculator to use (and if a given one *can* be used), use `Dicey::SumFrequencyCalculators::AutoSelector`. Its `#call(dice)` method will return a valid calculator for the given dice or `nil` if none are acceptable.
400
+
399
401
  ### Distribution properties
400
402
 
401
403
  While distribution itself is already enough in most cases (we are talking just dice here, after all). it may be of interest to calculate properties of it: mode, mean, expected value, standard deviation, etc. `Dicey::DistributionPropertiesCalculator` provides this functionality:
@@ -464,10 +466,10 @@ For a further discussion of calculations, it is important to understand which cl
464
466
  > [!TIP]
465
467
  > šŸ’” If you only need to roll **regular** dice, this section will not contain anything important.
466
468
 
467
- - **Natural** die has sides with only positive integers or 0. For example, (1,2,3,4,5,6), (5,1,6,5), (1,10000), (1,1,1,1,1,1,1,0).
469
+ - **Integer** die has sides with only integers. For example, (1,2,3,4,5,6), (-5,0,5), (1,10000), (1,1,1,1,1,1,1,0).
468
470
  - **Arithmetic** die's sides form an arithmetic sequence. For example, (1,2,3,4,5,6), (1,0,-1), (2.6,2.1,1.6,1.1).
469
471
  - **Numeric** die is limited by having sides confined to ā„ (or ā„‚ if you are feeling particularly adventurous).
470
- - **Abstract** die is not limited by anything other than not having partial sides (and how would that work anyway?).
472
+ - **Abstract** die is unlimited!
471
473
 
472
474
  > [!NOTE]
473
475
  > šŸ’” If your die definition starts with a negative number, it can be bracketed, prefixed with "d", or put after "--" pseudo-argument to avoid processing as an option.
@@ -477,31 +479,40 @@ Dicey is in principle able to handle any real numeric dice and some abstract dic
477
479
  Currently, three algorithms for calculating frequencies are implemented, with different possibilities and trade-offs.
478
480
 
479
481
  > [!NOTE]
480
- > šŸ’” Complexity is listed for **n** dice with at most **m** sides and has not been rigorously proven.
482
+ > šŸ’” Complexity is listed for **n** dice with at most **m** sides and is only an approximation.
481
483
 
482
484
  ### Kronecker substitution
483
485
 
484
486
  An algorithm based on fast polynomial multiplication. This is the default algorithm, used for most reasonable dice.
485
487
 
486
- - Limitations: only **natural** dice are allowed, including **regular** dice.
488
+ - Limitations: only **integer** dice are allowed, including **regular** dice.
487
489
  - Example: `dicey 5 3,4,1 0,`
488
- - Complexity: **O(mā‹…n)** where **m** is the highest value
490
+ - Complexity: **O(n<sup>3</sup>ā‹…m<sup>2</sup>)**
491
+ - Running time examples:
492
+ - 6d1000 — 0.5 seconds
493
+ - 1000d6 — 18 seconds
489
494
 
490
495
  ### Multinomial coefficients
491
496
 
492
- This algorithm is based on raising a univariate polynomial to a power and using the coefficients of the result, though certain restrictions are lifted as they don't actually matter for the calculation.
497
+ This algorithm is based on raising a univariate polynomial to a power and using the coefficients of the result, though certain restrictions are lifted as they don't actually matter for the calculation. It is usually faster than Kronecker substitution for many dice with few sides.
493
498
 
494
499
  - Limitations: only *equal* **arithmetic** dice are allowed.
495
500
  - Example: `dicey 1.5,3,4.5,6 1.5,3,4.5,6 1.5,3,4.5,6`
496
- - Complexity: **O(mā‹…n²)**
501
+ - Complexity: **O(n<sup>2</sup>ā‹…m<sup>2</sup>)**
502
+ - Running time examples:
503
+ - 6d1000 — 1.65 seconds
504
+ - 1000d6 — 10.5 seconds
497
505
 
498
506
  ### Brute force
499
507
 
500
- As a last resort, there is a brute force algorithm which goes through every possible dice roll and adds results together. While quickly growing terrible in performace, it has the largest input space, allowing to work with completely nonsensical dice, including aforementioned dice with complex numbers.
508
+ As a last resort, there is a brute force algorithm which goes through every possible dice roll and adds results together. While quickly growing terrible in performace (and memory usage), it has the largest input space, allowing to work with completely nonsensical dice, including complex numbers and altogether non-numeric values.
501
509
 
502
510
  - Limitations: without **vector_number** all values must be numbers, otherwise almost any values are viable.
503
- - Example: `dicey 5 1,0.1,2 1,-1,1,-1,0`
504
- - Complexity: **O(mⁿ)**
511
+ - Example: `dicey 5 1,0.1,2 A,B,C`
512
+ - Complexity: **O(m<sup>n</sup>)**
513
+ - Running time examples:
514
+ - 6d10 — 0.25 seconds
515
+ - 10d6 — 9.5 seconds
505
516
 
506
517
  ## Development
507
518
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Dicey
4
4
  # Asbtract die which may have an arbitrary list of sides,
5
- # not even neccessarily numbers, but strings or other objects.
5
+ # not even neccessarily numbers but strings or other objects.
6
6
  class AbstractDie
7
7
  # rubocop:disable Style/ClassVars
8
8
 
@@ -10,13 +10,6 @@ module Dicey
10
10
  # Slice and dice everything in the Dicey module to produce a useful result.
11
11
  # This is the entry point for the CLI.
12
12
  class Blender
13
- # List of calculators to use, ordered by efficiency.
14
- ROLL_FREQUENCY_CALCULATORS = [
15
- SumFrequencyCalculators::KroneckerSubstitution.new,
16
- SumFrequencyCalculators::MultinomialCoefficients.new,
17
- SumFrequencyCalculators::BruteForce.new,
18
- ].freeze
19
-
20
13
  # How to transform option values from command-line arguments
21
14
  # to internally significant objects.
22
15
  OPTION_TRANSFORMATIONS = {
@@ -27,6 +20,7 @@ module Dicey
27
20
  "gnuplot" => OutputFormatters::GnuplotFormatter.new,
28
21
  "yaml" => OutputFormatters::YAMLFormatter.new,
29
22
  "json" => OutputFormatters::JSONFormatter.new,
23
+ "null" => OutputFormatters::NullFormatter.new,
30
24
  }.freeze,
31
25
  }.freeze
32
26
 
@@ -47,7 +41,6 @@ module Dicey
47
41
  def call(argv = ARGV)
48
42
  options, arguments = get_options_and_arguments(argv)
49
43
  require_optional_libraries(options)
50
- options[:roll_calculators] = ROLL_FREQUENCY_CALCULATORS
51
44
  return_value = RUNNERS[options.delete(:mode)].call(arguments, **options)
52
45
  print return_value if return_value.is_a?(String)
53
46
  !!return_value
@@ -11,7 +11,7 @@ module Dicey
11
11
  # Allowed result types (--result).
12
12
  RESULT_TYPES = %w[frequencies probabilities].freeze
13
13
  # Allowed output formats (--format).
14
- FORMATS = %w[list gnuplot json yaml].freeze
14
+ FORMATS = %w[list gnuplot json yaml null].freeze
15
15
 
16
16
  # Default values for initial values of the options.
17
17
  DEFAULT_OPTIONS = { mode: "frequencies", format: "list", result: "frequencies" }.freeze
@@ -11,7 +11,7 @@ module Dicey
11
11
  # Convert +value+ to +Integer+ if it's a +Rational+ with denominator of 1.
12
12
  # Otherwise, return +value+ as-is.
13
13
  #
14
- # @value [Numeric, Any]
14
+ # @param value [Numeric, Any]
15
15
  # @return [Numeric, Integer, Any]
16
16
  def rational_to_integer(value)
17
17
  (Rational === value && value.denominator == 1) ? value.numerator : value
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dicey
4
+ # @api private
4
5
  module Mixins
5
6
  # Mix-in for converting dice with non-numeric sides into dice with +VectorNumber+ sides.
6
7
  module VectorizeDice
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dicey
4
+ module OutputFormatters
5
+ # Formatter that doesn't format anything and always returns an empty string.
6
+ class NullFormatter
7
+ # @param hash [Hash{Object => Object}]
8
+ # @param description [String] text to add as a comment.
9
+ # @return [String] always an empty string
10
+ def call(hash, description = nil) # rubocop:disable Lint/UnusedMethodArgument
11
+ ""
12
+ end
13
+ end
14
+ end
15
+ end
data/lib/dicey/roller.rb CHANGED
@@ -1,12 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Try to load "vector_number" pre-emptively.
4
- begin
5
- require "vector_number"
6
- rescue LoadError
7
- # VectorNumber not available, sad
8
- end
9
-
10
3
  require_relative "die_foundry"
11
4
 
12
5
  require_relative "mixins/rational_to_integer"
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "brute_force"
4
+ require_relative "kronecker_substitution"
5
+ require_relative "multinomial_coefficients"
6
+
7
+ module Dicey
8
+ module SumFrequencyCalculators
9
+ # Tool to automatically select a calculator for a given set of dice.
10
+ #
11
+ # Calculator is guaranteed to be compatible, with a strong chance of being the most performant.
12
+ #
13
+ # @see BaseCalculator#heuristic_complexity
14
+ class AutoSelector
15
+ # Calculators to consider when selecting a match.
16
+ AVAILABLE_CALCULATORS = [
17
+ KroneckerSubstitution.new,
18
+ MultinomialCoefficients.new,
19
+ BruteForce.new,
20
+ ].freeze
21
+
22
+ # @param calculators [Array<BaseCalculator>]
23
+ # calculators which this instance will consider
24
+ def initialize(calculators = AVAILABLE_CALCULATORS)
25
+ @calculators = calculators
26
+ end
27
+
28
+ # Determine best (or adequate) calculator for a given set of dice
29
+ # based on heuristics from the list of available calculators.
30
+ #
31
+ # @param dice [Enumerable<NumericDie>]
32
+ # @return [BaseCalculator, nil] +nil+ if no calculator is compatible
33
+ def call(dice)
34
+ compatible = @calculators.select { _1.valid_for?(dice) }
35
+ return if compatible.empty?
36
+
37
+ compatible.min_by { _1.heuristic_complexity(dice) }
38
+ end
39
+ end
40
+ end
41
+ end
@@ -55,6 +55,16 @@ module Dicey
55
55
  dice.is_a?(Enumerable) && dice.all?(AbstractDie) && validate(dice)
56
56
  end
57
57
 
58
+ # Heuristic complexity of the calculator, used to determine best calculator.
59
+ #
60
+ # @see AutoSelector
61
+ #
62
+ # @param dice [Enumerable<AbstractDie>]
63
+ # @return [Integer]
64
+ def heuristic_complexity(dice)
65
+ calculate_heuristic(dice.length, dice.map(&:sides_num).max).to_i
66
+ end
67
+
58
68
  private
59
69
 
60
70
  # Do additional validation on the dice list.
@@ -63,12 +73,19 @@ module Dicey
63
73
  true
64
74
  end
65
75
 
76
+ # Calculate heuristic complexity of the calculator.
77
+ #
78
+ # @param dice_count [Integer]
79
+ # @param sides_count [Integer] maximum number of sides
80
+ # @return [Numeric]
81
+ def calculate_heuristic(dice_count, sides_count)
82
+ raise NotImplementedError
83
+ end
84
+
66
85
  # Peform frequencies calculation.
67
86
  # (see #call)
68
87
  def calculate(dice, **nil)
69
- # :nocov:
70
88
  raise NotImplementedError
71
- # :nocov:
72
89
  end
73
90
 
74
91
  # Check that resulting frequencies actually add up to what they are supposed to be.
@@ -1,12 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Try to load "vector_number" pre-emptively.
4
- begin
5
- require "vector_number"
6
- rescue LoadError
7
- # VectorNumber not available, sad
8
- end
9
-
10
3
  require_relative "base_calculator"
11
4
 
12
5
  require_relative "../mixins/vectorize_dice"
@@ -33,46 +26,16 @@ module Dicey
33
26
  end
34
27
  end
35
28
 
36
- def calculate(dice, **nil)
37
- dice = vectorize_dice(dice) if defined?(VectorNumber)
38
- combine_dice_enumerators(dice.map(&:sides_list)).map(&:sum).tally
29
+ def calculate_heuristic(dice_count, sides_count)
30
+ 1000 * (sides_count**dice_count)
39
31
  end
40
32
 
41
- if defined?(Enumerator::Product)
42
- # Get an enumerator which goes through all possible permutations of dice sides.
43
- #
44
- # @param side_lists [Enumerable<Enumerable<Any>>]
45
- # @return [Enumerator<Enumerable<Enumerable<Any>>>]
46
- def combine_dice_enumerators(side_lists)
47
- Enumerator::Product.new(*side_lists)
48
- end
49
- # :nocov:
50
- else
51
- # Get an enumerator which goes through all possible permutations of dice sides.
52
- #
53
- # @param side_lists [Enumerable<Enumerable<Any>>]
54
- # @return [Enumerator<Array<Array<Any>>>]
55
- def combine_dice_enumerators(side_lists)
56
- product(side_lists)
57
- end
58
-
59
- # Simplified implementation of {Enumerator::Product}.
60
- # Adapted from {https://bugs.ruby-lang.org/issues/18685#note-10}.
61
- #
62
- # @param enums [Enumerable<Enumerable<Any>>]
63
- # @return [Enumerator<Array<Array<Any>>>]
64
- def product(enums, &block)
65
- return to_enum(__method__, enums) unless block_given?
66
-
67
- enums
68
- .reverse
69
- .reduce(block) { |inner, enum|
70
- ->(values) { enum.each_entry { inner.call([*values, _1]) } }
71
- }
72
- .call([])
73
- end
33
+ def calculate(dice, **nil)
34
+ dice = vectorize_dice(dice) if defined?(VectorNumber)
35
+ dice.map(&:sides_list).reduce { |result, die|
36
+ result.flat_map { |roll| die.map { |side| roll + side } }
37
+ }.tally
74
38
  end
75
- # :nocov:
76
39
  end
77
40
  end
78
41
  end
@@ -1,12 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # Try to load "vector_number" pre-emptively.
4
- begin
5
- require "vector_number"
6
- rescue LoadError
7
- # VectorNumber not available, sad
8
- end
9
-
10
3
  require_relative "base_calculator"
11
4
 
12
5
  require_relative "../mixins/vectorize_dice"
@@ -45,6 +38,10 @@ module Dicey
45
38
  end
46
39
  end
47
40
 
41
+ def calculate_heuristic(dice_count, sides_count)
42
+ N * dice_count * Math.log2(sides_count)
43
+ end
44
+
48
45
  def calculate(dice, rolls: N)
49
46
  dice = vectorize_dice(dice) if defined?(VectorNumber)
50
47
  statistics = rolls.times.with_object(Hash.new(0)) { |_, hash| hash[dice.sum(&:roll)] += 1 }
@@ -4,9 +4,9 @@ require_relative "base_calculator"
4
4
 
5
5
  module Dicey
6
6
  module SumFrequencyCalculators
7
- # Calculator for lists of dice with non-negative integer sides (fast).
7
+ # Calculator for lists of dice with integer sides (fast).
8
8
  #
9
- # Example dice: (1,2,3,4), (0,1,5,6), (5,4,5,4,5).
9
+ # Example dice: (1,2,3,4), (0,1,-5,6), (5,4,5,4,5).
10
10
  #
11
11
  # Based on Kronecker substitution method for polynomial multiplication.
12
12
  # @see https://en.wikipedia.org/wiki/Kronecker_substitution
@@ -17,15 +17,19 @@ module Dicey
17
17
  private
18
18
 
19
19
  def validate(dice)
20
- dice.all? { |die| die.sides_list.all? { _1.is_a?(Integer) && _1 >= 0 } }
20
+ dice.all? { |die| die.sides_list.all?(Integer) }
21
+ end
22
+
23
+ def calculate_heuristic(dice_count, sides_count)
24
+ (dice_count**3.2) * 100 * (sides_count**1.9)
21
25
  end
22
26
 
23
27
  def calculate(dice, **nil)
24
- polynomials = build_polynomials(dice)
28
+ polynomials, offset = build_polynomials(dice)
25
29
  evaluation_point = find_evaluation_point(polynomials)
26
30
  values = evaluate_polynomials(polynomials, evaluation_point)
27
31
  product = values.reduce(:*)
28
- extract_coefficients(product, evaluation_point)
32
+ extract_coefficients(product, evaluation_point, offset, polynomials.count)
29
33
  end
30
34
 
31
35
  # Turn dice into hashes where keys are side values and values are numbers of those sides,
@@ -35,7 +39,8 @@ module Dicey
35
39
  # @param dice [Enumerable<NumericDie>]
36
40
  # @return [Array<Hash{Integer => Integer}>]
37
41
  def build_polynomials(dice)
38
- dice.map { _1.sides_list.tally }
42
+ minimum = dice.map { |die| die.sides_list.min }.min
43
+ [dice.map { |die| die.sides_list.map { _1 - minimum }.tally }, minimum]
39
44
  end
40
45
 
41
46
  # Find a power of 2 which is larger in magnitude than any resulting polynomial coefficients,
@@ -67,13 +72,15 @@ module Dicey
67
72
  #
68
73
  # @param product [Integer]
69
74
  # @param evaluation_point [Integer]
75
+ # @param offset [Integer]
76
+ # @param number_of_dice [Integer]
70
77
  # @return [Hash{Integer => Integer}]
71
- def extract_coefficients(product, evaluation_point)
78
+ def extract_coefficients(product, evaluation_point, offset, number_of_dice)
72
79
  window = evaluation_point - 1
73
80
  window_shift = window.bit_length
74
81
  (0..).each_with_object({}) do |power, result|
75
82
  coefficient = product & window
76
- result[power] = coefficient unless coefficient.zero?
83
+ result[power + (offset * number_of_dice)] = coefficient unless coefficient.zero?
77
84
  product >>= window_shift
78
85
  break result if product.zero?
79
86
  end
@@ -34,6 +34,12 @@ module Dicey
34
34
  true
35
35
  end
36
36
 
37
+ def calculate_heuristic(dice_count, sides_count)
38
+ # Fitting shows both coefficients to be around 500,
39
+ # but empirical runtime doesn't agree, so 150 it is.
40
+ 150 * (dice_count**2.2) * 500 * (sides_count**1.9)
41
+ end
42
+
37
43
  def calculate(dice, **nil)
38
44
  first_die = dice.first
39
45
  number_of_sides = first_die.sides_num
@@ -72,7 +78,7 @@ module Dicey
72
78
  # @return [Array<Integer>]
73
79
  def next_row_of_coefficients(row_index, window_size, previous_row)
74
80
  length = (row_index * window_size) + 1
75
- (0..length).map do |col_index|
81
+ (0...length).map do |col_index|
76
82
  # Have to clamp to 0 to prevent accessing array from the end.
77
83
  # BUG: TruffleRuby can't handle endless range in #clamp (see https://github.com/oracle/truffleruby/issues/3945)
78
84
  window_range = ((col_index - window_size).clamp(0..col_index)..col_index)
@@ -13,17 +13,18 @@ module Dicey
13
13
  # Transform die definitions to roll frequencies.
14
14
  #
15
15
  # @param arguments [Array<String>] die definitions
16
- # @param roll_calculators [Array<BaseCalculator>] list of calculators to use
17
16
  # @param format [#call] formatter for output
18
17
  # @param result [Symbol] result type selector
19
18
  # @return [nil]
20
19
  # @raise [DiceyError]
21
- def call(arguments, roll_calculators:, format:, result:, **)
20
+ def call(arguments, format:, result:, **)
22
21
  raise DiceyError, "no dice!" if arguments.empty?
23
22
 
24
23
  dice = arguments.flat_map { |definition| die_foundry.cast(definition) }
25
- frequencies = roll_calculators.find { _1.valid_for?(dice) }&.call(dice, result_type: result)
26
- raise DiceyError, "no calculator could handle these dice!" unless frequencies
24
+ calculator = calculator_selector.call(dice)
25
+ raise DiceyError, "no calculator could handle these dice!" unless calculator
26
+
27
+ frequencies = calculator.call(dice, result_type: result)
27
28
 
28
29
  format.call(frequencies, AbstractDie.describe(dice))
29
30
  end
@@ -33,6 +34,10 @@ module Dicey
33
34
  def die_foundry
34
35
  @die_foundry ||= DieFoundry.new
35
36
  end
37
+
38
+ def calculator_selector
39
+ @calculator_selector ||= AutoSelector.new
40
+ end
36
41
  end
37
42
  end
38
43
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "auto_selector"
3
4
  require_relative "brute_force"
4
5
  require_relative "kronecker_substitution"
5
6
  require_relative "multinomial_coefficients"
@@ -8,6 +9,8 @@ module Dicey
8
9
  module SumFrequencyCalculators
9
10
  # A simple testing facility for roll frequency calculators.
10
11
  class TestRunner
12
+ AVAILABLE_CALCULATORS = AutoSelector::AVAILABLE_CALCULATORS
13
+
11
14
  # These are manually calculated frequencies,
12
15
  # with test cases for pretty much all variations of what this program can handle.
13
16
  TEST_DATA = [
@@ -70,12 +73,11 @@ module Dicey
70
73
 
71
74
  # Check all tests defined in {TEST_DATA} with every passed calculator.
72
75
  #
73
- # @param roll_calculators [Array<BaseCalculator>]
74
76
  # @param report_style [Symbol] one of: +:full+, +:quiet+;
75
77
  # +:quiet+ style does not output any text
76
78
  # @return [Boolean] whether there are no failing tests
77
- def call(*, roll_calculators:, report_style:, **)
78
- results = TEST_DATA.to_h { |test| run_test(test, roll_calculators) }
79
+ def call(*, report_style:, **)
80
+ results = TEST_DATA.to_h { |test| run_test(test) }
79
81
  full_report(results) if report_style == :full
80
82
  results.values.none? do |test_result|
81
83
  test_result.values.any? { FAILURE_RESULTS.include?(_1) }
@@ -86,13 +88,12 @@ module Dicey
86
88
 
87
89
  # @param test [Array(Array<Integer, Array<Numeric>>, Hash{Numeric => Integer})]
88
90
  # pair of a dice list definition and expected results
89
- # @param calculators [Array<BaseCalculator>]
90
91
  # @return [Array(Array<NumericDie>, Hash{BaseCalculator => Symbol})]
91
92
  # result of running the test in a format suitable for +#to_h+
92
- def run_test(test, calculators)
93
+ def run_test(test)
93
94
  dice = build_dice(test.first)
94
95
  test_result =
95
- calculators.each_with_object({}) do |calculator, hash|
96
+ AVAILABLE_CALCULATORS.each_with_object({}) do |calculator, hash|
96
97
  hash[calculator] = run_test_on_calculator(calculator, dice, test.last)
97
98
  end
98
99
  [dice, test_result]
data/lib/dicey/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dicey
4
- VERSION = "0.16.0"
4
+ VERSION = "0.16.1"
5
5
  end
data/lib/dicey.rb CHANGED
@@ -1,5 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ # Try to load "vector_number" pre-emptively.
4
+ begin
5
+ require "vector_number"
6
+ rescue LoadError
7
+ # VectorNumber not available, sad
8
+ end
9
+
3
10
  # A library for rolling dice and calculating roll frequencies.
4
11
  module Dicey
5
12
  # General error for Dicey.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dicey
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.16.0
4
+ version: 0.16.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alexandr Bulancov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-10-09 00:00:00.000000000 Z
11
+ date: 2025-10-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: vector_number
@@ -58,9 +58,11 @@ files:
58
58
  - lib/dicey/output_formatters/json_formatter.rb
59
59
  - lib/dicey/output_formatters/key_value_formatter.rb
60
60
  - lib/dicey/output_formatters/list_formatter.rb
61
+ - lib/dicey/output_formatters/null_formatter.rb
61
62
  - lib/dicey/output_formatters/yaml_formatter.rb
62
63
  - lib/dicey/regular_die.rb
63
64
  - lib/dicey/roller.rb
65
+ - lib/dicey/sum_frequency_calculators/auto_selector.rb
64
66
  - lib/dicey/sum_frequency_calculators/base_calculator.rb
65
67
  - lib/dicey/sum_frequency_calculators/brute_force.rb
66
68
  - lib/dicey/sum_frequency_calculators/empirical.rb
@@ -75,9 +77,9 @@ licenses:
75
77
  metadata:
76
78
  homepage_uri: https://github.com/trinistr/dicey
77
79
  bug_tracker_uri: https://github.com/trinistr/dicey/issues
78
- documentation_uri: https://rubydoc.info/gems/dicey/0.16.0
79
- source_code_uri: https://github.com/trinistr/dicey/tree/v0.16.0
80
- changelog_uri: https://github.com/trinistr/dicey/blob/v0.16.0/CHANGELOG.md
80
+ documentation_uri: https://rubydoc.info/gems/dicey/0.16.1
81
+ source_code_uri: https://github.com/trinistr/dicey/tree/v0.16.1
82
+ changelog_uri: https://github.com/trinistr/dicey/blob/v0.16.1/CHANGELOG.md
81
83
  rubygems_mfa_required: 'true'
82
84
  post_install_message:
83
85
  rdoc_options: