games_dice 0.3.12 → 0.4.2
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 +5 -5
- data/.github/workflows/ci.yml +38 -0
- data/.gitignore +1 -0
- data/.rubocop.yml +53 -0
- data/.yardopts +1 -1
- data/CHANGELOG.md +18 -0
- data/Gemfile +12 -0
- data/README.md +11 -33
- data/Rakefile +10 -23
- data/ext/games_dice/extconf.rb +4 -22
- data/ext/games_dice/games_dice.c +1 -1
- data/ext/games_dice/probabilities.c +128 -9
- data/ext/games_dice/probabilities.h +1 -1
- data/games_dice.gemspec +22 -36
- data/lib/games_dice/bunch.rb +203 -247
- data/lib/games_dice/bunch_helpers.rb +68 -0
- data/lib/games_dice/complex_die.rb +151 -270
- data/lib/games_dice/complex_die_helpers.rb +260 -60
- data/lib/games_dice/constants.rb +10 -10
- data/lib/games_dice/dice.rb +153 -143
- data/lib/games_dice/die.rb +101 -97
- data/lib/games_dice/die_result.rb +206 -189
- data/lib/games_dice/map_rule.rb +72 -70
- data/lib/games_dice/marshal.rb +41 -13
- data/lib/games_dice/parser.rb +245 -218
- data/lib/games_dice/reroll_rule.rb +76 -77
- data/lib/games_dice/version.rb +4 -1
- data/lib/games_dice.rb +23 -25
- data/spec/bunch_spec.rb +399 -420
- data/spec/complex_die_spec.rb +314 -305
- data/spec/dice_spec.rb +33 -34
- data/spec/die_result_spec.rb +174 -181
- data/spec/die_spec.rb +81 -81
- data/spec/helpers.rb +25 -25
- data/spec/map_rule_spec.rb +40 -44
- data/spec/parser_spec.rb +106 -82
- data/spec/probability_spec.rb +522 -526
- data/spec/readme_spec.rb +410 -390
- data/spec/reroll_rule_spec.rb +40 -44
- metadata +17 -129
- data/.travis.yml +0 -14
- data/lib/games_dice/prob_helpers.rb +0 -259
- data/lib/games_dice/probabilities.rb +0 -244
data/spec/reroll_rule_spec.rb
CHANGED
|
@@ -1,44 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
it
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
it
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
it
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
rule.
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
rule.
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
end
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'helpers'
|
|
4
|
+
|
|
5
|
+
describe GamesDice::RerollRule do
|
|
6
|
+
describe '#new' do
|
|
7
|
+
it 'accept self-consistent operator/value pairs as a trigger' do
|
|
8
|
+
expect { described_class.new(5, :>, :reroll_subtract) }.not_to raise_error
|
|
9
|
+
expect { described_class.new(1..5, :member?, :reroll_replace) }.not_to raise_error
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
it 'reject inconsistent operator/value pairs for a trigger' do
|
|
13
|
+
expect { described_class.new(5, :member?, :reroll_subtract) }.to raise_error(ArgumentError)
|
|
14
|
+
expect { described_class.new(1..5, :>, :reroll_replace) }.to raise_error(ArgumentError)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
it 'reject bad re-roll types' do
|
|
18
|
+
expect { described_class.new(5, :>, :reroll_again) }.to raise_error(ArgumentError)
|
|
19
|
+
expect { described_class.new(1..5, :member?, 42) }.to raise_error(ArgumentError)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe '#applies?' do
|
|
24
|
+
it 'return true if a trigger condition is met' do
|
|
25
|
+
rule = described_class.new(5, :>, :reroll_subtract)
|
|
26
|
+
expect(rule.applies?(4)).to be true
|
|
27
|
+
|
|
28
|
+
rule = described_class.new(1..5, :member?, :reroll_subtract)
|
|
29
|
+
expect(rule.applies?(4)).to be true
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
it 'return false if a trigger condition is not met' do
|
|
33
|
+
rule = described_class.new(5, :>, :reroll_subtract)
|
|
34
|
+
expect(rule.applies?(7)).to be false
|
|
35
|
+
|
|
36
|
+
rule = described_class.new(1..5, :member?, :reroll_subtract)
|
|
37
|
+
expect(rule.applies?(6)).to be false
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
metadata
CHANGED
|
@@ -1,129 +1,32 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: games_dice
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.4.2
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Neil Slater
|
|
8
|
-
autorequire:
|
|
9
8
|
bindir: bin
|
|
10
9
|
cert_chain: []
|
|
11
|
-
date:
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
12
11
|
dependencies:
|
|
13
|
-
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: rspec
|
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
|
16
|
-
requirements:
|
|
17
|
-
- - ">="
|
|
18
|
-
- !ruby/object:Gem::Version
|
|
19
|
-
version: 2.13.0
|
|
20
|
-
type: :development
|
|
21
|
-
prerelease: false
|
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
-
requirements:
|
|
24
|
-
- - ">="
|
|
25
|
-
- !ruby/object:Gem::Version
|
|
26
|
-
version: 2.13.0
|
|
27
|
-
- !ruby/object:Gem::Dependency
|
|
28
|
-
name: rake
|
|
29
|
-
requirement: !ruby/object:Gem::Requirement
|
|
30
|
-
requirements:
|
|
31
|
-
- - ">="
|
|
32
|
-
- !ruby/object:Gem::Version
|
|
33
|
-
version: 1.9.1
|
|
34
|
-
type: :development
|
|
35
|
-
prerelease: false
|
|
36
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
-
requirements:
|
|
38
|
-
- - ">="
|
|
39
|
-
- !ruby/object:Gem::Version
|
|
40
|
-
version: 1.9.1
|
|
41
|
-
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: yard
|
|
43
|
-
requirement: !ruby/object:Gem::Requirement
|
|
44
|
-
requirements:
|
|
45
|
-
- - ">="
|
|
46
|
-
- !ruby/object:Gem::Version
|
|
47
|
-
version: 0.8.6
|
|
48
|
-
type: :development
|
|
49
|
-
prerelease: false
|
|
50
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
-
requirements:
|
|
52
|
-
- - ">="
|
|
53
|
-
- !ruby/object:Gem::Version
|
|
54
|
-
version: 0.8.6
|
|
55
|
-
- !ruby/object:Gem::Dependency
|
|
56
|
-
name: coveralls
|
|
57
|
-
requirement: !ruby/object:Gem::Requirement
|
|
58
|
-
requirements:
|
|
59
|
-
- - ">="
|
|
60
|
-
- !ruby/object:Gem::Version
|
|
61
|
-
version: 0.6.7
|
|
62
|
-
type: :development
|
|
63
|
-
prerelease: false
|
|
64
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
-
requirements:
|
|
66
|
-
- - ">="
|
|
67
|
-
- !ruby/object:Gem::Version
|
|
68
|
-
version: 0.6.7
|
|
69
|
-
- !ruby/object:Gem::Dependency
|
|
70
|
-
name: json
|
|
71
|
-
requirement: !ruby/object:Gem::Requirement
|
|
72
|
-
requirements:
|
|
73
|
-
- - ">="
|
|
74
|
-
- !ruby/object:Gem::Version
|
|
75
|
-
version: 1.7.7
|
|
76
|
-
type: :development
|
|
77
|
-
prerelease: false
|
|
78
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
79
|
-
requirements:
|
|
80
|
-
- - ">="
|
|
81
|
-
- !ruby/object:Gem::Version
|
|
82
|
-
version: 1.7.7
|
|
83
|
-
- !ruby/object:Gem::Dependency
|
|
84
|
-
name: rake-compiler
|
|
85
|
-
requirement: !ruby/object:Gem::Requirement
|
|
86
|
-
requirements:
|
|
87
|
-
- - ">="
|
|
88
|
-
- !ruby/object:Gem::Version
|
|
89
|
-
version: 0.8.3
|
|
90
|
-
type: :development
|
|
91
|
-
prerelease: false
|
|
92
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
93
|
-
requirements:
|
|
94
|
-
- - ">="
|
|
95
|
-
- !ruby/object:Gem::Version
|
|
96
|
-
version: 0.8.3
|
|
97
|
-
- !ruby/object:Gem::Dependency
|
|
98
|
-
name: redcarpet
|
|
99
|
-
requirement: !ruby/object:Gem::Requirement
|
|
100
|
-
requirements:
|
|
101
|
-
- - ">="
|
|
102
|
-
- !ruby/object:Gem::Version
|
|
103
|
-
version: 2.3.0
|
|
104
|
-
type: :development
|
|
105
|
-
prerelease: false
|
|
106
|
-
version_requirements: !ruby/object:Gem::Requirement
|
|
107
|
-
requirements:
|
|
108
|
-
- - ">="
|
|
109
|
-
- !ruby/object:Gem::Version
|
|
110
|
-
version: 2.3.0
|
|
111
12
|
- !ruby/object:Gem::Dependency
|
|
112
13
|
name: parslet
|
|
113
14
|
requirement: !ruby/object:Gem::Requirement
|
|
114
15
|
requirements:
|
|
115
|
-
- - "
|
|
16
|
+
- - "~>"
|
|
116
17
|
- !ruby/object:Gem::Version
|
|
117
|
-
version:
|
|
18
|
+
version: '2.0'
|
|
118
19
|
type: :runtime
|
|
119
20
|
prerelease: false
|
|
120
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
121
22
|
requirements:
|
|
122
|
-
- - "
|
|
23
|
+
- - "~>"
|
|
123
24
|
- !ruby/object:Gem::Version
|
|
124
|
-
version:
|
|
125
|
-
description: A library for simulating dice. Use it to construct dice-rolling systems
|
|
25
|
+
version: '2.0'
|
|
26
|
+
description: 'A library for simulating dice. Use it to construct dice-rolling systems
|
|
126
27
|
used in role-playing and board games.
|
|
28
|
+
|
|
29
|
+
'
|
|
127
30
|
email:
|
|
128
31
|
- slobo777@gmail.com
|
|
129
32
|
executables: []
|
|
@@ -131,8 +34,9 @@ extensions:
|
|
|
131
34
|
- ext/games_dice/extconf.rb
|
|
132
35
|
extra_rdoc_files: []
|
|
133
36
|
files:
|
|
37
|
+
- ".github/workflows/ci.yml"
|
|
134
38
|
- ".gitignore"
|
|
135
|
-
- ".
|
|
39
|
+
- ".rubocop.yml"
|
|
136
40
|
- ".yardopts"
|
|
137
41
|
- CHANGELOG.md
|
|
138
42
|
- Gemfile
|
|
@@ -146,6 +50,7 @@ files:
|
|
|
146
50
|
- games_dice.gemspec
|
|
147
51
|
- lib/games_dice.rb
|
|
148
52
|
- lib/games_dice/bunch.rb
|
|
53
|
+
- lib/games_dice/bunch_helpers.rb
|
|
149
54
|
- lib/games_dice/complex_die.rb
|
|
150
55
|
- lib/games_dice/complex_die_helpers.rb
|
|
151
56
|
- lib/games_dice/constants.rb
|
|
@@ -155,8 +60,6 @@ files:
|
|
|
155
60
|
- lib/games_dice/map_rule.rb
|
|
156
61
|
- lib/games_dice/marshal.rb
|
|
157
62
|
- lib/games_dice/parser.rb
|
|
158
|
-
- lib/games_dice/prob_helpers.rb
|
|
159
|
-
- lib/games_dice/probabilities.rb
|
|
160
63
|
- lib/games_dice/reroll_rule.rb
|
|
161
64
|
- lib/games_dice/version.rb
|
|
162
65
|
- spec/bunch_spec.rb
|
|
@@ -174,8 +77,8 @@ files:
|
|
|
174
77
|
homepage: https://github.com/neilslater/games_dice
|
|
175
78
|
licenses:
|
|
176
79
|
- MIT
|
|
177
|
-
metadata:
|
|
178
|
-
|
|
80
|
+
metadata:
|
|
81
|
+
rubygems_mfa_required: 'true'
|
|
179
82
|
rdoc_options: []
|
|
180
83
|
require_paths:
|
|
181
84
|
- lib
|
|
@@ -183,30 +86,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
183
86
|
requirements:
|
|
184
87
|
- - ">="
|
|
185
88
|
- !ruby/object:Gem::Version
|
|
186
|
-
version:
|
|
89
|
+
version: 3.3.0
|
|
187
90
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
188
91
|
requirements:
|
|
189
92
|
- - ">="
|
|
190
93
|
- !ruby/object:Gem::Version
|
|
191
94
|
version: '0'
|
|
192
95
|
requirements: []
|
|
193
|
-
|
|
194
|
-
rubygems_version: 2.6.12
|
|
195
|
-
signing_key:
|
|
96
|
+
rubygems_version: 4.0.1
|
|
196
97
|
specification_version: 4
|
|
197
98
|
summary: Simulates and explains dice rolls from simple "1d6" to complex "roll 7 ten-sided
|
|
198
99
|
dice, take best 3, results of 10 roll again and add on".
|
|
199
|
-
test_files:
|
|
200
|
-
- spec/bunch_spec.rb
|
|
201
|
-
- spec/complex_die_spec.rb
|
|
202
|
-
- spec/dice_spec.rb
|
|
203
|
-
- spec/die_result_spec.rb
|
|
204
|
-
- spec/die_spec.rb
|
|
205
|
-
- spec/fixtures/probs_fair_die_6.dat
|
|
206
|
-
- spec/helpers.rb
|
|
207
|
-
- spec/map_rule_spec.rb
|
|
208
|
-
- spec/parser_spec.rb
|
|
209
|
-
- spec/probability_spec.rb
|
|
210
|
-
- spec/readme_spec.rb
|
|
211
|
-
- spec/reroll_rule_spec.rb
|
|
212
|
-
has_rdoc:
|
|
100
|
+
test_files: []
|
data/.travis.yml
DELETED
|
@@ -1,259 +0,0 @@
|
|
|
1
|
-
# @!visibility private
|
|
2
|
-
module GamesDice::ProbabilityValidations
|
|
3
|
-
|
|
4
|
-
# @!visibility private
|
|
5
|
-
# the Array, Offset representation of probabilities.
|
|
6
|
-
def to_ao
|
|
7
|
-
[ @probs, @offset ]
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def self.included(klass)
|
|
11
|
-
klass.extend ClassMethods
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
private
|
|
15
|
-
|
|
16
|
-
def check_probs_array probs_array
|
|
17
|
-
raise TypeError unless probs_array.is_a?( Array )
|
|
18
|
-
probs_array.map!{ |n| Float(n) }
|
|
19
|
-
total = probs_array.inject(0.0) do |t,x|
|
|
20
|
-
if x < 0.0 || x > 1.0
|
|
21
|
-
raise ArgumentError, "Found probability value #{x} which is not in range 0.0..1.0"
|
|
22
|
-
end
|
|
23
|
-
t+x
|
|
24
|
-
end
|
|
25
|
-
if (total-1.0).abs > 1e-6
|
|
26
|
-
raise ArgumentError, "Total probabilities too far from 1.0 for a valid distribution"
|
|
27
|
-
end
|
|
28
|
-
probs_array
|
|
29
|
-
end
|
|
30
|
-
|
|
31
|
-
def check_keep_mode kmode
|
|
32
|
-
raise "Keep mode #{kmode.inspect} not recognised" unless [:keep_best,:keep_worst].member?( kmode )
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
module ClassMethods
|
|
36
|
-
# Convert hash to array,offset notation
|
|
37
|
-
def prob_h_to_ao h
|
|
38
|
-
rmin,rmax = h.keys.minmax
|
|
39
|
-
o = rmin
|
|
40
|
-
s = 1 + rmax - rmin
|
|
41
|
-
raise ArgumentError, "Range of possible results too large" if s > 1000000
|
|
42
|
-
a = Array.new( s, 0.0 )
|
|
43
|
-
h.each { |k,v| a[k-rmin] = Float(v) }
|
|
44
|
-
[a,o]
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
# Convert array,offset notation to hash
|
|
48
|
-
def prob_ao_to_h a, o
|
|
49
|
-
h = Hash.new
|
|
50
|
-
a.each_with_index { |v,i| h[i+o] = v if v > 0.0 }
|
|
51
|
-
h
|
|
52
|
-
end
|
|
53
|
-
|
|
54
|
-
private
|
|
55
|
-
|
|
56
|
-
def check_is_gdp *probs
|
|
57
|
-
probs.each do |prob|
|
|
58
|
-
unless prob.is_a?( GamesDice::Probabilities )
|
|
59
|
-
raise TypeError, "parameter is not a GamesDice::Probabilities"
|
|
60
|
-
end
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
# @!visibility private
|
|
67
|
-
# This module is a set of related private methods for GamesDice::Probabilities that
|
|
68
|
-
# calculate how two distributions can be combined.
|
|
69
|
-
module GamesDice::ProbabilityCalcAddDistributions
|
|
70
|
-
private
|
|
71
|
-
|
|
72
|
-
def calc_combined_extremes m_a, pd_a, m_b, pd_b
|
|
73
|
-
[ [ :min, :min ], [ :min, :max ], [ :max, :min ], [ :max, :max ] ].map do |pda_meth, pdb_meth|
|
|
74
|
-
m_a * pd_a.send(pda_meth) + m_b * pd_b.send(pdb_meth)
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
|
-
def add_distributions_internal combined_min, combined_max, m_a, pd_a, m_b, pd_b
|
|
79
|
-
new_probs = Array.new( 1 + combined_max - combined_min, 0.0 )
|
|
80
|
-
probs_a, offset_a = pd_a.to_ao
|
|
81
|
-
probs_b, offset_b = pd_b.to_ao
|
|
82
|
-
|
|
83
|
-
probs_a.each_with_index do |pa,i|
|
|
84
|
-
probs_b.each_with_index do |pb,j|
|
|
85
|
-
k = m_a * (i + offset_a) + m_b * (j + offset_b) - combined_min
|
|
86
|
-
pc = pa * pb
|
|
87
|
-
new_probs[ k ] += pc
|
|
88
|
-
end
|
|
89
|
-
end
|
|
90
|
-
GamesDice::Probabilities.new( new_probs, combined_min )
|
|
91
|
-
end
|
|
92
|
-
end
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
# @!visibility private
|
|
96
|
-
# This module is a set of related private methods for GamesDice::Probabilities that
|
|
97
|
-
# calculate how a distribution can be combined with itself.
|
|
98
|
-
module GamesDice::ProbabilityCalcSums
|
|
99
|
-
|
|
100
|
-
private
|
|
101
|
-
|
|
102
|
-
def repeat_sum_internal( n )
|
|
103
|
-
pd_power = self
|
|
104
|
-
pd_result = nil
|
|
105
|
-
|
|
106
|
-
use_power = 1
|
|
107
|
-
loop do
|
|
108
|
-
if ( use_power & n ) > 0
|
|
109
|
-
if pd_result
|
|
110
|
-
pd_result = GamesDice::Probabilities.add_distributions( pd_result, pd_power )
|
|
111
|
-
else
|
|
112
|
-
pd_result = pd_power
|
|
113
|
-
end
|
|
114
|
-
end
|
|
115
|
-
use_power = use_power << 1
|
|
116
|
-
break if use_power > n
|
|
117
|
-
pd_power = GamesDice::Probabilities.add_distributions( pd_power, pd_power )
|
|
118
|
-
end
|
|
119
|
-
pd_result
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
def repeat_n_sum_k_internal( n, k, kmode )
|
|
123
|
-
if k >= n
|
|
124
|
-
return repeat_sum_internal( n )
|
|
125
|
-
end
|
|
126
|
-
new_probs = Array.new( @probs.count * k, 0.0 )
|
|
127
|
-
new_offset = @offset * k
|
|
128
|
-
d = n - k
|
|
129
|
-
|
|
130
|
-
each do | q, p_maybe |
|
|
131
|
-
repeat_n_sum_k_each_q( q, p_maybe, n, k, kmode, d, new_probs, new_offset )
|
|
132
|
-
end
|
|
133
|
-
|
|
134
|
-
GamesDice::Probabilities.new( new_probs, new_offset )
|
|
135
|
-
end
|
|
136
|
-
|
|
137
|
-
def repeat_n_sum_k_each_q q, p_maybe, n, k, kmode, d, new_probs, new_offset
|
|
138
|
-
# keep_distributions is array of Probabilities, indexed by number of keepers > q, which is in 0...k
|
|
139
|
-
keep_distributions = calc_keep_distributions( k, q, kmode )
|
|
140
|
-
p_table = calc_p_table( q, p_maybe, kmode )
|
|
141
|
-
(0...k).each do |kn|
|
|
142
|
-
repeat_n_sum_k_each_q_kn( k, kn, d, new_probs, new_offset, keep_distributions, p_table )
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def repeat_n_sum_k_each_q_kn k, kn, d, new_probs, new_offset, keep_distributions, p_table
|
|
147
|
-
keepers = [2] * kn + [1] * (k-kn)
|
|
148
|
-
p_so_far = keepers.inject(1.0) { |p,idx| p * p_table[idx] }
|
|
149
|
-
return unless p_so_far > 0.0
|
|
150
|
-
(0..d).each do |dn|
|
|
151
|
-
repeat_n_sum_k_each_q_kn_dn( keepers, kn, d, dn, p_so_far, new_probs, new_offset, keep_distributions, p_table )
|
|
152
|
-
end
|
|
153
|
-
end
|
|
154
|
-
|
|
155
|
-
def repeat_n_sum_k_each_q_kn_dn keepers, kn, d, dn, p_so_far, new_probs, new_offset, keep_distributions, p_table
|
|
156
|
-
discards = [1] * (d-dn) + [0] * dn
|
|
157
|
-
sequence = keepers + discards
|
|
158
|
-
p_sequence = discards.inject( p_so_far ) { |p,idx| p * p_table[idx] }
|
|
159
|
-
return unless p_sequence > 0.0
|
|
160
|
-
p_sequence *= GamesDice::Combinations.count_variations( sequence )
|
|
161
|
-
kd = keep_distributions[kn]
|
|
162
|
-
kd.each { |r,p_r| new_probs[r-new_offset] += p_r * p_sequence }
|
|
163
|
-
end
|
|
164
|
-
|
|
165
|
-
def calc_keep_distributions k, q, kmode
|
|
166
|
-
kd_probabilities = calc_keep_definite_distributions q, kmode
|
|
167
|
-
|
|
168
|
-
keep_distributions = [ GamesDice::Probabilities.new( [1.0], q * k ) ]
|
|
169
|
-
if kd_probabilities && k > 1
|
|
170
|
-
(1...k).each do |n|
|
|
171
|
-
extra_o = GamesDice::Probabilities.new( [1.0], q * ( k - n ) )
|
|
172
|
-
n_probs = kd_probabilities.repeat_sum( n )
|
|
173
|
-
keep_distributions[n] = GamesDice::Probabilities.add_distributions( extra_o, n_probs )
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
keep_distributions
|
|
178
|
-
end
|
|
179
|
-
|
|
180
|
-
def calc_keep_definite_distributions q, kmode
|
|
181
|
-
kd_probabilities = nil
|
|
182
|
-
case kmode
|
|
183
|
-
when :keep_best
|
|
184
|
-
p_definites = p_gt(q)
|
|
185
|
-
kd_probabilities = given_ge( q + 1 ) if p_definites > 0.0
|
|
186
|
-
when :keep_worst
|
|
187
|
-
p_definites = p_lt(q)
|
|
188
|
-
kd_probabilities = given_le( q - 1 ) if p_definites > 0.0
|
|
189
|
-
end
|
|
190
|
-
kd_probabilities
|
|
191
|
-
end
|
|
192
|
-
|
|
193
|
-
def calc_p_table q, p_maybe, kmode
|
|
194
|
-
case kmode
|
|
195
|
-
when :keep_best
|
|
196
|
-
p_kept = p_gt(q)
|
|
197
|
-
p_rejected = p_lt(q)
|
|
198
|
-
when :keep_worst
|
|
199
|
-
p_kept = p_lt(q)
|
|
200
|
-
p_rejected = p_gt(q)
|
|
201
|
-
end
|
|
202
|
-
[ p_rejected, p_maybe, p_kept ]
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
end
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
# @!visibility private
|
|
209
|
-
# Helper module with optimised Ruby for counting variations of arrays, such as those returned by
|
|
210
|
-
# Array#repeated_combination
|
|
211
|
-
#
|
|
212
|
-
# @example How many ways can [3,3,6] be arranged?
|
|
213
|
-
# GamesDice::Combinations.count_variations( [3,3,6] )
|
|
214
|
-
# => 3
|
|
215
|
-
#
|
|
216
|
-
# @example When prob( a ) and result( a ) are same for any arrangement of Array a
|
|
217
|
-
# items = [1,2,3,4,5,6]
|
|
218
|
-
# items.repeated_combination(5).each do |a|
|
|
219
|
-
# this_result = result( a )
|
|
220
|
-
# this_prob = prob( a ) * GamesDice::Combinations.count_variations( a )
|
|
221
|
-
# # Do something useful with this knowledge! E.g. save it to probability array.
|
|
222
|
-
# end
|
|
223
|
-
#
|
|
224
|
-
module GamesDice::Combinations
|
|
225
|
-
@@variations_cache = {}
|
|
226
|
-
@@factorial_cache = [1, 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800]
|
|
227
|
-
|
|
228
|
-
# Counts variations of an array. A unique variation is an arrangement of the array elements which
|
|
229
|
-
# is detectably different (using ==) from any other. So [1,1,1] has only 1 unique arrangement,
|
|
230
|
-
# but [1,2,3] has 6 possibilities.
|
|
231
|
-
# @param [Array] array List of things that can be arranged
|
|
232
|
-
# @return [Integer] Number of unique arrangements
|
|
233
|
-
def self.count_variations array
|
|
234
|
-
all_count = array.count
|
|
235
|
-
group_sizes = group_counts( array )
|
|
236
|
-
cache_key = all_count.to_s + ":" + group_sizes.join(',')
|
|
237
|
-
@@variations_cache[cache_key] ||= variations_of( all_count, group_sizes )
|
|
238
|
-
end
|
|
239
|
-
|
|
240
|
-
private
|
|
241
|
-
|
|
242
|
-
def self.variations_of all_count, groups
|
|
243
|
-
all_arrangements = factorial( all_count )
|
|
244
|
-
# The reject is an optimisation to avoid calculating and multplying by factorial(1) (==1)
|
|
245
|
-
identical_arrangements = groups.reject {|x| x==1 }.inject(1) { |prod,g| prod * factorial(g) }
|
|
246
|
-
all_arrangements/identical_arrangements
|
|
247
|
-
end
|
|
248
|
-
|
|
249
|
-
# Returns counts of unique items in array e.g. [8,8,8,7,6,6] returns [1,2,3]
|
|
250
|
-
# Sort is for caching
|
|
251
|
-
def self.group_counts array
|
|
252
|
-
array.group_by {|x| x}.values.map {|v| v.count}.sort
|
|
253
|
-
end
|
|
254
|
-
|
|
255
|
-
def self.factorial n
|
|
256
|
-
# Can start range from 2 because we have pre-cached the result for n=1
|
|
257
|
-
@@factorial_cache[n] ||= (2..n).inject(:*)
|
|
258
|
-
end
|
|
259
|
-
end
|