dicey 0.16.2 → 0.17.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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +50 -34
  3. data/exe/dicey +2 -2
  4. data/lib/dicey/abstract_die.rb +24 -5
  5. data/lib/dicey/cli/blender.rb +19 -14
  6. data/lib/dicey/{sum_frequency_calculators/runner.rb → cli/calculator_runner.rb} +9 -15
  7. data/lib/dicey/{sum_frequency_calculators/test_runner.rb → cli/calculator_test_runner.rb} +37 -13
  8. data/lib/dicey/cli/formatters/base_list_formatter.rb +36 -0
  9. data/lib/dicey/cli/formatters/base_map_formatter.rb +38 -0
  10. data/lib/dicey/cli/formatters/gnuplot_formatter.rb +28 -0
  11. data/lib/dicey/cli/formatters/json_formatter.rb +14 -0
  12. data/lib/dicey/cli/formatters/list_formatter.rb +14 -0
  13. data/lib/dicey/cli/formatters/null_formatter.rb +17 -0
  14. data/lib/dicey/cli/formatters/yaml_formatter.rb +14 -0
  15. data/lib/dicey/cli/options.rb +15 -11
  16. data/lib/dicey/cli/roller.rb +47 -0
  17. data/lib/dicey/cli.rb +23 -0
  18. data/lib/dicey/die_foundry.rb +3 -2
  19. data/lib/dicey/distribution_calculators/auto_selector.rb +73 -0
  20. data/lib/dicey/{sum_frequency_calculators → distribution_calculators}/base_calculator.rb +44 -37
  21. data/lib/dicey/distribution_calculators/binomial.rb +62 -0
  22. data/lib/dicey/{sum_frequency_calculators → distribution_calculators}/empirical.rb +9 -10
  23. data/lib/dicey/distribution_calculators/iterative.rb +51 -0
  24. data/lib/dicey/{sum_frequency_calculators → distribution_calculators}/multinomial_coefficients.rb +21 -12
  25. data/lib/dicey/{sum_frequency_calculators/kronecker_substitution.rb → distribution_calculators/polynomial_convolution.rb} +15 -8
  26. data/lib/dicey/distribution_calculators/trivial.rb +56 -0
  27. data/lib/dicey/distribution_properties_calculator.rb +5 -2
  28. data/lib/dicey/mixins/missing_math.rb +44 -0
  29. data/lib/dicey/mixins/rational_to_integer.rb +1 -0
  30. data/lib/dicey/mixins/vectorize_dice.rb +17 -12
  31. data/lib/dicey/mixins/warn_about_vector_number.rb +19 -0
  32. data/lib/dicey/numeric_die.rb +1 -1
  33. data/lib/dicey/version.rb +1 -1
  34. data/lib/dicey.rb +26 -5
  35. metadata +30 -26
  36. data/lib/dicey/output_formatters/gnuplot_formatter.rb +0 -24
  37. data/lib/dicey/output_formatters/hash_formatter.rb +0 -36
  38. data/lib/dicey/output_formatters/json_formatter.rb +0 -12
  39. data/lib/dicey/output_formatters/key_value_formatter.rb +0 -34
  40. data/lib/dicey/output_formatters/list_formatter.rb +0 -12
  41. data/lib/dicey/output_formatters/null_formatter.rb +0 -15
  42. data/lib/dicey/output_formatters/yaml_formatter.rb +0 -12
  43. data/lib/dicey/roller.rb +0 -46
  44. data/lib/dicey/sum_frequency_calculators/auto_selector.rb +0 -41
  45. data/lib/dicey/sum_frequency_calculators/brute_force.rb +0 -41
@@ -3,17 +3,24 @@
3
3
  require_relative "base_calculator"
4
4
 
5
5
  module Dicey
6
- module SumFrequencyCalculators
7
- # Calculator for lists of dice with integer sides (fast).
6
+ module DistributionCalculators
7
+ # Calculator for lists of dice with integer sides (fast),
8
+ # using polynomial convolution.
8
9
  #
9
10
  # Example dice: (1,2,3,4), (0,1,-5,6), (5,4,5,4,5).
10
11
  #
11
- # Based on Kronecker substitution method for polynomial multiplication.
12
+ # Discrete random variables can be represented as polynomials,
13
+ # while probability mass function of a sum of independent random variables
14
+ # is a convolution of their probability mass functions,
15
+ # which corresponds to multiplication of polynomials.
16
+ #
17
+ # Uses Kronecker substitution method for polynomial multiplication.
18
+ # @see https://en.wikipedia.org/wiki/Convolution#Discrete_convolution
12
19
  # @see https://en.wikipedia.org/wiki/Kronecker_substitution
