games_dice 0.0.3 → 0.0.5
Sign up to get free protection for your applications and to get access to all the features.
- data/games_dice.gemspec +4 -2
- data/lib/games_dice.rb +3 -0
- data/lib/games_dice/bunch.rb +275 -320
- data/lib/games_dice/complex_die.rb +226 -269
- data/lib/games_dice/constants.rb +16 -0
- data/lib/games_dice/dice.rb +43 -0
- data/lib/games_dice/die.rb +58 -92
- data/lib/games_dice/die_result.rb +3 -15
- data/lib/games_dice/map_rule.rb +41 -43
- data/lib/games_dice/probabilities.rb +97 -0
- data/lib/games_dice/reroll_rule.rb +41 -58
- data/lib/games_dice/version.rb +1 -1
- data/spec/bunch_spec.rb +196 -188
- data/spec/complex_die_spec.rb +77 -68
- data/spec/dice_spec.rb +34 -0
- data/spec/die_spec.rb +25 -29
- data/spec/probability_spec.rb +265 -0
- metadata +31 -8
@@ -0,0 +1,16 @@
|
|
1
|
+
module GamesDice
|
2
|
+
|
3
|
+
# reasons for making a reroll, and text explanation symbols for them
|
4
|
+
REROLL_TYPES = {
|
5
|
+
:basic => ',',
|
6
|
+
:reroll_add => '+',
|
7
|
+
:reroll_subtract => '-',
|
8
|
+
:reroll_replace => '|',
|
9
|
+
:reroll_use_best => '/',
|
10
|
+
:reroll_use_worst => '\\',
|
11
|
+
# These are not yet implemented:
|
12
|
+
# :reroll_new_die => '*',
|
13
|
+
# :reroll_new_keeper => '*',
|
14
|
+
}
|
15
|
+
|
16
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# models any combination of zero or more Bunches, plus a constant offset, summing them
|
2
|
+
# to create a total result when rolled
|
3
|
+
class GamesDice::Dice
|
4
|
+
# bunches is an Array of Hashes, each of which describes either a GamesDice::Bunch
|
5
|
+
# a Hash in the Array that describes a Bunch may contain any of the keys that can be used to initialize
|
6
|
+
# the Bunch, plus the following optional key:
|
7
|
+
# :multiplier => any Integer, but typically 1 or -1 to describe whether the Bunch total is to be added or subtracted
|
8
|
+
# offset is an Integer which will be added to the result when rolling all the bunches
|
9
|
+
# name can be any String, and is used to identify the dice being rolled.
|
10
|
+
def initialize( bunches, offset = 0, name = '' )
|
11
|
+
@name = name
|
12
|
+
@offset = offset
|
13
|
+
@bunches = bunches.map { |b| GamesDice::Bunch.new( b ) }
|
14
|
+
@bunch_multipliers = bunches.map { |b| b[:multiplier] || 1 }
|
15
|
+
@result = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# the string name as provided to the constructor, it will appear in explain_result
|
19
|
+
attr_reader :name
|
20
|
+
|
21
|
+
# an array of GamesDice::Bunch objects that together describe all the dice and roll-altering
|
22
|
+
# rules that apply to the GamesDice::Dice object
|
23
|
+
attr_reader :bunches
|
24
|
+
|
25
|
+
# an array of Integers, used to multiply result from each bunch when total results are summed
|
26
|
+
attr_reader :bunch_multipliers
|
27
|
+
|
28
|
+
# the integer offset that is added to the total result from all bunches
|
29
|
+
attr_reader :offset
|
30
|
+
|
31
|
+
# after calling #roll, this is set to the total integer value as calculated by simulating all the
|
32
|
+
# defined dice and their rules
|
33
|
+
attr_reader :result
|
34
|
+
|
35
|
+
# simulate dice roll. Returns integer final total, and also stores same value in #result
|
36
|
+
def roll
|
37
|
+
@result = @offset + @bunch_multipliers.zip(@bunches).inject(0) do |total,mb|
|
38
|
+
m,b = mb
|
39
|
+
total += m * b.roll
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
end # class Dice
|
data/lib/games_dice/die.rb
CHANGED
@@ -1,92 +1,58 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
# returns probability than a roll will produce a number less than or equal to target integer
|
61
|
-
def probability_le target
|
62
|
-
target = Integer(target)
|
63
|
-
return 1.0 if target >= @sides
|
64
|
-
return 0.0 if target < 1
|
65
|
-
return 1.0 * target/@sides
|
66
|
-
end
|
67
|
-
|
68
|
-
# returns probability than a roll will produce a number less than target integer
|
69
|
-
def probability_lt target
|
70
|
-
probability_le( Integer(target) - 1 )
|
71
|
-
end
|
72
|
-
|
73
|
-
# generates Integer between #min and #max, using rand()
|
74
|
-
def roll
|
75
|
-
if @prng
|
76
|
-
@result = @prng.rand(@sides) + 1
|
77
|
-
else
|
78
|
-
@result = rand(@sides) + 1
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# always nil, available for compatibility with ComplexDie
|
83
|
-
def rerolls
|
84
|
-
nil
|
85
|
-
end
|
86
|
-
|
87
|
-
# always nil, available for compatibility with ComplexDie
|
88
|
-
def maps
|
89
|
-
nil
|
90
|
-
end
|
91
|
-
end # class Die
|
92
|
-
end # module GamesDice
|
1
|
+
# basic die that rolls 1..N, typically with equal weighting for each value
|
2
|
+
# d = Die.new(6)
|
3
|
+
# d.roll # => Integer in range 1..6
|
4
|
+
# d.result # => same Integer value as returned by d.roll
|
5
|
+
class GamesDice::Die
|
6
|
+
# sides is e.g. 6 for traditional cubic die, or 20 for icosahedron.
|
7
|
+
# It can take non-traditional values, such as 7, but must be at least 1.
|
8
|
+
# prng is an object that has a rand(x) method. If provided, it will be called as
|
9
|
+
# prng.rand(sides), and is expected to return an integer in range 0...sides
|
10
|
+
def initialize( sides, prng=nil )
|
11
|
+
@sides = Integer(sides)
|
12
|
+
raise ArgumentError, "sides value #{sides} is too low, it must be 1 or greater" if @sides < 1
|
13
|
+
raise ArgumentError, "prng does not support the rand() method" if prng && ! prng.respond_to?(:rand)
|
14
|
+
@prng = prng
|
15
|
+
@result = nil
|
16
|
+
end
|
17
|
+
|
18
|
+
# number of sides as set by #new
|
19
|
+
attr_reader :sides
|
20
|
+
|
21
|
+
# integer result of last call to #roll, nil if no call made yet
|
22
|
+
attr_reader :result
|
23
|
+
|
24
|
+
# minimum possible value
|
25
|
+
def min
|
26
|
+
1
|
27
|
+
end
|
28
|
+
|
29
|
+
# maximum possible value
|
30
|
+
def max
|
31
|
+
@sides
|
32
|
+
end
|
33
|
+
|
34
|
+
# returns a GamesDice::Probabilities object that models distribution of the die
|
35
|
+
def probabilities
|
36
|
+
return @probabilities if @probabilities
|
37
|
+
@probabilities = GamesDice::Probabilities.for_fair_die( @sides )
|
38
|
+
end
|
39
|
+
|
40
|
+
# generates Integer between #min and #max, using rand()
|
41
|
+
def roll
|
42
|
+
if @prng
|
43
|
+
@result = @prng.rand(@sides) + 1
|
44
|
+
else
|
45
|
+
@result = rand(@sides) + 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# always nil, available for compatibility with ComplexDie
|
50
|
+
def rerolls
|
51
|
+
nil
|
52
|
+
end
|
53
|
+
|
54
|
+
# always nil, available for compatibility with ComplexDie
|
55
|
+
def maps
|
56
|
+
nil
|
57
|
+
end
|
58
|
+
end # class Die
|
@@ -10,21 +10,9 @@
|
|
10
10
|
class GamesDice::DieResult
|
11
11
|
include Comparable
|
12
12
|
|
13
|
-
# allowed reasons for making a roll, and symbol to use before number in #explain
|
14
|
-
REASONS = {
|
15
|
-
:basic => ',',
|
16
|
-
:reroll_add => '+',
|
17
|
-
:reroll_new_die => '*', # TODO: This needs to be flagged *before* value, and maybe linked to cause
|
18
|
-
:reroll_new_keeper => '*',
|
19
|
-
:reroll_subtract => '-',
|
20
|
-
:reroll_replace => '|',
|
21
|
-
:reroll_use_best => '/',
|
22
|
-
:reroll_use_worst => '\\',
|
23
|
-
}
|
24
|
-
|
25
13
|
# first_roll_result is optional value of first roll of the die
|
26
14
|
def initialize( first_roll_result=nil, first_roll_reason=:basic )
|
27
|
-
unless
|
15
|
+
unless GamesDice::REROLL_TYPES.has_key?(first_roll_reason)
|
28
16
|
raise ArgumentError, "Unrecognised reason for roll #{first_roll_reason}"
|
29
17
|
end
|
30
18
|
|
@@ -63,7 +51,7 @@ class GamesDice::DieResult
|
|
63
51
|
# roll_reason is an optional symbol description of why the roll was made
|
64
52
|
# #total and #value are calculated based on roll_reason
|
65
53
|
def add_roll( roll_result, roll_reason=:basic )
|
66
|
-
unless
|
54
|
+
unless GamesDice::REROLL_TYPES.has_key?(roll_reason)
|
67
55
|
raise ArgumentError, "Unrecognised reason for roll #{roll_reason}"
|
68
56
|
end
|
69
57
|
@rolls << Integer(roll_result)
|
@@ -109,7 +97,7 @@ class GamesDice::DieResult
|
|
109
97
|
text = @total.to_s
|
110
98
|
else
|
111
99
|
text = '[' + @rolls[0].to_s
|
112
|
-
text = (1..@rolls.length-1).inject( text ) { |so_far,i| so_far +
|
100
|
+
text = (1..@rolls.length-1).inject( text ) { |so_far,i| so_far + GamesDice::REROLL_TYPES[@roll_reasons[i]] + @rolls[i].to_s }
|
113
101
|
text += '] ' + @total.to_s
|
114
102
|
end
|
115
103
|
text += ' ' + @map_description if @mapped && @map_description && @map_description.length > 0
|
data/lib/games_dice/map_rule.rb
CHANGED
@@ -1,46 +1,44 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
raise ArgumentError, "trigger_value #{trigger_value.inspect} cannot respond to trigger_op #{trigger_value.inspect}"
|
11
|
-
end
|
12
|
-
|
13
|
-
@trigger_value = trigger_value
|
14
|
-
@trigger_op = trigger_op
|
15
|
-
raise TypeError if ! mapped_value.is_a? Numeric
|
16
|
-
@mapped_value = Integer(mapped_value)
|
17
|
-
@mapped_name = mapped_name.to_s
|
1
|
+
# helps model complex dice systems such as "count number of dice showing X or more"
|
2
|
+
class GamesDice::MapRule
|
3
|
+
|
4
|
+
# trigger_op, trigger_value, mapped_value and mapped_name set the attributes of the same name
|
5
|
+
# rule = RPGMapRule.new( 6, :<=, 1, 'Success' ) # score 1 for a result of 6 or more
|
6
|
+
def initialize trigger_value, trigger_op, mapped_value=0, mapped_name=''
|
7
|
+
|
8
|
+
if ! trigger_value.respond_to?( trigger_op )
|
9
|
+
raise ArgumentError, "trigger_value #{trigger_value.inspect} cannot respond to trigger_op #{trigger_value.inspect}"
|
18
10
|
end
|
19
11
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
12
|
+
@trigger_value = trigger_value
|
13
|
+
@trigger_op = trigger_op
|
14
|
+
raise TypeError if ! mapped_value.is_a? Numeric
|
15
|
+
@mapped_value = Integer(mapped_value)
|
16
|
+
@mapped_name = mapped_name.to_s
|
17
|
+
end
|
18
|
+
|
19
|
+
# an Integer value or Range that will be mapped to a single value. #trigger_op is called against it
|
20
|
+
attr_reader :trigger_value
|
21
|
+
|
22
|
+
# a valid symbol for a method, which will be called against #trigger_value with the current
|
23
|
+
# die result as a param. If the operator returns true for a specific die result, then the
|
24
|
+
# mapped_value will be used in its stead. If the operator returns nil or false, the map is not
|
25
|
+
# triggered. All other values will be returned as the result of the map (allowing you to
|
26
|
+
# specify any method that takes an integer as input and returns something else as the end result)
|
27
|
+
attr_reader :trigger_op
|
28
|
+
|
29
|
+
# an integer value
|
30
|
+
attr_reader :mapped_value
|
31
|
+
|
32
|
+
# a string description of the mapping, e.g. 'S' for a success
|
33
|
+
attr_reader :mapped_name
|
34
|
+
|
35
|
+
# runs the rule against test_value, returning either a new value, or nil if the rule does not apply
|
36
|
+
def map_from test_value
|
37
|
+
op_result = @trigger_value.send( @trigger_op, test_value )
|
38
|
+
return nil unless op_result
|
39
|
+
if op_result == true
|
40
|
+
return @mapped_value
|
44
41
|
end
|
45
|
-
|
46
|
-
end
|
42
|
+
return op_result
|
43
|
+
end
|
44
|
+
end # class MapRule
|
@@ -0,0 +1,97 @@
|
|
1
|
+
# utility class for calculating with probabilities for reuslts from GamesDice objects
|
2
|
+
class GamesDice::Probabilities
|
3
|
+
# prob_hash is a Hash with each key as an Integer, and the associated value being the probability
|
4
|
+
# of getting that value. It is not validated. Avoid using the default constructor if
|
5
|
+
# one of the factory methods or calculation methods already does what you need.
|
6
|
+
def initialize( prob_hash = { 0 => 1.0 } )
|
7
|
+
# This should *probably* be validated in future
|
8
|
+
@ph = prob_hash
|
9
|
+
end
|
10
|
+
|
11
|
+
# the Hash representation of probabilities. TODO: Hide this from public interface, but make it available
|
12
|
+
# to factory methods
|
13
|
+
attr_reader :ph
|
14
|
+
|
15
|
+
# a clone of probability data (as provided to constructor), safe to pass to methods that modify in place
|
16
|
+
def to_h
|
17
|
+
@ph.clone
|
18
|
+
end
|
19
|
+
|
20
|
+
def min
|
21
|
+
(@minmax ||= @ph.keys.minmax )[0]
|
22
|
+
end
|
23
|
+
|
24
|
+
def max
|
25
|
+
(@minmax ||= @ph.keys.minmax )[1]
|
26
|
+
end
|
27
|
+
|
28
|
+
# returns mean expected value as a Float
|
29
|
+
def expected
|
30
|
+
@expected ||= @ph.inject(0.0) { |accumulate,p| accumulate + p[0] * p[1] }
|
31
|
+
end
|
32
|
+
|
33
|
+
# returns Float probability fram Range (0.0..1.0) that a value chosen from the distribution will
|
34
|
+
# be equal to target integer
|
35
|
+
def p_eql target
|
36
|
+
@ph[ Integer(target) ] || 0.0
|
37
|
+
end
|
38
|
+
|
39
|
+
# returns Float probability fram Range (0.0..1.0) that a value chosen from the distribution will
|
40
|
+
# be a number greater than target integer
|
41
|
+
def p_gt target
|
42
|
+
p_ge( Integer(target) + 1 )
|
43
|
+
end
|
44
|
+
|
45
|
+
# returns Float probability fram Range (0.0..1.0) that a value chosen from the distribution will
|
46
|
+
# be a number greater than or equal to target integer
|
47
|
+
def p_ge target
|
48
|
+
target = Integer(target)
|
49
|
+
return @prob_ge[target] if @prob_ge && @prob_ge[target]
|
50
|
+
@prob_ge = {} unless @prob_ge
|
51
|
+
|
52
|
+
return 1.0 if target <= min
|
53
|
+
return 0.0 if target > max
|
54
|
+
@prob_ge[target] = @ph.select {|k,v| target <= k}.inject(0.0) {|so_far,pv| so_far + pv[1] }
|
55
|
+
end
|
56
|
+
|
57
|
+
# returns probability than a roll will produce a number less than or equal to target integer
|
58
|
+
def p_le target
|
59
|
+
target = Integer(target)
|
60
|
+
return @prob_le[target] if @prob_le && @prob_le[target]
|
61
|
+
@prob_le = {} unless @prob_le
|
62
|
+
|
63
|
+
return 1.0 if target >= max
|
64
|
+
return 0.0 if target < min
|
65
|
+
@prob_le[target] = @ph.select {|k,v| target >= k}.inject(0.0) {|so_far,pv| so_far + pv[1] }
|
66
|
+
end
|
67
|
+
|
68
|
+
# returns probability than a roll will produce a number less than target integer
|
69
|
+
def p_lt target
|
70
|
+
p_le( Integer(target) - 1 )
|
71
|
+
end
|
72
|
+
|
73
|
+
# constructor returns probability distrubution for a simple fair die
|
74
|
+
def self.for_fair_die sides
|
75
|
+
sides = Integer(sides)
|
76
|
+
raise ArgumentError, "sides must be at least 1" unless sides > 0
|
77
|
+
h = {}
|
78
|
+
p = 1.0/sides
|
79
|
+
(1..sides).each { |x| h[x] = p }
|
80
|
+
GamesDice::Probabilities.new( h )
|
81
|
+
end
|
82
|
+
|
83
|
+
# adding two probability distributions calculates a new distribution, representing what would
|
84
|
+
# happen if you created a random number using the sum of numbers from both distributions
|
85
|
+
def self.add_distributions pd_a, pd_b
|
86
|
+
h = {}
|
87
|
+
pd_a.ph.each do |ka,pa|
|
88
|
+
pd_b.ph.each do |kb,pb|
|
89
|
+
kc = ka + kb
|
90
|
+
pc = pa * pb
|
91
|
+
h[kc] = h[kc] ? h[kc] + pc : pc
|
92
|
+
end
|
93
|
+
end
|
94
|
+
GamesDice::Probabilities.new( h )
|
95
|
+
end
|
96
|
+
|
97
|
+
end # class Dice
|
@@ -1,64 +1,47 @@
|
|
1
|
-
|
1
|
+
# specifies when and how a ComplexDie should be re-rolled
|
2
|
+
class GamesDice::RerollRule
|
2
3
|
|
3
|
-
#
|
4
|
-
|
4
|
+
# trigger_op, trigger_value, type and limit set the attributes of the same name
|
5
|
+
# rule = GamesDice::RerollRule.new( 10, :<=, :reroll_add ) # an 'exploding' die
|
6
|
+
def initialize trigger_value, trigger_op, type, limit=nil
|
5
7
|
|
6
|
-
|
7
|
-
|
8
|
-
:reroll_add => '+',
|
9
|
-
:reroll_new_die => '*',
|
10
|
-
:reroll_new_keeper => '*',
|
11
|
-
:reroll_subtract => '-',
|
12
|
-
:reroll_replace => '|',
|
13
|
-
:reroll_use_best => '/',
|
14
|
-
:reroll_use_worst => '\\',
|
15
|
-
}
|
16
|
-
|
17
|
-
# trigger_op, trigger_value, type and limit set the attributes of the same name
|
18
|
-
# rule = GamesDice::RerollRule.new( 10, :<=, :reroll_add ) # an 'exploding' die
|
19
|
-
def initialize trigger_value, trigger_op, type, limit=nil
|
20
|
-
|
21
|
-
if ! trigger_value.respond_to?( trigger_op )
|
22
|
-
raise ArgumentError, "trigger_value #{trigger_value.inspect} cannot respond to trigger_op #{trigger_value.inspect}"
|
23
|
-
end
|
24
|
-
|
25
|
-
unless TYPES.has_key?(type)
|
26
|
-
raise ArgumentError, "Unrecognised reason for a re-roll #{type}"
|
27
|
-
end
|
28
|
-
|
29
|
-
@trigger_value = trigger_value
|
30
|
-
@trigger_op = trigger_op
|
31
|
-
@type = type
|
32
|
-
@limit = limit ? Integer(limit) : 1000
|
33
|
-
@limit = 1 if @type == :reroll_subtract
|
8
|
+
if ! trigger_value.respond_to?( trigger_op )
|
9
|
+
raise ArgumentError, "trigger_value #{trigger_value.inspect} cannot respond to trigger_op #{trigger_value.inspect}"
|
34
10
|
end
|
35
11
|
|
36
|
-
|
37
|
-
|
38
|
-
attr_reader :trigger_op
|
39
|
-
|
40
|
-
# an Integer value or Range that will cause the reroll to occur. #trigger_op is called against it
|
41
|
-
attr_reader :trigger_value
|
42
|
-
|
43
|
-
# a symbol, should be one of the following:
|
44
|
-
# :reroll_add - add result of reroll to running total, and ignore :reroll_subtract for this die
|
45
|
-
# :reroll_new_die - roll a new die of the same type
|
46
|
-
# :reroll_new_keeper - roll a new die of the same type, and keep the result
|
47
|
-
# :reroll_subtract - subtract result of reroll from running total, and reverse sense of any further :reroll_add results
|
48
|
-
# :reroll_replace - use the new value in place of existing value for the die
|
49
|
-
# :reroll_use_best - use the new value if it is higher than the erxisting value
|
50
|
-
# :reroll_use_worst - use the new value if it is higher than the existing value
|
51
|
-
attr_reader :type
|
52
|
-
|
53
|
-
# maximum number of times this rule should be applied to a single die. If type is:reroll_subtract,
|
54
|
-
# this value is always 1. A default value of 100 is used if not set in the constructor
|
55
|
-
attr_reader :limit
|
56
|
-
|
57
|
-
# runs the rule against a test value, returning truth value from calling the trigger_op method
|
58
|
-
def applies? test_value
|
59
|
-
@trigger_value.send( @trigger_op, test_value ) ? true : false
|
12
|
+
unless GamesDice::REROLL_TYPES.has_key?(type)
|
13
|
+
raise ArgumentError, "Unrecognised reason for a re-roll #{type}"
|
60
14
|
end
|
61
15
|
|
62
|
-
|
63
|
-
|
64
|
-
|
16
|
+
@trigger_value = trigger_value
|
17
|
+
@trigger_op = trigger_op
|
18
|
+
@type = type
|
19
|
+
@limit = limit ? Integer(limit) : 1000
|
20
|
+
@limit = 1 if @type == :reroll_subtract
|
21
|
+
end
|
22
|
+
|
23
|
+
# a valid symbol for a method, which will be called against #trigger_value with the current
|
24
|
+
# die result as a param. It should return true or false
|
25
|
+
attr_reader :trigger_op
|
26
|
+
|
27
|
+
# an Integer value or Range that will cause the reroll to occur. #trigger_op is called against it
|
28
|
+
attr_reader :trigger_value
|
29
|
+
|
30
|
+
# a symbol, should be one of the following:
|
31
|
+
# :reroll_add - add result of reroll to running total, and ignore :reroll_subtract for this die
|
32
|
+
# :reroll_subtract - subtract result of reroll from running total, and reverse sense of any further :reroll_add results
|
33
|
+
# :reroll_replace - use the new value in place of existing value for the die
|
34
|
+
# :reroll_use_best - use the new value if it is higher than the erxisting value
|
35
|
+
# :reroll_use_worst - use the new value if it is higher than the existing value
|
36
|
+
attr_reader :type
|
37
|
+
|
38
|
+
# maximum number of times this rule should be applied to a single die. If type is:reroll_subtract,
|
39
|
+
# this value is always 1. A default value of 100 is used if not set in the constructor
|
40
|
+
attr_reader :limit
|
41
|
+
|
42
|
+
# runs the rule against a test value, returning truth value from calling the trigger_op method
|
43
|
+
def applies? test_value
|
44
|
+
@trigger_value.send( @trigger_op, test_value ) ? true : false
|
45
|
+
end
|
46
|
+
|
47
|
+
end # class RerollRule
|