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