13
20
  # @see https://arxiv.org/pdf/0712.4046v1.pdf
14
21
  # David Harvey, Faster polynomial multiplication via multi-point Kronecker substitution
15
22
  # (in particular section 3)
16
- class KroneckerSubstitution < BaseCalculator
23
+ class PolynomialConvolution < BaseCalculator
17
24
  private
18
25
 
19
26
  def validate(dice)
@@ -21,7 +28,7 @@ module Dicey
21
28
  end
22
29
 
23
30
  def calculate_heuristic(dice_count, sides_count)
24
- (dice_count**3.2) * 100 * (sides_count**1.9)
31
+ (2 * dice_count**3 - 4_250_000) + (26 * sides_count**2 + 230_000)
25
32
  end
26
33
 
27
34
  def calculate(dice, **nil)
@@ -32,9 +39,9 @@ module Dicey
32
39
  extract_coefficients(product, evaluation_point, offset, polynomials.count)
33
40
  end
34
41
 
35
- # Turn dice into hashes where keys are side values and values are numbers of those sides,
42
+ # Turn dice into hashes where keys are side values and values are counts of those sides,
36
43
  # representing corresponding polynomials where
37
- # side values are powers and numbers are coefficients.
44
+ # side values are powers and counts are coefficients.
38
45
  #
39
46
  # @param dice [Enumerable<NumericDie>]
40
47
  # @return [Array<Hash{Integer => Integer}>]
@@ -56,7 +63,7 @@ module Dicey
56
63
  1 << coefficient_magnitude
57
64
  end
58
65
 
59
- # Get values of polynomials if +evaluation_point+ is substituted for the variable.
66
+ # Calculate values of polynomials at +evaluation_point+.
60
67
  #
61
68
  # @param polynomials [Array<Hash{Integer => Integer}>]
62
69
  # @param evaluation_point [Integer]
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "base_calculator"
4
+
5
+ require_relative "../mixins/vectorize_dice"
6
+
7
+ module Dicey
8
+ module DistributionCalculators
9
+ # Distribution calculator with fast paths for some trivial cases (very fast).
10
+ #
11
+ # Currently included cases:
12
+ # - single {AbstractDie}, even without +VectorNumber+ (categorical distribution),
13
+ # - two of the same {RegularDie} (simple multinomial distribution).
14
+ #
15
+ # You probably shouldn't use this one manually, it's mostly there for {AutoSelector}.
16
+ class Trivial < BaseCalculator
17
+ include Mixins::VectorizeDice
18
+
19
+ private
20
+
21
+ def validate(dice)
22
+ dice.size == 1 || two_regular_dice?(dice)
23
+ end
24
+
25
+ def two_regular_dice?(dice)
26
+ dice.size == 2 && RegularDie === dice.first && dice.first == dice.last
27
+ end
28
+
29
+ def calculate_heuristic(_dice_count, sides_count)
30
+ -5_000_000 + (328 * sides_count - 89800)
31
+ end
32
+
33
+ def calculate(dice, **nil)
34
+ die = dice.first
35
+ if dice.size == 1
36
+ categorical(die)
37
+ else
38
+ bimultinomial(die)
39
+ end
40
+ end
41
+
42
+ def categorical(die)
43
+ die = vectorize_dice(die)
44
+ die.sides_list.tally
45
+ end
46
+
47
+ # Simplest multinomial distribution: two regular dice.
48
+ def bimultinomial(die)
49
+ middle = die.sides_num
50
+ (1...(die.sides_num * 2)).each_with_object({}) do |i, hash|
51
+ hash[i + 1] = middle - (middle - i).abs
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -11,8 +11,11 @@ module Dicey
11
11
  # - mode(s), median, arithmetic mean;
12
12
  # - important moments (expected value, variance, skewness, kurtosis).
13
13
  #
14
- # It is notable that most dice create symmetric distributions,
15
- # which means that skewness is 0, while properties denoting center in some way
14
+ # Distributions are assumed to be complete populations,
15
+ # i.e. this class is unsuitable for samples.
16
+ #
17
+ # It is notable that common dice create symmetric distributions,
18
+ # which means that skewness is 0, while measures of central tendency
16
19
  # (median, mean, ...) are all equal.
