bag_of_holding 0.0.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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +37 -0
  3. data/Rakefile +17 -0
  4. data/bin/boh +28 -0
  5. data/lib/bag_of_holding/dice/addition_operation.rb +18 -0
  6. data/lib/bag_of_holding/dice/constant.rb +23 -0
  7. data/lib/bag_of_holding/dice/constant_result.rb +27 -0
  8. data/lib/bag_of_holding/dice/die.rb +39 -0
  9. data/lib/bag_of_holding/dice/die_result.rb +26 -0
  10. data/lib/bag_of_holding/dice/die_roller.rb +51 -0
  11. data/lib/bag_of_holding/dice/die_validator.rb +23 -0
  12. data/lib/bag_of_holding/dice/division_operation.rb +18 -0
  13. data/lib/bag_of_holding/dice/multiplication_operation.rb +18 -0
  14. data/lib/bag_of_holding/dice/operation.rb +45 -0
  15. data/lib/bag_of_holding/dice/operation_result.rb +38 -0
  16. data/lib/bag_of_holding/dice/parser.rb +50 -0
  17. data/lib/bag_of_holding/dice/pool.rb +58 -0
  18. data/lib/bag_of_holding/dice/pool_factory.rb +70 -0
  19. data/lib/bag_of_holding/dice/pool_result.rb +37 -0
  20. data/lib/bag_of_holding/dice/subtraction_operation.rb +18 -0
  21. data/lib/bag_of_holding/dice/transform.rb +59 -0
  22. data/lib/bag_of_holding/dice.rb +41 -0
  23. data/lib/bag_of_holding.rb +1 -0
  24. data/spec/dice/addition_operation_spec.rb +23 -0
  25. data/spec/dice/constant_result_spec.rb +48 -0
  26. data/spec/dice/constant_spec.rb +31 -0
  27. data/spec/dice/die_result_spec.rb +70 -0
  28. data/spec/dice/die_roller_spec.rb +76 -0
  29. data/spec/dice/die_spec.rb +111 -0
  30. data/spec/dice/die_validator_spec.rb +34 -0
  31. data/spec/dice/division_operation_spec.rb +35 -0
  32. data/spec/dice/multiplication_operation_spec.rb +27 -0
  33. data/spec/dice/operation_result_spec.rb +64 -0
  34. data/spec/dice/operation_spec.rb +55 -0
  35. data/spec/dice/parser_spec.rb +215 -0
  36. data/spec/dice/pool_factory_spec.rb +71 -0
  37. data/spec/dice/pool_result_spec.rb +63 -0
  38. data/spec/dice/pool_spec.rb +126 -0
  39. data/spec/dice/subtraction_operation_spec.rb +27 -0
  40. data/spec/dice/transform_spec.rb +237 -0
  41. data/spec/spec_helper.rb +24 -0
  42. metadata +186 -0
