bag_of_holding 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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