17
20
  # Mode is often not unique, but includes this center.
18
21
  class DistributionPropertiesCalculator
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dicey
4
+ module Mixins
5
+ # @api private
6
+ # Some math functions missing from Math, though without argument range checks.
7
+ module MissingMath
8
+ module_function
9
+
10
+ # Calculate number of combinations of +n+ elements taken +k+ at a time.
11
+ #
12
+ # Unlike plain binomial coefficients, combinations are defined as 0 for +k > n+.
13
+ #
14
+ # @param n [Integer] natural integer
15
+ # @param k [Integer] natural integer
16
+ # @return [Integer]
17
+ def combinations(n, k) # rubocop:disable Naming/MethodParameterName
18
+ return 0 if k > n
19
+ return 1 if k.zero? || k == n
20
+
21
+ factorial_quo(n, k) / factorial(n - k)
22
+ end
23
+
24
+ # Calculate factorial of a natural number.
25
+ #
26
+ # @param n [Integer] natural integer
27
+ # @return [Integer]
28
+ def factorial(n) # rubocop:disable Naming/MethodParameterName
29
+ (n < 23) ? Math.gamma(n + 1).to_i : (1..n).reduce(:*)
30
+ end
31
+
32
+ # Calculate +n! / k!+ where +n >= k+.
33
+ #
34
+ # @param n [Integer] natural integer
35
+ # @param k [Integer] natural integer
36
+ # @return [Integer]
37
+ def factorial_quo(n, k) # rubocop:disable Naming/MethodParameterName
38
+ return Math.gamma(n + 1).to_i / Math.gamma(k + 1).to_i if n < 23 && k < 23
39
+
40
+ ((k + 1)..n).reduce(:*)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -4,6 +4,7 @@ module Dicey
4
4
  # @api private
5
5
  # Various mixins with shared methods.
6
6
  module Mixins
7
+ # @api private
7
8
  # Mix-in for converting rationals with denominator of 1 to integers.
8
9
  module RationalToInteger
9
10
  private
@@ -1,8 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dicey
4
- # @api private
5
4
  module Mixins
5
+ # @api private
6
6
  # Mix-in for converting dice with non-numeric sides into dice with +VectorNumber+ sides.
7
7
  module VectorizeDice
8
8
  private
@@ -10,20 +10,25 @@ module Dicey
10
10
  # Vectorize non-numeric sides for AbstractDie instances,
11
11
  # leaving NumericDie instances unchanged.
12
12
  #
13
- # Check for VectorNumber availability *before* calling.
13
+ # If +VectorNumber+ is not available, returns the original dice.
14
14
  #
15
- # @param dice [Array<AbstractDie>]
16
- # @return [Array<AbstractDie>] a new array of dice
15
+ # @param dice [Array<AbstractDie>, AbstractDie]
16
+ # @return [Array<AbstractDie>, AbstractDie] dice with vectorized sides
17
17
  def vectorize_dice(dice)
18
- dice.map do |die|
19
- next die if NumericDie === die
18
+ return dice unless defined?(VectorNumber)
19
+ return vectorize_die_sides(dice) if AbstractDie === dice
20
20
 
21
- sides =
22
- die.sides_list.map do |side|
23
- (Numeric === side || VectorNumber === side) ? side : VectorNumber.new([side])
24
- end
25
- die.class.new(sides)
26
- end
21
+ dice.map { vectorize_die_sides(_1) }
22
+ end
23
+
24
+ def vectorize_die_sides(die)
25
+ return die if NumericDie === die
26
+
27
+ die.class.new(
28
+ die.sides_list.map do |side|
29
+ (Numeric === side || VectorNumber === side) ? side : VectorNumber.new([side])
30
+ end
31
+ )
27
32
  end
28
33
  end
29
34
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dicey
4
+ module Mixins
5
+ # @api private
6
+ # Mix-in for warning about missing VectorNumber gem.
7
+ module WarnAboutVectorNumber
8
+ private
9
+
10
+ def warn_about_vector_number
11
+ warn <<~TEXT
12
+ Dice with non-numeric sides need gem "vector_number" to be present and available.
13
+ If this is intended, please install the gem.
14
+ TEXT
15
+ false
16
+ end
17
+ end
18
+ end
19
+ end
@@ -7,7 +7,7 @@ module Dicey
7
7
  #
8
8
  # The only inherent difference in behavior compared to {AbstractDie} is
