games_dice 0.2.0 → 0.2.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.
- data/.travis.yml +2 -2
- data/Rakefile +5 -0
- data/games_dice.gemspec +3 -1
- data/lib/games_dice.rb +1 -0
- data/lib/games_dice/complex_die.rb +60 -35
- data/lib/games_dice/die.rb +43 -16
- data/lib/games_dice/version.rb +1 -1
- data/spec/die_spec.rb +3 -18
- data/spec/helpers.rb +15 -0
- metadata +39 -6
data/.travis.yml
CHANGED
data/Rakefile
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rspec/core/rake_task"
|
3
|
+
require "yard"
|
3
4
|
|
4
5
|
task :default => [:test]
|
5
6
|
|
@@ -8,3 +9,7 @@ RSpec::Core::RakeTask.new(:test) do |t|
|
|
8
9
|
t.pattern = "spec/*_spec.rb"
|
9
10
|
t.verbose = false
|
10
11
|
end
|
12
|
+
|
13
|
+
YARD::Rake::YardocTask.new do |t|
|
14
|
+
t.files = ['lib/**/*.rb']
|
15
|
+
end
|
data/games_dice.gemspec
CHANGED
@@ -16,8 +16,10 @@ Gem::Specification.new do |gem|
|
|
16
16
|
|
17
17
|
gem.add_development_dependency "rspec", ">= 2.13.0"
|
18
18
|
gem.add_development_dependency "rake", ">= 1.9.1"
|
19
|
+
gem.add_development_dependency "yard", ">= 0.8.6"
|
20
|
+
gem.add_development_dependency "redcarpet", ">=2.3.0"
|
19
21
|
|
20
|
-
gem.add_dependency "parslet", "
|
22
|
+
gem.add_dependency "parslet", ">= 1.5.0"
|
21
23
|
|
22
24
|
gem.files = `git ls-files`.split($/)
|
23
25
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
data/lib/games_dice.rb
CHANGED
@@ -1,59 +1,82 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
#
|
4
|
-
#
|
5
|
-
#
|
1
|
+
# This class models a die that is built up from a simpler unit by adding rules to re-roll
|
2
|
+
# and interpret the value shown.
|
3
|
+
#
|
4
|
+
# An object of this class represents a single complex die. It rolls 1..#sides, with equal weighting
|
5
|
+
# for each value. The value from a roll may be used to trigger yet more rolls that combine together.
|
6
|
+
# After any re-rolls, the value can be interpretted ("mapped") as another integer, which is used as
|
7
|
+
# the final result.
|
8
|
+
#
|
9
|
+
# @example An open-ended percentile die from a popular RPG
|
10
|
+
# d = GamesDice::ComplexDie.new( 100, :rerolls => [[96, :<=, :reroll_add],[5, :>=, :reroll_subtract]] )
|
11
|
+
# d.roll # => #<GamesDice::DieResult:0x007ff03a2415f8 @rolls=[4, 27], ...>
|
12
|
+
# d.result.value # => -23
|
13
|
+
# d.explain_result # => "[4-27] -23"
|
14
|
+
#
|
15
|
+
# @example An "exploding" six-sided die with a target number
|
16
|
+
# d = GamesDice::ComplexDie.new( 6, :rerolls => [[6, :<=, :reroll_add]], :maps => [[8, :<=, 1, 'Success']] )
|
17
|
+
# d.roll # => #<GamesDice::DieResult:0x007ff03a1e8e08 @rolls=[6, 5], ...>
|
18
|
+
# d.result.value # => 1
|
19
|
+
# d.explain_result # => "[6+5] 11 Success"
|
20
|
+
#
|
21
|
+
|
6
22
|
class GamesDice::ComplexDie
|
7
23
|
|
8
|
-
#
|
9
|
-
#
|
24
|
+
# @!visibility private
|
25
|
+
# arbitrary limit to speed up probability calculations. It should
|
26
|
+
# be larger than anything seen in real-world tabletop games.
|
10
27
|
MAX_REROLLS = 1000
|
11
28
|
|
12
|
-
#
|
13
|
-
#
|
14
|
-
#
|
15
|
-
#
|
16
|
-
#
|
17
|
-
#
|
18
|
-
#
|
19
|
-
def initialize(sides,
|
20
|
-
@basic_die = GamesDice::Die.new(sides,
|
29
|
+
# Creates new instance of GamesDice::ComplexDie
|
30
|
+
# @param [Integer] sides Number of sides on a single die, passed to GamesDice::Die's constructor
|
31
|
+
# @param [Hash] options
|
32
|
+
# @option options [Array<GamesDice::RerollRule,Array>] :rerolls The rules that cause the die to roll again
|
33
|
+
# @option options [Array<GamesDice::MapRule,Array>] :maps The rules to convert a value into a final result for the die
|
34
|
+
# @option options [#rand] :prng An alternative source of randomness to Ruby's built-in #rand, passed to GamesDice::Die's constructor
|
35
|
+
# @return [GamesDice::ComplexDie]
|
36
|
+
def initialize( sides, options = {} )
|
37
|
+
@basic_die = GamesDice::Die.new(sides, options[:prng])
|
21
38
|
|
22
|
-
@rerolls = construct_rerolls(
|
23
|
-
@maps = construct_maps(
|
39
|
+
@rerolls = construct_rerolls( options[:rerolls] )
|
40
|
+
@maps = construct_maps( options[:maps] )
|
24
41
|
|
25
42
|
@total = nil
|
26
43
|
@result = nil
|
27
44
|
end
|
28
45
|
|
29
|
-
#
|
46
|
+
# The simple component used by this complex one
|
47
|
+
# @return [GamesDice::Die] Object used to make individual dice rolls for the complex die
|
30
48
|
attr_reader :basic_die
|
31
49
|
|
32
|
-
#
|
50
|
+
# @return [Array<GamesDice::RerollRule>, nil] Sequence of re-roll rules, or nil if re-rolls are not required.
|
33
51
|
attr_reader :rerolls
|
34
52
|
|
35
|
-
#
|
53
|
+
# @return [Array<GamesDice::MapRule>, nil] Sequence of map rules, or nil if mapping is not required.
|
36
54
|
attr_reader :maps
|
37
55
|
|
38
|
-
#
|
56
|
+
# @return [GamesDice::DieResult, nil] Result of last call to #roll, nil if no call made yet
|
39
57
|
attr_reader :result
|
40
58
|
|
41
|
-
#
|
42
|
-
#
|
43
|
-
#
|
59
|
+
# Whether or not #probabilities includes all possible outcomes.
|
60
|
+
# True if all possible results are represented and assigned a probability. Dice with open-ended re-rolls
|
61
|
+
# may have calculations cut short, and will result in a false value of this attribute. Even when this
|
62
|
+
# attribute is false, probabilities should still be accurate to nearest 1e-9.
|
63
|
+
# @return [Boolean, nil] Depending on completeness when generating #probabilites
|
44
64
|
attr_reader :probabilities_complete
|
45
65
|
|
46
|
-
#
|
66
|
+
# @!attribute [r] sides
|
67
|
+
# @return [Integer] Number of sides.
|
47
68
|
def sides
|
48
69
|
@basic_die.sides
|
49
70
|
end
|
50
71
|
|
51
|
-
#
|
72
|
+
# @!attribute [r] explain_result
|
73
|
+
# @return [String,nil] Explanation of result, or nil if no call to #roll yet.
|
52
74
|
def explain_result
|
53
75
|
@result.explain_value
|
54
76
|
end
|
55
77
|
|
56
|
-
#
|
78
|
+
# @!attribute [r] min
|
79
|
+
# @return [Integer] Minimum possible result from a call to #roll
|
57
80
|
def min
|
58
81
|
return @min_result if @min_result
|
59
82
|
@min_result, @max_result = [probabilities.min, probabilities.max]
|
@@ -63,8 +86,8 @@ class GamesDice::ComplexDie
|
|
63
86
|
@min_result
|
64
87
|
end
|
65
88
|
|
66
|
-
#
|
67
|
-
#
|
89
|
+
# @!attribute [r] max
|
90
|
+
# @return [Integer] Maximum possible result from a call to #roll
|
68
91
|
def max
|
69
92
|
return @max_result if @max_result
|
70
93
|
@min_result, @max_result = [probabilities.min, probabilities.max]
|
@@ -74,9 +97,10 @@ class GamesDice::ComplexDie
|
|
74
97
|
@max_result
|
75
98
|
end
|
76
99
|
|
77
|
-
#
|
78
|
-
#
|
79
|
-
#
|
100
|
+
# Calculates the probability distribution for the die. For open-ended re-roll rules, there are some
|
101
|
+
# arbitrary limits imposed to prevent large amounts of recursion. Probabilities should be to nearest
|
102
|
+
# 1e-9 at worst.
|
103
|
+
# @return [GamesDice::Probabilities] Probability distribution of die.
|
80
104
|
def probabilities
|
81
105
|
return @probabilities if @probabilities
|
82
106
|
@probabilities_complete = true
|
@@ -106,8 +130,9 @@ class GamesDice::ComplexDie
|
|
106
130
|
@probabilities = GamesDice::Probabilities.new( prob_hash )
|
107
131
|
end
|
108
132
|
|
109
|
-
#
|
110
|
-
#
|
133
|
+
# Simulates rolling the die
|
134
|
+
# @param [Symbol] reason Assign a reason for rolling the first die.
|
135
|
+
# @return [GamesDice::DieResult] Detailed results from rolling the die, including resolution of rules.
|
111
136
|
def roll( reason = :basic )
|
112
137
|
# Important bit - actually roll the die
|
113
138
|
@result = GamesDice::DieResult.new( @basic_die.roll, reason )
|
data/lib/games_dice/die.rb
CHANGED
@@ -1,12 +1,28 @@
|
|
1
|
-
#
|
2
|
-
#
|
1
|
+
# This class models the simplest, most-familiar kind of die.
|
2
|
+
#
|
3
|
+
# An object of the class represents a basic die that rolls 1..#sides, with equal weighting for each value.
|
4
|
+
#
|
5
|
+
# @example Create a 6-sided die, and roll it
|
6
|
+
# d = GamesDice::Die.new( 6 )
|
3
7
|
# d.roll # => Integer in range 1..6
|
4
|
-
# d.result # => same Integer value as returned by d.roll
|
8
|
+
# d.result # => same Integer value as just returned by d.roll
|
9
|
+
#
|
10
|
+
# @example Create a 10-sided die, that rolls using a monkey-patch to SecureRandom
|
11
|
+
# module SecureRandom
|
12
|
+
# def self.rand n
|
13
|
+
# random_number( n )
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
# d = GamesDice::Die.new( 10, SecureRandom )
|
17
|
+
# d.roll # => (secure) Integer in range 1..10
|
18
|
+
# d.result # => same Integer value as just returned by d.roll
|
19
|
+
|
5
20
|
class GamesDice::Die
|
6
|
-
|
7
|
-
#
|
8
|
-
#
|
9
|
-
#
|
21
|
+
|
22
|
+
# Creates new instance of GamesDice::Die
|
23
|
+
# @param [Integer] sides the number of sides
|
24
|
+
# @param [#rand] prng random number generator, GamesDice::Die will use Ruby's built-in #rand() by default
|
25
|
+
# @return [GamesDice::Die]
|
10
26
|
def initialize( sides, prng=nil )
|
11
27
|
@sides = Integer(sides)
|
12
28
|
raise ArgumentError, "sides value #{sides} is too low, it must be 1 or greater" if @sides < 1
|
@@ -15,29 +31,36 @@ class GamesDice::Die
|
|
15
31
|
@result = nil
|
16
32
|
end
|
17
33
|
|
18
|
-
# number of sides
|
34
|
+
# @return [Integer] number of sides on simulated die
|
19
35
|
attr_reader :sides
|
20
36
|
|
21
|
-
#
|
37
|
+
# @return [Integer] result of last call to #roll, nil if no call made yet
|
22
38
|
attr_reader :result
|
23
39
|
|
24
|
-
#
|
40
|
+
# @return [Object] random number generator as supplied to constructor, may be nil
|
41
|
+
attr_reader :prng
|
42
|
+
|
43
|
+
# @!attribute [r] min
|
44
|
+
# @return [Integer] minimum possible result from a call to #roll
|
25
45
|
def min
|
26
46
|
1
|
27
47
|
end
|
28
48
|
|
29
|
-
#
|
49
|
+
# @!attribute [r] max
|
50
|
+
# @return [Integer] maximum possible result from a call to #roll
|
30
51
|
def max
|
31
52
|
@sides
|
32
53
|
end
|
33
54
|
|
34
|
-
#
|
55
|
+
# Calculates probability distribution for this die.
|
56
|
+
# @return [GamesDice::Probabilities] probability distribution of the die
|
35
57
|
def probabilities
|
36
58
|
return @probabilities if @probabilities
|
37
59
|
@probabilities = GamesDice::Probabilities.for_fair_die( @sides )
|
38
60
|
end
|
39
61
|
|
40
|
-
#
|
62
|
+
# Simulates rolling the die
|
63
|
+
# @return [Integer] selected value between 1 and #sides inclusive
|
41
64
|
def roll
|
42
65
|
if @prng
|
43
66
|
@result = @prng.rand(@sides) + 1
|
@@ -46,13 +69,17 @@ class GamesDice::Die
|
|
46
69
|
end
|
47
70
|
end
|
48
71
|
|
49
|
-
#
|
72
|
+
# @!attribute [r] rerolls
|
73
|
+
# Rules for when to re-roll this die.
|
74
|
+
# @return [nil] always nil, available for interface equivalence with GamesDice::ComplexDie
|
50
75
|
def rerolls
|
51
76
|
nil
|
52
77
|
end
|
53
78
|
|
54
|
-
#
|
79
|
+
# @!attribute [r] maps
|
80
|
+
# Rules for when to map return value of this die.
|
81
|
+
# @return [nil] always nil, available for interface equivalence with GamesDice::ComplexDie
|
55
82
|
def maps
|
56
83
|
nil
|
57
84
|
end
|
58
|
-
end # class Die
|
85
|
+
end # class GamesDice::Die
|
data/lib/games_dice/version.rb
CHANGED
data/spec/die_spec.rb
CHANGED
@@ -46,8 +46,10 @@ describe GamesDice::Die do
|
|
46
46
|
describe "#probabilities" do
|
47
47
|
it "should return the die's probability distribution as a GamesDice::Probabilities object" do
|
48
48
|
die = GamesDice::Die.new(6)
|
49
|
-
die.probabilities.is_a?( GamesDice::Probabilities ).should be_true
|
50
49
|
probs = die.probabilities
|
50
|
+
probs.is_a?( GamesDice::Probabilities ).should be_true
|
51
|
+
|
52
|
+
probs.to_h.should be_valid_distribution
|
51
53
|
|
52
54
|
probs.p_eql(1).should be_within(1e-10).of 1/6.0
|
53
55
|
probs.p_eql(2).should be_within(1e-10).of 1/6.0
|
@@ -55,23 +57,6 @@ describe GamesDice::Die do
|
|
55
57
|
probs.p_eql(4).should be_within(1e-10).of 1/6.0
|
56
58
|
probs.p_eql(5).should be_within(1e-10).of 1/6.0
|
57
59
|
probs.p_eql(6).should be_within(1e-10).of 1/6.0
|
58
|
-
probs.to_h.values.inject(:+).should be_within(1e-9).of 1.0
|
59
|
-
|
60
|
-
probs.p_gt(6).should == 0.0
|
61
|
-
probs.p_gt(-20).should == 1.0
|
62
|
-
probs.p_gt(4).should be_within(1e-10).of 2/6.0
|
63
|
-
|
64
|
-
probs.p_ge(20).should == 0.0
|
65
|
-
probs.p_ge(1).should == 1.0
|
66
|
-
probs.p_ge(4).should be_within(1e-10).of 0.5
|
67
|
-
|
68
|
-
probs.p_le(6).should == 1.0
|
69
|
-
probs.p_le(-3).should == 0.0
|
70
|
-
probs.p_le(5).should be_within(1e-10).of 5/6.0
|
71
|
-
|
72
|
-
probs.p_lt(7).should == 1.0
|
73
|
-
probs.p_lt(1).should == 0.0
|
74
|
-
probs.p_lt(3).should be_within(1e-10).of 2/6.0
|
75
60
|
|
76
61
|
probs.expected.should be_within(1e-10).of 3.5
|
77
62
|
end
|
data/spec/helpers.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# games_dice/spec/helpers.rb
|
2
2
|
|
3
|
+
# TestPRNG tests short predictable series
|
3
4
|
class TestPRNG
|
4
5
|
def initialize
|
5
6
|
@numbers = [0.123,0.234,0.345,0.999,0.876,0.765,0.543,0.111,0.333,0.777]
|
@@ -9,6 +10,20 @@ class TestPRNG
|
|
9
10
|
end
|
10
11
|
end
|
11
12
|
|
13
|
+
# TestPRNGMax checks behaviour of re-rolls
|
14
|
+
class TestPRNGMax
|
15
|
+
def rand(n)
|
16
|
+
Integer( n ) - 1
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# TestPRNGMax checks behaviour of re-rolls
|
21
|
+
class TestPRNGMin
|
22
|
+
def rand(n)
|
23
|
+
1
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
12
27
|
# A valid distribution is:
|
13
28
|
# A hash
|
14
29
|
# Keys are all Integers
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: games_dice
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.1
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-05
|
12
|
+
date: 2013-06-05 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rspec
|
@@ -43,12 +43,44 @@ dependencies:
|
|
43
43
|
- - ! '>='
|
44
44
|
- !ruby/object:Gem::Version
|
45
45
|
version: 1.9.1
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: yard
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: 0.8.6
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 0.8.6
|
62
|
+
- !ruby/object:Gem::Dependency
|
63
|
+
name: redcarpet
|
64
|
+
requirement: !ruby/object:Gem::Requirement
|
65
|
+
none: false
|
66
|
+
requirements:
|
67
|
+
- - ! '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: 2.3.0
|
70
|
+
type: :development
|
71
|
+
prerelease: false
|
72
|
+
version_requirements: !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: 2.3.0
|
46
78
|
- !ruby/object:Gem::Dependency
|
47
79
|
name: parslet
|
48
80
|
requirement: !ruby/object:Gem::Requirement
|
49
81
|
none: false
|
50
82
|
requirements:
|
51
|
-
- -
|
83
|
+
- - ! '>='
|
52
84
|
- !ruby/object:Gem::Version
|
53
85
|
version: 1.5.0
|
54
86
|
type: :runtime
|
@@ -56,7 +88,7 @@ dependencies:
|
|
56
88
|
version_requirements: !ruby/object:Gem::Requirement
|
57
89
|
none: false
|
58
90
|
requirements:
|
59
|
-
- -
|
91
|
+
- - ! '>='
|
60
92
|
- !ruby/object:Gem::Version
|
61
93
|
version: 1.5.0
|
62
94
|
description: ! "A simulated-dice library, with flexible rules that allow dice systems
|
@@ -112,7 +144,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
112
144
|
version: '0'
|
113
145
|
segments:
|
114
146
|
- 0
|
115
|
-
hash:
|
147
|
+
hash: -3596350699515982404
|
116
148
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
117
149
|
none: false
|
118
150
|
requirements:
|
@@ -121,7 +153,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
121
153
|
version: '0'
|
122
154
|
segments:
|
123
155
|
- 0
|
124
|
-
hash:
|
156
|
+
hash: -3596350699515982404
|
125
157
|
requirements: []
|
126
158
|
rubyforge_project:
|
127
159
|
rubygems_version: 1.8.24
|
@@ -141,3 +173,4 @@ test_files:
|
|
141
173
|
- spec/probability_spec.rb
|
142
174
|
- spec/readme_spec.rb
|
143
175
|
- spec/reroll_rule_spec.rb
|
176
|
+
has_rdoc:
|