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