9
9
  # that this class checks values for sides on initialization.
10
- # However, other classes may reject {AbstractDie} even with all numeric sides.
10
+ # {AbstractDie} may be rejected where only numeric dice are expected.
11
11
  class NumericDie < AbstractDie
12
12
  # @param sides_list [Array<Numeric>, Range<Numeric>, Enumerable<Numeric>]
13
13
  # @raise [DiceyError] if +sides_list+ contains non-numerical values or is empty
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.2"
4
+ VERSION = "0.17.0"
5
5
  end
data/lib/dicey.rb CHANGED
@@ -7,13 +7,34 @@ rescue LoadError
7
7
  # VectorNumber not available, sad
8
8
  end
9
9
 
10
- # A library for rolling dice and calculating roll frequencies.
10
+ # A library for calculating roll distributions and rolling dice.
11
+ #
12
+ # Includes several classes of dice:
13
+ # - {AbstractDie}, the base and most generic class;
14
+ # - {NumericDie}, a subclass for strictly numeric dice;
15
+ # - {RegularDie}, for the most common dice.
16
+ #
17
+ # See {AbstractDie} for API and more information.
18
+ #
19
+ # Roll distributions can be calculated via one of several algorithms
20
+ # in {DistributionCalculators},
21
+ # with automatic selection available via {DistributionCalculators::AutoSelector}.
22
+ #
23
+ # There are also a couple of utility classes:
24
+ # - {DistributionPropertiesCalculator} for analyzing a distribution;
25
+ # - {DieFoundry} for creating dice from strings.
11
26
  module Dicey
12
27
  # General error for Dicey.
13
28
  class DiceyError < StandardError; end
14
29
 
15
- Dir["dicey/mixins/*.rb", base: __dir__].each { require_relative _1 }
16
- Dir["dicey/*.rb", base: __dir__].each { require_relative _1 }
17
- Dir["dicey/output_formatters/*.rb", base: __dir__].each { require_relative _1 }
18
- Dir["dicey/sum_frequency_calculators/*.rb", base: __dir__].each { require_relative _1 }
30
+ require_relative "dicey/abstract_die"
31
+ require_relative "dicey/numeric_die"
32
+ require_relative "dicey/regular_die"
33
+
34
+ require_relative "dicey/die_foundry"
35
+
36
+ require_relative "dicey/distribution_properties_calculator"
37
+ Dir["dicey/distribution_calculators/*.rb", base: __dir__].each { require_relative _1 }
38
+
39
+ require_relative "dicey/version"
19
40
  end
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.2
4
+ version: 0.17.0
5
5
  platform: ruby
6
6
  authors:
7
- - Alexandr Bulancov
7
+ - Alexander Bulancov
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-10-10 00:00:00.000000000 Z
11
+ date: 2025-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: vector_number
@@ -26,8 +26,8 @@ dependencies:
26
26
  version: 0.4.3
27
27
  description: |
28
28
  Dicey provides a CLI executable and a Ruby API for fast calculation of
29
- frequency/probability distributions of dice rolls,
30
- with support for all kinds of numeric dice, even Complex ones!
29
+ distribution of weights or probabilities of dice rolls,
30
+ with support for all kinds of numeric dice, and non-numeric ones too!
31
31
  Results can be exported as JSON, YAML or a gnuplot data file.
32
32
 
33
33
  It can also be used to roll dice. While not the primary focus,
@@ -46,30 +46,35 @@ files:
46
46
  - exe/dicey-to-gnuplot
47
47
  - lib/dicey.rb
48
48
  - lib/dicey/abstract_die.rb
49
+ - lib/dicey/cli.rb
49
50
  - lib/dicey/cli/blender.rb
51
+ - lib/dicey/cli/calculator_runner.rb
52
+ - lib/dicey/cli/calculator_test_runner.rb
53
+ - lib/dicey/cli/formatters/base_list_formatter.rb
54
+ - lib/dicey/cli/formatters/base_map_formatter.rb
55
+ - lib/dicey/cli/formatters/gnuplot_formatter.rb
56
+ - lib/dicey/cli/formatters/json_formatter.rb
57
+ - lib/dicey/cli/formatters/list_formatter.rb
58
+ - lib/dicey/cli/formatters/null_formatter.rb
59
+ - lib/dicey/cli/formatters/yaml_formatter.rb
50
60
  - lib/dicey/cli/options.rb
61
+ - lib/dicey/cli/roller.rb
51
62
  - lib/dicey/die_foundry.rb
