games_dice 0.0.3 → 0.0.5
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/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
|