@@ -0,0 +1,76 @@
1
+ RSpec.describe BagOfHolding::Dice::DieRoller do
2
+ let(:die) { BagOfHolding::Dice::Die.new sides: 20, explode: 19, reroll: 2 }
3
+ subject { BagOfHolding::Dice::DieRoller.new die: die }
4
+
5
+ # Spec ::build
6
+ describe '::roll' do
7
+ it 'passes the argument to new' do
8
+ klass = BagOfHolding::Dice::DieRoller
9
+ die = double('Die', roll: true)
10
+ expect(klass).to receive(:new).with(die: die).and_return(subject)
11
+ klass.roll die
12
+ end
13
+ end
14
+
15
+ describe '#roll' do
16
+ context 'with no options' do
17
+ it 'returns the roll' do
18
+ allow(subject).to receive(:rng).and_return(5)
19
+ expect(subject.roll).to eq(
20
+ BagOfHolding::Dice::DieResult.new rolls: [5],
21
+ value: 5,
22
+ die: subject
23
+ )
24
+ end
25
+ end
26
+
27
+ context 'with rerolling die' do
28
+ it 're-rolls and adds a number larger if we are equal to the reroll' do
29
+ allow(subject).to receive(:rng).and_return(2, 3)
30
+ expect(subject.roll).to eq(
31
+ BagOfHolding::Dice::DieResult.new rolls: [2, 3],
32
+ value: 3,
33
+ die: subject
34
+ )
35
+ end
36
+
37
+ it 're-rolls and adds a number larger if we are less than the reroll' do
38
+ allow(subject).to receive(:rng).and_return(1, 5)
39
+ expect(subject.roll).to eq(
40
+ BagOfHolding::Dice::DieResult.new rolls: [1, 5],
41
+ value: 5,
42
+ die: subject
43
+ )
44
+ end
45
+ end
46
+
47
+ context 'with an exploding die' do
48
+ it 'accumulates and re-rolls if we are equal to the explode number' do
49
+ allow(subject).to receive(:rng).and_return(19, 3)
50
+ expect(subject.roll).to eq(
51
+ BagOfHolding::Dice::DieResult.new rolls: [19, 3],
52
+ value: 22,
53
+ die: subject
54
+ )
55
+ end
56
+
57
+ it 'accumulates and re-rolls if we are greater than the explode number' do
58
+ allow(subject).to receive(:rng).and_return(20, 5)
59
+ expect(subject.roll).to eq(
60
+ BagOfHolding::Dice::DieResult.new rolls: [20, 5],
61
+ value: 25,
62
+ die: subject
63
+ )
64
+ end
65
+
66
+ it 'accumulates and re-rolls if we continue to explode' do
67
+ allow(subject).to receive(:rng).and_return(20, 20, 4)
68
+ expect(subject.roll).to eq(
69
+ BagOfHolding::Dice::DieResult.new rolls: [20, 20, 4],
70
+ value: 44,
71
+ die: subject
72
+ )
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,111 @@
1
+ RSpec.describe BagOfHolding::Dice::Die do
2
+ subject { BagOfHolding::Dice::Die.new sides: 20, explode: 19, reroll: 2 }
3
+
4
+ describe '#initialize' do
5
+ it 'sets the attributes on the class' do
6
+ die = BagOfHolding::Dice::Die.new sides: 8, explode: 20, reroll: 1
7
+ expect(die.sides).to eq(8)
8
+ expect(die.explode).to eq(20)
9
+ expect(die.reroll).to eq(1)
10
+ end
11
+ end
12
+
13
+ describe '#sides=' do
14
+ it 'sets the number of sides on the die' do
15
+ subject.sides = 100
16
+ expect(subject.sides).to eq(100)
17
+ end
18
+ end
19
+
20
+ describe '#sides' do
21
+ it 'returns the number of sides on the die' do
22
+ expect(subject.sides).to eq(20)
23
+ end
24
+ end
25
+
26
+ describe '#explode=' do
27
+ it 'sets the number of explode on the die' do
28
+ subject.explode = 15
29
+ expect(subject.explode).to eq(15)
30
+ end
31
+ end
32
+
33
+ describe '#explode' do
34
+ it 'returns the number of explode on the die' do
35
+ expect(subject.explode).to eq(19)
36
+ end
37
+ end
38
+
39
+ describe '#reroll=' do
40
+ it 'sets the number of reroll on the die' do
41
+ subject.reroll = 2
42
+ expect(subject.reroll).to eq(2)
43
+ end
44
+ end
45
+
46
+ describe '#reroll' do
47
+ it 'returns the number of reroll on the die' do
48
+ expect(subject.reroll).to eq(2)
49
+ end
50
+ end
51
+
52
+ describe '#==' do
53
+ let(:other) do
54
+ BagOfHolding::Dice::Die.new sides: subject.sides,
55
+ explode: subject.explode,
56
+ reroll: subject.reroll
57
+ end
58
+
59
+ it 'returns true for a die with the same attributes' do
60
+ expect(subject == other).to eq(true)
61
+ end
62
+
63
+ it 'returns false for a die with different sides' do
64
+ other.sides = 10_000
65
+ expect(subject == other).to eq(false)
66
+ end
67
+
68
+ it 'returns false for a die with different explode' do
69
+ other.explode = 1
70
+ expect(subject == other).to eq(false)
71
+ end
72
+
73
+ it 'returns false for a die with different reroll' do
74
+ other.reroll = 4
75
+ expect(subject == other).to eq(false)
76
+ end
77
+ end
78
+
79
+ describe '#valid?' do
80
+ it 'passes itself to DieValidator' do
81
+ allow(BagOfHolding::Dice::DieValidator).to(
82
+ receive(:valid?).and_return(true)
83
+ )
84
+
85
+ expect(subject.valid?).to eq(true)
86
+ end
87
+
88
+ it 'returns true with a valid die' do
89
+ expect(subject.valid?).to eq(true)
90
+ end
91
+ end
92
+
93
+ describe '#roll' do
94
+ context 'with an invalid die' do
95
+ before { subject.sides = nil }
96
+
97
+ it 'raises a StandardError' do
98
+ allow(subject).to receive(:valid?).and_return(false)
99
+ expect { subject.roll }.to raise_error(StandardError)
100
+ end
101
+ end
102
+
103
+ it 'passes itself to DieRoller' do
104
+ result = BagOfHolding::Dice::DieResult.new rolls: [5],
105
+ value: 5,
106
+ die: subject
107
+ allow(BagOfHolding::Dice::DieRoller).to receive(:roll).and_return(result)
108
+ expect(subject.roll).to eq(result)
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,34 @@
1
+ RSpec.describe BagOfHolding::Dice::DieValidator do
2
+ let(:die) { BagOfHolding::Dice::Die.new sides: 20 }
3
+ subject { BagOfHolding::Dice::DieValidator.new die: die }
4
+
5
+ context 'with a reroll higher than explode' do
6
+ before do
7
+ die.explode = 10
8
+ die.reroll = 11
9
+ end
10
+
11
+ it 'returns false' do
12
+ expect(subject.valid?).to eq(false)
13
+ end
14
+ end
15
+
16
+ context 'with an explode equal to reroll' do
17
+ before do
18
+ die.explode = 10
19
+ die.reroll = 10
20
+ end
21
+
22
+ it 'returns false' do
23
+ expect(subject.valid?).to eq(false)
24
+ end
25
+ end
26
+
27
+ context 'with no sides set' do
28
+ before { die.sides = nil }
29
+
30
+ it 'returns false' do
31
+ expect(subject.valid?).to eq(false)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,35 @@
1
+ RSpec.describe BagOfHolding::Dice::DivisionOperation do
2
+ let(:left) { BagOfHolding::Dice::Constant.new value: 4 }
3
+ let(:right) { BagOfHolding::Dice::Constant.new value: 2 }
4
+
5
+ subject do
6
+ BagOfHolding::Dice::DivisionOperation.new left: left,
7
+ right: right
8
+ end
9
+
10
+ describe '::operator' do
11
+ it 'returns /' do
12
+ expect(BagOfHolding::Dice::DivisionOperation.operator).to eq('/')
13
+ end
14
+ end
15
+
16
+ describe '#value' do
17
+ it 'subtracts the right value from the left' do
18
+ expect(subject.value).to eq(2)
19
+ end
20
+
21
+ it 'rounds down any values' do
22
+ BagOfHolding::Dice::Constant.new value: 5
23
+ BagOfHolding::Dice::Constant.new value: 2
24
+ op = BagOfHolding::Dice::DivisionOperation.new left: left,
25
+ right: right
26
+ expect(op.value).to eq(2)
27
+ end
28
+ end
29
+
30
+ describe '#operator' do
31
+ it 'returns /' do
32
+ expect(subject.operator).to eq('/')
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,27 @@
1
+ RSpec.describe BagOfHolding::Dice::MultiplicationOperation do
2
+ let(:left) { BagOfHolding::Dice::Constant.new value: 4 }
3
+ let(:right) { BagOfHolding::Dice::Constant.new value: 2 }
4
+
5
+ subject do
6
+ BagOfHolding::Dice::MultiplicationOperation.new left: left,
7
+ right: right
8
+ end
9
+
10
+ describe '::operator' do
11
+ it 'returns *' do
12
+ expect(BagOfHolding::Dice::MultiplicationOperation.operator).to eq('*')
13
+ end
14
+ end
15
+
16
+ describe '#value' do
17
+ it 'subtracts the right value from the left' do
18
+ expect(subject.value).to eq(8)
19
+ end
20
+ end
21
+
22
+ describe '#operator' do
23
+ it 'returns *' do
24
+ expect(subject.operator).to eq('*')
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,64 @@
1
+ RSpec.describe BagOfHolding::Dice::OperationResult do
2
+ let(:left) { BagOfHolding::Dice::Constant.new value: 3 }
3
+ let(:right) { BagOfHolding::Dice::Constant.new value: 5 }
4
+
5
+ let(:operation) do
6
+ BagOfHolding::Dice::AdditionOperation.new left: left,
7
+ right: right
8
+ end
9
+
10
+ subject do
11
+ BagOfHolding::Dice::OperationResult.new left_result: left.roll,
12
+ operation: operation,
13
+ right_result: right.roll,
14
+ value: 8
15
+ end
16
+
17
+ describe '#initialize' do
18
+ it 'assigns the attributes that are passed' do
19
+ result = BagOfHolding::Dice::OperationResult.new left_result: left.roll,
20
+ operation: operation,
21
+ right_result: right.roll,
22
+ value: 8
23
+ expect(result.left_result).to eq(left.roll)
24
+ expect(result.operation).to eq(operation)
25
+ expect(result.right_result).to eq(right.roll)
26
+ expect(result.value).to eq(8)
27
+ end
28
+ end
29
+
30
+ describe '#to_s' do
31
+ it 'returns a string with the left result, operator and right result' do
32
+ expect(subject.to_s).to eq('3 + 5')
33
+ end
34
+ end
35
+
36
+ describe '#inspect' do
37
+ it 'returns the inspected left and right results and operator' do
38
+ expect(subject.to_s).to eq('3 + 5')
39
+ end
40
+ end
41
+
42
+ describe '#==' do
43
+ let(:other) do
44
+ BagOfHolding::Dice::OperationResult.new left_result: left.roll,
45
+ operation: operation,
46
+ right_result: right.roll,
47
+ value: 8
48
+ end
49
+
50
+ it 'returns true when other has matching attributes' do
51
+ expect(subject == other).to eq(true)
52
+ end
53
+
54
+ it 'returns false with a different left_result' do
55
+ other.left_result = right.roll
56
+ expect(subject == other).to eq(false)
57
+ end
58
+
59
+ it 'returns false with a different right_result' do
60
+ other.right_result = left.roll
61
+ expect(subject == other).to eq(false)
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,55 @@
1
+ RSpec.describe BagOfHolding::Dice::Operation do
2
+ let(:left) { BagOfHolding::Dice::Constant.new value: 3 }
3
+ let(:operator) { '+' }
4
+ let(:right) { BagOfHolding::Dice::Constant.new value: 5 }
5
+
6
+ subject do
7
+ BagOfHolding::Dice::Operation.new left: left,
8
+ right: right
9
+ end
10
+
11
+ describe '#initialize' do
12
+ it 'sets the attributes passed' do
13
+ op = BagOfHolding::Dice::Operation.new left: left,
14
+ right: right
15
+ expect(op.left).to eq(left)
16
+ expect(op.right).to eq(right)
17
+ end
18
+ end
19
+
20
+ describe '#roll' do
21
+ it 'returns an OperationResult' do
22
+ allow(subject).to receive(:value).and_return(8)
23
+
24
+ expect(subject.roll).to eq(
25
+ BagOfHolding::Dice::OperationResult.new(
26
+ left_result: left.roll,
27
+ right_result: right.roll,
28
+ operation: subject,
29
+ value: 8
30
+ )
31
+ )
32
+ end
33
+ end
34
+
35
+ describe '#==' do
36
+ let(:other) do
37
+ BagOfHolding::Dice::Operation.new left: left,
38
+ right: right
39
+ end
40
+
41
+ it 'returns true when matching attributes' do
42
+ expect(subject == other).to eq(true)
43
+ end
44
+
45
+ it 'returns false with a different left' do
46
+ other.left = BagOfHolding::Dice::Constant.new value: 30
47
+ expect(subject == other).to eq(false)
48
+ end
49
+
50
+ it 'returns false with a different right' do
51
+ other.left = BagOfHolding::Dice::Constant.new value: 40
52
+ expect(subject == other).to eq(false)
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,215 @@
1
+ RSpec.describe BagOfHolding::Dice::Parser do
2
+ subject { BagOfHolding::Dice::Parser.new }
3
+
4
+ describe '#parse' do
5
+ it 'parses 5' do
6
+ tree = subject.parse '5'
7
+ expect(tree).to match(
8
+ [constant: '5']
9
+ )
10
+ end
11
+
12
+ it 'parses 1d20' do
13
+ tree = subject.parse '1d20'
14
+ expect(tree).to match([
15
+ { count: '1', die: [{ sides: '20' }] }
16
+ ])
17
+ end
18
+
19
+ it 'parses 2d20k' do
20
+ tree = subject.parse '2d20k'
21
+ expect(tree).to match([
22
+ { count: '2', die: [{ sides: '20' }, { keep: nil }] }
23
+ ])
24
+ end
25
+
26
+ it 'parses 3d20k2' do
27
+ tree = subject.parse '3d20k2'
28
+ expect(tree).to match([
29
+ { count: '3', die: [{ sides: '20' }, { keep: '2' }] }
30
+ ])
31
+ end
32
+
33
+ it 'parses 1d20,1d8' do
34
+ tree = subject.parse '1d20,1d8'
35
+ expect(tree).to match(
36
+ [
37
+ { count: '1', die: [{ sides: '20' }] },
38
+ { count: '1', die: [{ sides: '8' }] }
39
+ ]
40
+ )
41
+ end
42
+
43
+ it 'parses d20' do
44
+ tree = subject.parse 'd20'
45
+ expect(tree).to match([
46
+ die: [{ sides: '20' }]
47
+ ])
48
+ end
49
+
50
+ it 'parses 3d8 (Attack)' do
51
+ tree = subject.parse '3d8 (Attack)'
52
+ expect(tree).to match([
53
+ { count: '3', die: [{ sides: '8' }], label: 'Attack' }
54
+ ])
55
+ end
56
+
57
+ it 'parses 1d20r' do
58
+ tree = subject.parse '1d20r'
59
+ expect(tree).to match([
60
+ { count: '1', die: [{ sides: '20' }, { reroll: nil }] }
61
+ ])
62
+ end
63
+
64
+ it 'parses 5+6' do
65
+ tree = subject.parse '5+6'
66
+ expect(tree).to match([
67
+ { left: { constant: '5' }, operator: '+', right: { constant: '6' } }
68
+ ])
69
+ end
70
+
71
+ it 'parses 5-2' do
72
+ tree = subject.parse '5-2'
73
+ expect(tree).to match([
74
+ { left: { constant: '5' }, operator: '-', right: { constant: '2' } }
75
+ ])
76
+ end
77
+
78
+ it 'parses 4/2' do
79
+ tree = subject.parse '4/2'
80
+ expect(tree).to match([
81
+ { left: { constant: '4' }, operator: '/', right: { constant: '2' } }
82
+ ])
83
+ end
84
+
85
+ it 'parses 4*2' do
86
+ tree = subject.parse '4*2'
87
+ expect(tree).to match([
88
+ { left: { constant: '4' }, operator: '*', right: { constant: '2' } }
89
+ ])
90
+ end
91
+
92
+ it 'parses 8, 9' do
93
+ tree = subject.parse '8, 9'
94
+ expect(tree).to match([
95
+ { constant: '8' },
96
+ { constant: '9' }
97
+ ])
98
+ end
99
+
100
+ it 'parses 1 + 2, 3' do
101
+ tree = subject.parse('1 + 2, 3')
102
+ expect(tree).to match([
103
+ { left: { constant: '1' }, operator: '+', right: { constant: '2' } },
104
+ { constant: '3' }
105
+ ])
106
+ end
107
+
108
+ it 'parses 1d20r (Defense)' do
109
+ tree = subject.parse('1d20r (Defense)')
110
+ expect(tree).to match([
111
+ {
112
+ count: '1',
113
+ die: [
114
+ { sides: '20' },
115
+ { reroll: nil }
116
+ ],
117
+ label: 'Defense' }
118
+ ])
119
+ end
120
+
121
+ it 'parses 1d20r2' do
122
+ tree = subject.parse('1d20r2')
123
+ expect(tree).to match([
124
+ { count: '1', die: [{ sides: '20' }, { reroll: '2' }] }
125
+ ])
126
+ end
127
+
128
+ it 'parses 1d20e' do
129
+ tree = subject.parse('1d20e')
130
+ expect(tree).to match([
131
+ { count: '1', die: [{ sides: '20' }, { explode: nil }] }
132
+ ])
133
+ end
134
+
135
+ it 'parses 1d20e18' do
136
+ tree = subject.parse('1d20e18')
137
+ expect(tree).to match([
138
+ { count: '1', die: [{ sides: '20' }, { explode: '18' }] }
139
+ ])
140
+ end
141
+
142
+ it 'parses 1d20e18r2' do
143
+ tree = subject.parse('1d20e18r2')
144
+ expect(tree).to match([
145
+ {
146
+ count: '1',
147
+ die: [
148
+ { sides: '20' },
149
+ { explode: '18' },
150
+ { reroll: '2' }
151
+ ]
152
+ }
153
+ ])
154
+ end
155
+
156
+ it 'parses 1d20r2e18' do
157
+ tree = subject.parse('1d20r2e18')
158
+ expect(tree).to match([
159
+ {
160
+ count: '1',
161
+ die: [
162
+ { sides: '20' },
163
+ { reroll: '2' },
164
+ { explode: '18' }
165
+ ]
166
+ }
167
+ ])
168
+ end
169
+
170
+ it 'parses 1d20 + 5' do
171
+ tree = subject.parse('1d20 + 5')
172
+ expect(tree).to match([
173
+ {
174
+ left: {
175
+ count: '1',
176
+ die: [
177
+ { sides: '20' }
178
+ ]
179
+ },
180
+ operator: '+',
181
+ right: {
182
+ constant: '5'
183
+ }
184
+ }
185
+ ])
186
+ end
187
+
188
+ it 'parses 2d6+ 1d4 + 2' do
189
+ tree = subject.parse('2d6+ 1d4 + 2')
190
+ expect(tree).to match([
191
+ {
192
+ left: {
193
+ count: '2',
194
+ die: [
195
+ { sides: '6' }
196
+ ]
197
+ },
198
+ operator: '+',
199
+ right: {
200
+ left: {
201
+ count: '1',
202
+ die: [
203
+ { sides: '4' }
204
+ ]
205
+ },
206
+ operator: '+',
207
+ right: {
208
+ constant: '2'
209
+ }
210
+ }
211
+ }
212
+ ])
213
+ end
214
+ end
215
+ end