63
+ - lib/dicey/distribution_calculators/auto_selector.rb
64
+ - lib/dicey/distribution_calculators/base_calculator.rb
65
+ - lib/dicey/distribution_calculators/binomial.rb
66
+ - lib/dicey/distribution_calculators/empirical.rb
67
+ - lib/dicey/distribution_calculators/iterative.rb
68
+ - lib/dicey/distribution_calculators/multinomial_coefficients.rb
69
+ - lib/dicey/distribution_calculators/polynomial_convolution.rb
70
+ - lib/dicey/distribution_calculators/trivial.rb
52
71
  - lib/dicey/distribution_properties_calculator.rb
72
+ - lib/dicey/mixins/missing_math.rb
53
73
  - lib/dicey/mixins/rational_to_integer.rb
54
74
  - lib/dicey/mixins/vectorize_dice.rb
75
+ - lib/dicey/mixins/warn_about_vector_number.rb
55
76
  - lib/dicey/numeric_die.rb
56
- - lib/dicey/output_formatters/gnuplot_formatter.rb
57
- - lib/dicey/output_formatters/hash_formatter.rb
58
- - lib/dicey/output_formatters/json_formatter.rb
59
- - lib/dicey/output_formatters/key_value_formatter.rb
60
- - lib/dicey/output_formatters/list_formatter.rb
61
- - lib/dicey/output_formatters/null_formatter.rb
62
- - lib/dicey/output_formatters/yaml_formatter.rb
63
77
  - lib/dicey/regular_die.rb
64
- - lib/dicey/roller.rb
65
- - lib/dicey/sum_frequency_calculators/auto_selector.rb
66
- - lib/dicey/sum_frequency_calculators/base_calculator.rb
67
- - lib/dicey/sum_frequency_calculators/brute_force.rb
68
- - lib/dicey/sum_frequency_calculators/empirical.rb
69
- - lib/dicey/sum_frequency_calculators/kronecker_substitution.rb
70
- - lib/dicey/sum_frequency_calculators/multinomial_coefficients.rb
71
- - lib/dicey/sum_frequency_calculators/runner.rb
72
- - lib/dicey/sum_frequency_calculators/test_runner.rb
73
78
  - lib/dicey/version.rb
74
79
  homepage: https://github.com/trinistr/dicey
75
80
  licenses:
@@ -77,9 +82,9 @@ licenses:
77
82
  metadata:
78
83
  homepage_uri: https://github.com/trinistr/dicey
79
84
  bug_tracker_uri: https://github.com/trinistr/dicey/issues
80
- documentation_uri: https://rubydoc.info/gems/dicey/0.16.2
81
- source_code_uri: https://github.com/trinistr/dicey/tree/v0.16.2
82
- changelog_uri: https://github.com/trinistr/dicey/blob/v0.16.2/CHANGELOG.md
85
+ documentation_uri: https://rubydoc.info/gems/dicey/0.17.0
86
+ source_code_uri: https://github.com/trinistr/dicey/tree/v0.17.0
87
+ changelog_uri: https://github.com/trinistr/dicey/blob/v0.17.0/CHANGELOG.md
83
88
  rubygems_mfa_required: 'true'
84
89
  post_install_message:
85
90
  rdoc_options:
@@ -101,6 +106,5 @@ requirements: []
101
106
  rubygems_version: 3.5.22
102
107
  signing_key:
103
108
  specification_version: 4
104
- summary: Calculator for dice roll frequency/probability distributions. Also rolls
105
- dice.
109
+ summary: Dice roll weights/probabilities distribution calculator. Also rolls dice.
106
110
  test_files: []
