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.
- checksums.yaml +7 -0
- data/README.md +37 -0
- data/Rakefile +17 -0
- data/bin/boh +28 -0
- data/lib/bag_of_holding/dice/addition_operation.rb +18 -0
- data/lib/bag_of_holding/dice/constant.rb +23 -0
- data/lib/bag_of_holding/dice/constant_result.rb +27 -0
- data/lib/bag_of_holding/dice/die.rb +39 -0
- data/lib/bag_of_holding/dice/die_result.rb +26 -0
- data/lib/bag_of_holding/dice/die_roller.rb +51 -0
- data/lib/bag_of_holding/dice/die_validator.rb +23 -0
- data/lib/bag_of_holding/dice/division_operation.rb +18 -0
- data/lib/bag_of_holding/dice/multiplication_operation.rb +18 -0
- data/lib/bag_of_holding/dice/operation.rb +45 -0
- data/lib/bag_of_holding/dice/operation_result.rb +38 -0
- data/lib/bag_of_holding/dice/parser.rb +50 -0
- data/lib/bag_of_holding/dice/pool.rb +58 -0
- data/lib/bag_of_holding/dice/pool_factory.rb +70 -0
- data/lib/bag_of_holding/dice/pool_result.rb +37 -0
- data/lib/bag_of_holding/dice/subtraction_operation.rb +18 -0
- data/lib/bag_of_holding/dice/transform.rb +59 -0
- data/lib/bag_of_holding/dice.rb +41 -0
- data/lib/bag_of_holding.rb +1 -0
- data/spec/dice/addition_operation_spec.rb +23 -0
- data/spec/dice/constant_result_spec.rb +48 -0
- data/spec/dice/constant_spec.rb +31 -0
- data/spec/dice/die_result_spec.rb +70 -0
- data/spec/dice/die_roller_spec.rb +76 -0
- data/spec/dice/die_spec.rb +111 -0
- data/spec/dice/die_validator_spec.rb +34 -0
- data/spec/dice/division_operation_spec.rb +35 -0
- data/spec/dice/multiplication_operation_spec.rb +27 -0
- data/spec/dice/operation_result_spec.rb +64 -0
- data/spec/dice/operation_spec.rb +55 -0
- data/spec/dice/parser_spec.rb +215 -0
- data/spec/dice/pool_factory_spec.rb +71 -0
- data/spec/dice/pool_result_spec.rb +63 -0
- data/spec/dice/pool_spec.rb +126 -0
- data/spec/dice/subtraction_operation_spec.rb +27 -0
- data/spec/dice/transform_spec.rb +237 -0
- data/spec/spec_helper.rb +24 -0
- metadata +186 -0
@@ -0,0 +1,58 @@
|
|
1
|
+
module BagOfHolding
|
2
|
+
module Dice
|
3
|
+
# Internal: Contains information about the number of dice in a pool, the
|
4
|
+
# description of those dice and a label if it has one.
|
5
|
+
class Pool
|
6
|
+
attr_accessor :count, :die, :label, :keep
|
7
|
+
|
8
|
+
def initialize(count: nil, die: nil, label: nil, keep: nil)
|
9
|
+
self.count = count
|
10
|
+
self.die = die
|
11
|
+
self.label = label
|
12
|
+
self.keep = keep
|
13
|
+
end
|
14
|
+
|
15
|
+
def ==(other)
|
16
|
+
begin
|
17
|
+
return false unless count == other.count
|
18
|
+
return false unless die == other.die
|
19
|
+
return false unless label == other.label
|
20
|
+
rescue NoMethodError
|
21
|
+
return false
|
22
|
+
end
|
23
|
+
|
24
|
+
true
|
25
|
+
end
|
26
|
+
|
27
|
+
def roll
|
28
|
+
BagOfHolding::Dice::PoolResult.new die_results: die_results,
|
29
|
+
value: value,
|
30
|
+
pool: self
|
31
|
+
end
|
32
|
+
|
33
|
+
def to_s
|
34
|
+
"#{count}#{die}"
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def value
|
40
|
+
if keep.nil?
|
41
|
+
@value = die_values.reduce(&:+)
|
42
|
+
else
|
43
|
+
@value = die_values.sort.reverse.slice(0, keep).reduce(&:+)
|
44
|
+
end
|
45
|
+
|
46
|
+
@value
|
47
|
+
end
|
48
|
+
|
49
|
+
def die_values
|
50
|
+
@die_values ||= die_results.map(&:value)
|
51
|
+
end
|
52
|
+
|
53
|
+
def die_results
|
54
|
+
@die_results ||= count.times.map { die.roll }
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module BagOfHolding
|
2
|
+
module Dice
|
3
|
+
# Internal: This class takes the parser output describing a dice pool
|
4
|
+
# and builds the correct pool object.
|
5
|
+
class PoolFactory
|
6
|
+
def self.build(count: nil, die:, label: nil)
|
7
|
+
new(count: count, die: die, label: label).build
|
8
|
+
end
|
9
|
+
|
10
|
+
DIE_OPTIONS = [:sides, :reroll, :explode]
|
11
|
+
|
12
|
+
DIE_DEFAULTS = {
|
13
|
+
explode: ->(attrs) { attrs[:sides] },
|
14
|
+
reroll: ->(_attrs) { 1 }
|
15
|
+
}
|
16
|
+
|
17
|
+
def initialize(count: nil, die:, label: nil)
|
18
|
+
self.raw_count = count
|
19
|
+
self.raw_die = die
|
20
|
+
self.raw_label = label
|
21
|
+
end
|
22
|
+
|
23
|
+
def build
|
24
|
+
BagOfHolding::Dice::Pool.new count: pool_count,
|
25
|
+
die: die,
|
26
|
+
label: raw_label,
|
27
|
+
keep: keep
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
attr_accessor :raw_count, :raw_die, :raw_label
|
33
|
+
|
34
|
+
def keep
|
35
|
+
keep_option = raw_die.find { |o| o.keys.first == :keep }
|
36
|
+
return nil if keep_option.nil?
|
37
|
+
|
38
|
+
value = keep_option.values.first
|
39
|
+
value.nil? ? 1 : value.to_i
|
40
|
+
end
|
41
|
+
|
42
|
+
def pool_count
|
43
|
+
raw_count.nil? ? 1 : raw_count.to_i
|
44
|
+
end
|
45
|
+
|
46
|
+
def die
|
47
|
+
BagOfHolding::Dice::Die.new die_attrs
|
48
|
+
end
|
49
|
+
|
50
|
+
def die_attrs
|
51
|
+
die_options.reduce({}) do |attrs, attr|
|
52
|
+
attrs.merge die_attr(attr, attrs)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def die_attr(attr, attrs)
|
57
|
+
name = attr.keys.first
|
58
|
+
|
59
|
+
value = attr.values.first
|
60
|
+
value = value.nil? ? DIE_DEFAULTS[name].call(attrs) : value.to_i
|
61
|
+
|
62
|
+
{ name => value }
|
63
|
+
end
|
64
|
+
|
65
|
+
def die_options
|
66
|
+
raw_die.select { |attr| DIE_OPTIONS.include? attr.keys.first }
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module BagOfHolding
|
2
|
+
module Dice
|
3
|
+
# Internal: contains the results of a rolled pool and the die rolls that
|
4
|
+
# got us there.
|
5
|
+
class PoolResult
|
6
|
+
attr_accessor :die_results, :value, :pool
|
7
|
+
|
8
|
+
def initialize(die_results: nil, value: nil, pool: nil)
|
9
|
+
self.die_results = die_results
|
10
|
+
self.value = value
|
11
|
+
self.pool = pool
|
12
|
+
end
|
13
|
+
|
14
|
+
def ==(other)
|
15
|
+
return false if value != other.value
|
16
|
+
return false if die_results != other.die_results
|
17
|
+
return false if pool != other.pool
|
18
|
+
|
19
|
+
true
|
20
|
+
end
|
21
|
+
|
22
|
+
def to_s
|
23
|
+
"#{value} (#{pool})"
|
24
|
+
end
|
25
|
+
|
26
|
+
def inspect
|
27
|
+
"#{value} (#{pool})".tap do |str|
|
28
|
+
die_results.each do |die_result|
|
29
|
+
die_result.rolls.each do |roll|
|
30
|
+
str << " [#{roll}]"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module BagOfHolding
|
2
|
+
module Dice
|
3
|
+
# Internal: Encapsulates subtracting two expressions
|
4
|
+
class SubtractionOperation < Operation
|
5
|
+
def self.operator
|
6
|
+
'-'
|
7
|
+
end
|
8
|
+
|
9
|
+
def value
|
10
|
+
left_result.value - right_result.value
|
11
|
+
end
|
12
|
+
|
13
|
+
def operator
|
14
|
+
'-'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'parslet'
|
2
|
+
|
3
|
+
module BagOfHolding
|
4
|
+
module Dice
|
5
|
+
# Internal: Transform a parsed dice string into a callable structure
|
6
|
+
class Transform < Parslet::Transform
|
7
|
+
rule(constant: simple(:value)) do |dict|
|
8
|
+
build_const value: dict[:value]
|
9
|
+
end
|
10
|
+
|
11
|
+
# Dice pool with count, without label
|
12
|
+
rule(count: simple(:count), die: subtree(:die)) do |dict|
|
13
|
+
build_pool count: dict[:count], die: dict[:die]
|
14
|
+
end
|
15
|
+
|
16
|
+
# Dice pool without a count, without label
|
17
|
+
rule(die: subtree(:die)) do |dict|
|
18
|
+
build_pool die: dict[:die]
|
19
|
+
end
|
20
|
+
|
21
|
+
# Dice pool with count, label
|
22
|
+
rule(
|
23
|
+
count: simple(:count), die: subtree(:die), label: simple(:label)
|
24
|
+
) do |dict|
|
25
|
+
build_pool count: dict[:count],
|
26
|
+
die: dict[:die],
|
27
|
+
label: dict[:label]
|
28
|
+
end
|
29
|
+
|
30
|
+
rule(
|
31
|
+
left: simple(:left), operator: simple(:operator), right: simple(:right)
|
32
|
+
) do |dict|
|
33
|
+
build_operation left: dict[:left],
|
34
|
+
operator: dict[:operator],
|
35
|
+
right: dict[:right]
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.build_operation(left:, operator:, right:)
|
39
|
+
operations = [
|
40
|
+
AdditionOperation, SubtractionOperation,
|
41
|
+
DivisionOperation, MultiplicationOperation
|
42
|
+
]
|
43
|
+
|
44
|
+
operations.find { |o| o.operator == operator }.new left: left,
|
45
|
+
right: right
|
46
|
+
end
|
47
|
+
|
48
|
+
def self.build_const(value:)
|
49
|
+
BagOfHolding::Dice::Constant.new value: Integer(value)
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.build_pool(count: nil, die:, label: nil)
|
53
|
+
BagOfHolding::Dice::PoolFactory.build count: count,
|
54
|
+
die: die,
|
55
|
+
label: label
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'bag_of_holding/dice/constant'
|
2
|
+
require 'bag_of_holding/dice/constant_result'
|
3
|
+
|
4
|
+
require 'bag_of_holding/dice/die'
|
5
|
+
require 'bag_of_holding/dice/die_validator'
|
6
|
+
require 'bag_of_holding/dice/die_roller'
|
7
|
+
require 'bag_of_holding/dice/die_result'
|
8
|
+
|
9
|
+
require 'bag_of_holding/dice/operation'
|
10
|
+
require 'bag_of_holding/dice/addition_operation'
|
11
|
+
require 'bag_of_holding/dice/subtraction_operation'
|
12
|
+
require 'bag_of_holding/dice/division_operation'
|
13
|
+
require 'bag_of_holding/dice/multiplication_operation'
|
14
|
+
require 'bag_of_holding/dice/operation_result'
|
15
|
+
|
16
|
+
require 'bag_of_holding/dice/parser'
|
17
|
+
|
18
|
+
require 'bag_of_holding/dice/pool'
|
19
|
+
require 'bag_of_holding/dice/pool_factory'
|
20
|
+
require 'bag_of_holding/dice/pool_result'
|
21
|
+
|
22
|
+
require 'bag_of_holding/dice/transform'
|
23
|
+
|
24
|
+
module BagOfHolding
|
25
|
+
# Public: Bag of Holding dice bag. All you need for your myriad of dice
|
26
|
+
# rolling needs.
|
27
|
+
#
|
28
|
+
# Examples
|
29
|
+
#
|
30
|
+
# BagOfHolding::Dice.roll '1d20+5'
|
31
|
+
# # => [BagOfHolding::Dice::OperationResult]
|
32
|
+
module Dice
|
33
|
+
class << self
|
34
|
+
def roll(str)
|
35
|
+
tree = Parser.new.parse str
|
36
|
+
rollables = Transform.new.apply(tree)
|
37
|
+
rollables.map(&:roll)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'bag_of_holding/dice'
|
@@ -0,0 +1,23 @@
|
|
1
|
+
RSpec.describe BagOfHolding::Dice::AdditionOperation do
|
2
|
+
let(:left) { BagOfHolding::Dice::Constant.new value: 3 }
|
3
|
+
let(:right) { BagOfHolding::Dice::Constant.new value: 5 }
|
4
|
+
subject { BagOfHolding::Dice::AdditionOperation.new left: left, right: right }
|
5
|
+
|
6
|
+
describe '::operator' do
|
7
|
+
it 'returns +' do
|
8
|
+
expect(BagOfHolding::Dice::AdditionOperation.operator).to eq('+')
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
describe '#value' do
|
13
|
+
it 'adds the left and right values' do
|
14
|
+
expect(subject.value).to eq(8)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe '#operator' do
|
19
|
+
it 'returns +' do
|
20
|
+
expect(subject.operator).to eq('+')
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
RSpec.describe BagOfHolding::Dice::ConstantResult do
|
2
|
+
subject { BagOfHolding::Dice::ConstantResult.new value: 5 }
|
3
|
+
|
4
|
+
describe '::new' do
|
5
|
+
it 'sets the attributes on the class' do
|
6
|
+
const = BagOfHolding::Dice::ConstantResult.new value: 9
|
7
|
+
expect(const.value).to eq(9)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#value' do
|
12
|
+
it 'returns the value' do
|
13
|
+
expect(subject.value).to eq(5)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#value=' do
|
18
|
+
it 'sets the value' do
|
19
|
+
subject.value = 7
|
20
|
+
expect(subject.value).to eq(7)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#to_s' do
|
25
|
+
it 'returns the value as a string' do
|
26
|
+
expect(subject.to_s).to eq('5')
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
describe '#inspect' do
|
31
|
+
it 'returns the value as a string' do
|
32
|
+
expect(subject.inspect).to eq('5')
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe '#==' do
|
37
|
+
let(:other) { BagOfHolding::Dice::ConstantResult.new value: 5 }
|
38
|
+
|
39
|
+
it 'returns true when the other constant has a matching value' do
|
40
|
+
expect(subject == other).to eq(true)
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'returns false when the other constant has a different value' do
|
44
|
+
other.value = 99
|
45
|
+
expect(subject == other).to eq(false)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
RSpec.describe BagOfHolding::Dice::Constant do
|
2
|
+
subject { BagOfHolding::Dice::Constant.new value: 5 }
|
3
|
+
|
4
|
+
describe '::new' do
|
5
|
+
it 'sets the attributes on the class' do
|
6
|
+
const = BagOfHolding::Dice::Constant.new value: 9
|
7
|
+
expect(const.value).to eq(9)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
describe '#roll' do
|
12
|
+
it 'always rolls to the value' do
|
13
|
+
expect(subject.roll).to eq(
|
14
|
+
BagOfHolding::Dice::ConstantResult.new value: subject.value
|
15
|
+
)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#==' do
|
20
|
+
let(:other) { BagOfHolding::Dice::Constant.new value: 5 }
|
21
|
+
|
22
|
+
it 'returns true when the other constant has a matching value' do
|
23
|
+
expect(subject == other).to eq(true)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'returns false when the other constant has a different value' do
|
27
|
+
other.value = 9
|
28
|
+
expect(subject == other).to eq(false)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
RSpec.describe BagOfHolding::Dice::DieResult do
|
2
|
+
let(:die) { BagOfHolding::Dice::Die.new sides: 4 }
|
3
|
+
subject do
|
4
|
+
BagOfHolding::Dice::DieResult.new rolls: [1, 4],
|
5
|
+
value: 4,
|
6
|
+
die: die
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#initialize' do
|
10
|
+
it 'sets the rolls and value attributes' do
|
11
|
+
result = BagOfHolding::Dice::DieResult.new rolls: [1, 20],
|
12
|
+
value: 20,
|
13
|
+
die: die
|
14
|
+
|
15
|
+
expect(result.rolls).to eq([1, 20])
|
16
|
+
expect(result.value).to eq(20)
|
17
|
+
expect(result.die).to eq(die)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
describe '#add_roll' do
|
22
|
+
it 'adds the roll to the end of the rolls' do
|
23
|
+
subject.add_roll 8
|
24
|
+
expect(subject.rolls).to eq([1, 4, 8])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#rolls' do
|
29
|
+
it 'returns the array of roll results' do
|
30
|
+
expect(subject.rolls).to eq([1, 4])
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
describe '#value' do
|
35
|
+
it 'returns the result value' do
|
36
|
+
expect(subject.value).to eq(4)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
describe '#==' do
|
41
|
+
let(:other) do
|
42
|
+
BagOfHolding::Dice::DieResult.new rolls: [1, 4],
|
43
|
+
value: 4,
|
44
|
+
die: die
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'returns true when passed another result with equal rolls and value' do
|
48
|
+
expect(subject == other).to eq(true)
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'returns true when passed another result with equal rolls and value' do
|
52
|
+
expect(subject == other).to eq(true)
|
53
|
+
end
|
54
|
+
|
55
|
+
it 'returns false when the other result has a different value' do
|
56
|
+
other.value = 1
|
57
|
+
expect(subject == other).to eq(false)
|
58
|
+
end
|
59
|
+
|
60
|
+
it 'returns false when the other result has different rolls' do
|
61
|
+
other.rolls = [4]
|
62
|
+
expect(subject == other).to eq(false)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'returns false when the other result has a different die' do
|
66
|
+
other.die = BagOfHolding::Dice::Die.new sides: 8
|
67
|
+
expect(subject == other).to eq(false)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|