games_dice 0.3.9 → 0.4.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.
@@ -1,189 +1,193 @@
1
- # This class models the output of GamesDice::ComplexDie.
2
- #
3
- # An object of the class represents the results of a roll of a ComplexDie, including any re-rolls and
4
- # value mapping.
5
- #
6
- # @example Building up a result manually
7
- # dr = GamesDice::DieResult.new
8
- # dr.add_roll 5
9
- # dr.add_roll 4, :reroll_replace
10
- # dr.value # => 4
11
- # dr.rolls # => [5, 4]
12
- # dr.roll_reasons # => [:basic, :reroll_replace]
13
- # # dr can behave as dr.value due to coercion and support for some operators
14
- # dr + 6 # => 10
15
- #
16
- # @example Using a result from GamesDice::ComplexDie
17
- # # An "exploding" six-sided die that needs a result of 8 to score "1 Success"
18
- # d = GamesDice::ComplexDie.new( 6, :rerolls => [[6, :<=, :reroll_add]], :maps => [[8, :<=, 1, 'Success']] )
19
- # # Generate result object by rolling the die
20
- # dr = d.roll
21
- # dr.rolls # => [6, 3]
22
- # dr.roll_reasons # => [:basic, :reroll_add]
23
- # dr.total # => 9
24
- # dr.value # => 1
25
- # dr.explain_value # => "[6+3] 9 Success"
26
- #
27
-
28
- class GamesDice::DieResult
29
- include Comparable
30
-
31
- # Creates new instance of GamesDice::DieResult. The object can be initialised "empty" or with a first result.
32
- # @param [Integer,nil] first_roll_result Value for first roll of the die.
33
- # @param [Symbol] first_roll_reason Reason for first roll of the die.
34
- # @return [GamesDice::DieResult]
35
- def initialize( first_roll_result=nil, first_roll_reason=:basic )
36
- unless GamesDice::REROLL_TYPES.has_key?(first_roll_reason)
37
- raise ArgumentError, "Unrecognised reason for roll #{first_roll_reason}"
38
- end
39
-
40
- if (first_roll_result)
41
- @rolls = [Integer(first_roll_result)]
42
- @roll_reasons = [first_roll_reason]
43
- @total = @rolls[0]
44
- else
45
- @rolls = []
46
- @roll_reasons = []
47
- @total = nil
48
- end
49
- @mapped = false
50
- @value = @total
51
- end
52
-
53
- # The individual die rolls that combined to generate this result.
54
- # @return [Array<Integer>] Un-processed values of each die roll used for this result.
55
- attr_reader :rolls
56
-
57
- # The individual reasons for each roll of the die. See GamesDice::RerollRule for allowed values.
58
- # @return [Array<Symbol>] Reasons for each die roll, indexes match the #rolls Array.
59
- attr_reader :roll_reasons
60
-
61
- # Combined result of all rolls, *before* mapping.
62
- # @return [Integer,nil]
63
- attr_reader :total
64
-
65
- # Combined result of all rolls, *after* mapping.
66
- # @return [Integer,nil]
67
- attr_reader :value
68
-
69
- # Whether or not #value has been mapped from #total.
70
- # @return [Boolean]
71
- attr_reader :mapped
72
-
73
- # Adds value from a new roll to the object. GamesDice::DieResult tracks reasons for the roll
74
- # and makes the correct adjustment to the total so far. Any mapped value is cleared.
75
- # @param [Integer] roll_result Value result from rolling the die.
76
- # @param [Symbol] roll_reason Reason for rolling the die.
77
- # @return [Integer] Total so far
78
- def add_roll( roll_result, roll_reason=:basic )
79
- unless GamesDice::REROLL_TYPES.has_key?(roll_reason)
80
- raise ArgumentError, "Unrecognised reason for roll #{roll_reason}"
81
- end
82
- @rolls << Integer(roll_result)
83
- @roll_reasons << roll_reason
84
- if @rolls.length == 1
85
- @total = 0
86
- end
87
-
88
- case roll_reason
89
- when :basic
90
- @total = roll_result
91
- when :reroll_add
92
- @total += roll_result
93
- when :reroll_subtract
94
- @total -= roll_result
95
- when :reroll_new_die
96
- @total = roll_result
97
- when :reroll_new_keeper
98
- @total = roll_result
99
- when :reroll_replace
100
- @total = roll_result
101
- when :reroll_use_best
102
- @total = [@value,roll_result].max
103
- when :reroll_use_worst
104
- @total = [@value,roll_result].min
105
- end
106
-
107
- @mapped = false
108
- @value = @total
109
- end
110
-
111
- # Sets value arbitrarily, and notes that the value has been mapped. Used by GamesDice::ComplexDie
112
- # when there are one or more GamesDice::MapRule objects to process for a die.
113
- # @param [Integer] to_value Replacement value.
114
- # @param [String] description Description of what the mapped value represents e.g. "Success"
115
- # @return [nil]
116
- def apply_map( to_value, description = '' )
117
- @mapped = true
118
- @value = to_value
119
- @map_description = description
120
- return
121
- end
122
-
123
- # Generates a text description of how #value is determined. If #value has been mapped, includes the
124
- # map description, but does not include the mapped value.
125
- # @return [String] Explanation of #value.
126
- def explain_value
127
- text = ''
128
- if @rolls.length < 2
129
- text = @total.to_s
130
- else
131
- text = '[' + @rolls[0].to_s
132
- text = (1..@rolls.length-1).inject( text ) { |so_far,i| so_far + GamesDice::REROLL_TYPES[@roll_reasons[i]] + @rolls[i].to_s }
133
- text += '] ' + @total.to_s
134
- end
135
- text += ' ' + @map_description if @mapped && @map_description && @map_description.length > 0
136
- return text
137
- end
138
-
139
- # @!visibility private
140
- # This is mis-named, it doesn't explain the total at all! It is used to generate summaries of keeper dice.
141
- def explain_total
142
- text = @total.to_s
143
- text += ' ' + @map_description if @mapped && @map_description && @map_description.length > 0
144
- return text
145
- end
146
-
147
- # @!visibility private
148
- # all coercions simply use #value (i.e. nil or a Fixnum)
149
- def coerce(thing)
150
- @value.coerce(thing)
151
- end
152
-
153
- # @!visibility private
154
- # addition uses #value
155
- def +(thing)
156
- @value + thing
157
- end
158
-
159
- # @!visibility private
160
- # subtraction uses #value
161
- def -(thing)
162
- @value - thing
163
- end
164
-
165
- # @!visibility private
166
- # multiplication uses #value
167
- def *(thing)
168
- @value * thing
169
- end
170
-
171
- # @!visibility private
172
- # comparison <=> uses #value
173
- def <=>(other)
174
- self.value <=> other
175
- end
176
-
177
- # This is a deep clone, all attributes are cloned.
178
- # @return [GamesDice::DieResult]
179
- def clone
180
- cloned = GamesDice::DieResult.new()
181
- cloned.instance_variable_set('@rolls', @rolls.clone)
182
- cloned.instance_variable_set('@roll_reasons', @roll_reasons.clone)
183
- cloned.instance_variable_set('@total', @total)
184
- cloned.instance_variable_set('@value', @value)
185
- cloned.instance_variable_set('@mapped', @mapped)
186
- cloned.instance_variable_set('@map_description', @map_description)
187
- cloned
188
- end
189
- end
1
+ # frozen_string_literal: true
2
+
3
+ module GamesDice
4
+ # This class models the output of GamesDice::ComplexDie.
5
+ #
6
+ # An object of the class represents the results of a roll of a ComplexDie, including any re-rolls and
7
+ # value mapping.
8
+ #
9
+ # @example Building up a result manually
10
+ # dr = GamesDice::DieResult.new
11
+ # dr.add_roll 5
12
+ # dr.add_roll 4, :reroll_replace
13
+ # dr.value # => 4
14
+ # dr.rolls # => [5, 4]
15
+ # dr.roll_reasons # => [:basic, :reroll_replace]
16
+ # # dr can behave as dr.value due to coercion and support for some operators
17
+ # dr + 6 # => 10
18
+ #
19
+ # @example Using a result from GamesDice::ComplexDie
20
+ # # An "exploding" six-sided die that needs a result of 8 to score "1 Success"
21
+ # d = GamesDice::ComplexDie.new( 6, :rerolls => [[6, :<=, :reroll_add]], :maps => [[8, :<=, 1, 'Success']] )
22
+ # # Generate result object by rolling the die
23
+ # dr = d.roll
24
+ # dr.rolls # => [6, 3]
25
+ # dr.roll_reasons # => [:basic, :reroll_add]
26
+ # dr.total # => 9
27
+ # dr.value # => 1
28
+ # dr.explain_value # => "[6+3] 9 Success"
29
+ #
30
+ class DieResult
31
+ include Comparable
32
+
33
+ # Creates new instance of GamesDice::DieResult. The object can be initialised "empty" or with a first result.
34
+ # @param [Integer,nil] first_roll_result Value for first roll of the die.
35
+ # @param [Symbol] first_roll_reason Reason for first roll of the die.
36
+ # @return [GamesDice::DieResult]
37
+ def initialize(first_roll_result = nil, first_roll_reason = :basic)
38
+ unless GamesDice::REROLL_TYPES.key?(first_roll_reason)
39
+ raise ArgumentError, "Unrecognised reason for roll #{first_roll_reason}"
40
+ end
41
+
42
+ if first_roll_result
43
+ @rolls = [Integer(first_roll_result)]
44
+ @roll_reasons = [first_roll_reason]
45
+ @total = @rolls[0]
46
+ else
47
+ @rolls = []
48
+ @roll_reasons = []
49
+ @total = nil
50
+ end
51
+ @mapped = false
52
+ @value = @total
53
+ end
54
+
55
+ # The individual die rolls that combined to generate this result.
56
+ # @return [Array<Integer>] Un-processed values of each die roll used for this result.
57
+ attr_reader :rolls
58
+
59
+ # The individual reasons for each roll of the die. See GamesDice::RerollRule for allowed values.
60
+ # @return [Array<Symbol>] Reasons for each die roll, indexes match the #rolls Array.
61
+ attr_reader :roll_reasons
62
+
63
+ # Combined result of all rolls, *before* mapping.
64
+ # @return [Integer,nil]
65
+ attr_reader :total
66
+
67
+ # Combined result of all rolls, *after* mapping.
68
+ # @return [Integer,nil]
69
+ attr_reader :value
70
+
71
+ # Whether or not #value has been mapped from #total.
72
+ # @return [Boolean]
73
+ attr_reader :mapped
74
+
75
+ # Adds value from a new roll to the object. GamesDice::DieResult tracks reasons for the roll
76
+ # and makes the correct adjustment to the total so far. Any mapped value is cleared.
77
+ # @param [Integer] roll_result Value result from rolling the die.
78
+ # @param [Symbol] roll_reason Reason for rolling the die.
79
+ # @return [Integer] Total so far
80
+ def add_roll(roll_result, roll_reason = :basic)
81
+ unless GamesDice::REROLL_TYPES.key?(roll_reason)
82
+ raise ArgumentError, "Unrecognised reason for roll #{roll_reason}"
83
+ end
84
+
85
+ @rolls << Integer(roll_result)
86
+ @roll_reasons << roll_reason
87
+ @total = 0 if @rolls.length == 1
88
+
89
+ case roll_reason
90
+ when :basic
91
+ @total = roll_result
92
+ when :reroll_add
93
+ @total += roll_result
94
+ when :reroll_subtract
95
+ @total -= roll_result
96
+ when :reroll_new_die
97
+ @total = roll_result
98
+ when :reroll_new_keeper
99
+ @total = roll_result
100
+ when :reroll_replace
101
+ @total = roll_result
102
+ when :reroll_use_best
103
+ @total = [@value, roll_result].max
104
+ when :reroll_use_worst
105
+ @total = [@value, roll_result].min
106
+ end
107
+
108
+ @mapped = false
109
+ @value = @total
110
+ end
111
+
112
+ # Sets value arbitrarily, and notes that the value has been mapped. Used by GamesDice::ComplexDie
113
+ # when there are one or more GamesDice::MapRule objects to process for a die.
114
+ # @param [Integer] to_value Replacement value.
115
+ # @param [String] description Description of what the mapped value represents e.g. "Success"
116
+ # @return [nil]
117
+ def apply_map(to_value, description = '')
118
+ @mapped = true
119
+ @value = to_value
120
+ @map_description = description
121
+ nil
122
+ end
123
+
124
+ # Generates a text description of how #value is determined. If #value has been mapped, includes the
125
+ # map description, but does not include the mapped value.
126
+ # @return [String] Explanation of #value.
127
+ def explain_value
128
+ text = ''
129
+ if @rolls.length < 2
130
+ text = @total.to_s
131
+ else
132
+ text = "[#{@rolls[0]}"
133
+ text = (1..@rolls.length - 1).inject(text) do |so_far, i|
134
+ so_far + GamesDice::REROLL_TYPES[@roll_reasons[i]] + @rolls[i].to_s
135
+ end
136
+ text += "] #{@total}"
137
+ end
138
+ text += " #{@map_description}" if @mapped && @map_description && @map_description.length.positive?
139
+ text
140
+ end
141
+
142
+ # @!visibility private
143
+ # This is mis-named, it doesn't explain the total at all! It is used to generate summaries of keeper dice.
144
+ def explain_total
145
+ text = @total.to_s
146
+ text += " #{@map_description}" if @mapped && @map_description && @map_description.length.positive?
147
+ text
148
+ end
149
+
150
+ # @!visibility private
151
+ # all coercions simply use #value (i.e. nil or a Integer)
152
+ def coerce(thing)
153
+ @value.coerce(thing)
154
+ end
155
+
156
+ # @!visibility private
157
+ # addition uses #value
158
+ def +(other)
159
+ @value + other
160
+ end
161
+
162
+ # @!visibility private
163
+ # subtraction uses #value
164
+ def -(other)
165
+ @value - other
166
+ end
167
+
168
+ # @!visibility private
169
+ # multiplication uses #value
170
+ def *(other)
171
+ @value * other
172
+ end
173
+
174
+ # @!visibility private
175
+ # comparison <=> uses #value
176
+ def <=>(other)
177
+ value <=> other
178
+ end
179
+
180
+ # This is a deep clone, all attributes are cloned.
181
+ # @return [GamesDice::DieResult]
182
+ def clone
183
+ cloned = GamesDice::DieResult.new
184
+ cloned.instance_variable_set('@rolls', @rolls.clone)
185
+ cloned.instance_variable_set('@roll_reasons', @roll_reasons.clone)
186
+ cloned.instance_variable_set('@total', @total)
187
+ cloned.instance_variable_set('@value', @value)
188
+ cloned.instance_variable_set('@mapped', @mapped)
189
+ cloned.instance_variable_set('@map_description', @map_description)
190
+ cloned
191
+ end
192
+ end
193
+ end
@@ -1,70 +1,72 @@
1
- # This class models rules that convert numbers shown on a die to values used in a game. A
2
- # common use for this is to count "successes" - dice that score a certain number or higher.
3
- #
4
- # An object of the class represents a single rule, such as "count a die result of 5 or more as 1
5
- # _success_".
6
- #
7
- # @example A rule for counting successes
8
- # rule = GamesDice::MapRule.new( 6, :<=, 1, 'Success' )
9
- # # Test how the rule applies . . .
10
- # rule.map_from 4 # => nil
11
- # rule.map_from 6 # => 1
12
- #
13
- # @example A rule for counting "fumbles" which reduce total successes
14
- # rule = GamesDice::MapRule.new( 1, :==, -1, 'Fumble' )
15
- # # Test how the rule applies . . .
16
- # rule.map_from 7 # => nil
17
- # rule.map_from 1 # => -1
18
- #
19
-
20
- class GamesDice::MapRule
21
-
22
- # Creates new instance of GamesDice::MapRule. The rule will be assessed as
23
- # trigger_value.send( trigger_op, x )
24
- # where x is the Integer value shown on a die.
25
- # @param [Integer,Range<Integer>,Object] trigger_value Any object is allowed, but typically an Integer
26
- # @param [Symbol] trigger_op A method of trigger_value that takes an Integer param and returns Boolean
27
- # @param [Integer] mapped_value The value to use in place of the trigger value
28
- # @param [String] mapped_name Name of mapped value, for use in descriptions
29
- # @return [GamesDice::MapRule]
30
- def initialize trigger_value, trigger_op, mapped_value=0, mapped_name=''
31
-
32
- if ! trigger_value.respond_to?( trigger_op )
33
- raise ArgumentError, "trigger_value #{trigger_value.inspect} cannot respond to trigger_op #{trigger_value.inspect}"
34
- end
35
-
36
- @trigger_value = trigger_value
37
- @trigger_op = trigger_op
38
- raise TypeError if ! mapped_value.is_a? Numeric
39
- @mapped_value = Integer(mapped_value)
40
- @mapped_name = mapped_name.to_s
41
- end
42
-
43
- # Trigger operation. How the rule is assessed against #trigger_value.
44
- # @return [Symbol] Method name to be sent to #trigger_value
45
- attr_reader :trigger_op
46
-
47
- # Trigger value. An object that will use #trigger_op to assess a die result for a reroll.
48
- # @return [Integer,Range,Object] Object that receives (#trigger_op, die_result)
49
- attr_reader :trigger_value
50
-
51
- # Value that a die will use after the value has been mapped.
52
- # @return [Integer]
53
- attr_reader :mapped_value
54
-
55
- # Name for mapped value, used in explanations.
56
- # @return [String]
57
- attr_reader :mapped_name
58
-
59
- # Assesses the rule against a die result value.
60
- # @param [Integer] test_value Value that is result of rolling a single die.
61
- # @return [Integer,nil] Replacement value, or nil if this rule doesn't apply
62
- def map_from test_value
63
- op_result = @trigger_value.send( @trigger_op, test_value )
64
- return nil unless op_result
65
- if op_result == true
66
- return @mapped_value
67
- end
68
- return op_result
69
- end
70
- end # class MapRule
1
+ # frozen_string_literal: true
2
+
3
+ module GamesDice
4
+ # This class models rules that convert numbers shown on a die to values used in a game. A
5
+ # common use for this is to count "successes" - dice that score a certain number or higher.
6
+ #
7
+ # An object of the class represents a single rule, such as "count a die result of 5 or more as 1
8
+ # _success_".
9
+ #
10
+ # @example A rule for counting successes
11
+ # rule = GamesDice::MapRule.new( 6, :<=, 1, 'Success' )
12
+ # # Test how the rule applies . . .
13
+ # rule.map_from 4 # => nil
14
+ # rule.map_from 6 # => 1
15
+ #
16
+ # @example A rule for counting "fumbles" which reduce total successes
17
+ # rule = GamesDice::MapRule.new( 1, :==, -1, 'Fumble' )
18
+ # # Test how the rule applies . . .
19
+ # rule.map_from 7 # => nil
20
+ # rule.map_from 1 # => -1
21
+ #
22
+ class MapRule
23
+ # Creates new instance of GamesDice::MapRule. The rule will be assessed as
24
+ # trigger_value.send( trigger_op, x )
25
+ # where x is the Integer value shown on a die.
26
+ # @param [Integer,Range<Integer>,Object] trigger_value Any object is allowed, but typically an Integer
27
+ # @param [Symbol] trigger_op A method of trigger_value that takes an Integer param and returns Boolean
28
+ # @param [Integer] mapped_value The value to use in place of the trigger value
29
+ # @param [String] mapped_name Name of mapped value, for use in descriptions
30
+ # @return [GamesDice::MapRule]
31
+ def initialize(trigger_value, trigger_op, mapped_value = 0, mapped_name = '')
32
+ unless trigger_value.respond_to?(trigger_op)
33
+ raise ArgumentError,
34
+ "trigger_value #{trigger_value.inspect} cannot respond to trigger_op #{trigger_value.inspect}"
35
+ end
36
+
37
+ @trigger_value = trigger_value
38
+ @trigger_op = trigger_op
39
+ raise TypeError unless mapped_value.is_a? Numeric
40
+
41
+ @mapped_value = Integer(mapped_value)
42
+ @mapped_name = mapped_name.to_s
43
+ end
44
+
45
+ # Trigger operation. How the rule is assessed against #trigger_value.
46
+ # @return [Symbol] Method name to be sent to #trigger_value
47
+ attr_reader :trigger_op
48
+
49
+ # Trigger value. An object that will use #trigger_op to assess a die result for a reroll.
50
+ # @return [Integer,Range,Object] Object that receives (#trigger_op, die_result)
51
+ attr_reader :trigger_value
52
+
53
+ # Value that a die will use after the value has been mapped.
54
+ # @return [Integer]
55
+ attr_reader :mapped_value
56
+
57
+ # Name for mapped value, used in explanations.
58
+ # @return [String]
59
+ attr_reader :mapped_name
60
+
61
+ # Assesses the rule against a die result value.
62
+ # @param [Integer] test_value Value that is result of rolling a single die.
63
+ # @return [Integer,nil] Replacement value, or nil if this rule doesn't apply
64
+ def map_from(test_value)
65
+ op_result = @trigger_value.send(@trigger_op, test_value)
66
+ return nil unless op_result
67
+ return @mapped_value if op_result == true
68
+
69
+ op_result
70
+ end
71
+ end
72
+ end
@@ -1,13 +1,18 @@
1
- class GamesDice::Probabilities
2
- # @!visibility private
3
- # Adds support for Marshal, via to_h and from_h methods
4
- def _dump *ignored
5
- Marshal.dump to_h
6
- end
7
-
8
- # @!visibility private
9
- def self._load buf
10
- h = Marshal.load buf
11
- from_h h
12
- end
13
- end
1
+ # frozen_string_literal: true
2
+
3
+ module GamesDice
4
+ # Probability calculations
5
+ class Probabilities
6
+ # @!visibility private
7
+ # Adds support for Marshal, via to_h and from_h methods
8
+ def _dump(*_ignored)
9
+ Marshal.dump to_h
10
+ end
11
+
12
+ # @!visibility private
13
+ def self._load(buf)
14
+ h = Marshal.load buf
15
+ from_h h
16
+ end
17
+ end
18
+ end