@@ -1,24 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "key_value_formatter"
4
-
5
- module Dicey
6
- module OutputFormatters
7
- # Formats a hash as a text file suitable for consumption by Gnuplot.
8
- #
9
- # Will transform Rational probabilities to Floats.
10
- class GnuplotFormatter < KeyValueFormatter
11
- SEPARATOR = " "
12
-
13
- private
14
-
15
- def transform(key, value)
16
- [derationalize(key), derationalize(value)]
17
- end
18
-
19
- def derationalize(value)
20
- value.is_a?(Rational) ? value.to_f : value
21
- end
22
- end
23
- end
24
- end
@@ -1,36 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Dicey
4
- # Processors which turn data to text.
5
- module OutputFormatters
6
- # Base formatter for outputting in formats which can be converted from a Hash directly.
7
- # Can add an optional description into the result.
8
- # @abstract
9
- class HashFormatter
10
- # @param hash [Hash{Object => Object}]
11
- # @param description [String] text to add to result as an extra key
12
- # @return [String]
13
- def call(hash, description = nil)
14
- hash = hash.transform_keys { to_primitive(_1) }
15
- hash.transform_values! { to_primitive(_1) }
16
- output = {}
17
- output["description"] = description if description
18
- output["results"] = hash
19
- output.public_send(self.class::METHOD)
20
- end
21
-
22
- private
23
-
24
- def to_primitive(value)
25
- return value if primitive?(value)
26
- return value.to_f if Numeric === value
27
-
28
- value.to_s
29
- end
30
-
31
- def primitive?(value)
32
- value.is_a?(Integer) || value.is_a?(Float) || value.is_a?(String)
33
- end
34
- end
35
- end
36
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "hash_formatter"
4
-
5
- module Dicey
6
- module OutputFormatters
7
- # Formats a hash as a JSON document under +results+ key, with optional +description+ key.
8
- class JSONFormatter < HashFormatter
9
- METHOD = :to_json
10
- end
11
- end
12
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "../mixins/rational_to_integer"
4
-
5
- module Dicey
6
- module OutputFormatters
7
- # Base formatter for outputting lists of key-value pairs separated by newlines.
8
- # Can add an optional description into the result.
9
- # @abstract
10
- class KeyValueFormatter
11
- include Mixins::RationalToInteger
12
-
13
- # @param hash [Hash{Object => Object}]
14
- # @param description [String] text to add as a comment.
15
- # @return [String]
16
- def call(hash, description = nil)
17
- initial_string = description ? "# #{description}\n" : +""
18
- hash.each_with_object(initial_string) do |(key, value), output|
19
- output << line(transform(key, value)) << "\n"
20
- end
21
- end
22
-
23
- private
24
-
25
- def transform(key, value)
26
- [rational_to_integer(key), rational_to_integer(value)]
27
- end
28
-
29
- def line((key, value))
30
- "#{key}#{self.class::SEPARATOR}#{value}"
31
- end
32
- end
33
- end
34
- end
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "key_value_formatter"
4
-
5
- module Dicey
6
- module OutputFormatters
7
- # Formats a hash as list of key => value pairs, similar to a Ruby Hash.
8
- class ListFormatter < KeyValueFormatter
9
- SEPARATOR = " => "
10
- end
11
- end
12
- end
@@ -1,15 +0,0 @@
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
@@ -1,12 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "hash_formatter"
4
-
5
- module Dicey
6
- module OutputFormatters
7
- # Formats a hash as a YAML document under +results+ key, with optional +description+ key.
8
- class YAMLFormatter < HashFormatter
9
- METHOD = :to_yaml
10
- end
11
- end
12
- end
data/lib/dicey/roller.rb DELETED
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "die_foundry"
4
-
5
- require_relative "mixins/rational_to_integer"
6
- require_relative "mixins/vectorize_dice"
7
-
8
- module Dicey
9
- # Let the dice roll!
10
- #
11
- # This is the implementation of roll mode for the CLI.
12
- class Roller
13
- include Mixins::RationalToInteger
14
- include Mixins::VectorizeDice
15
-
16
- # @param arguments [Array<String>] die definitions
17
- # @param format [#call] formatter for output
18
- # @return [nil]
19
- # @raise [DiceyError]
20
- def call(arguments, format:, **)
21
- raise DiceyError, "no dice!" if arguments.empty?
22
-
23
- dice = arguments.flat_map { |definition| die_foundry.cast(definition) }
24
- result = roll_dice(dice)
25
-
26
- format.call({ "roll" => rational_to_integer(result) }, AbstractDie.describe(dice))
27
- rescue TypeError
28
- warn <<~TEXT
29
- Dice with non-numeric sides need gem "vector_number" to be present and available.
30
- If this is intended, please install the gem.
31
- TEXT
32
- raise DiceyError, "can not roll dice with non-numeric sides!"
33
- end
34
-
35
- private
36
-
37
- def die_foundry
38
- @die_foundry ||= DieFoundry.new
39
- end
40
-
41
- def roll_dice(dice)
42
- dice = vectorize_dice(dice) if defined?(VectorNumber)
43
- dice.sum(&:roll)
44
- end
45
- end
46
- end
@@ -1,41 +0,0 @@
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