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,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