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
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 39a78a5204d2098a8fecd8cccd492ba45139f81b
4
+ data.tar.gz: 6d51bf38c65f02d9ce32491429ee79a113063e38
5
+ SHA512:
6
+ metadata.gz: b2ca5daf352b62082f721add1a6bc2a8b4688503ab86c3f80abe9ffaaa8c641959c3740af2bec702d024e0ba8ef8fba04149b9c04c54dcb56a0535b3b813ef43
7
+ data.tar.gz: b41b2a0dafea439a03a1ed06d1c284d51b95651d383df9f175697e41208a2a0a6c0d4dbea7fce9ad8c6daa818bf1ae465f7c152ffa79f3e4e79304dd10da77bb
data/README.md ADDED
@@ -0,0 +1,37 @@
1
+ # Bag of Holding [![Code Climate](https://codeclimate.com/github/packetmonkey/bag_of_holding/badges/gpa.svg)](https://codeclimate.com/github/packetmonkey/bag_of_holding)
2
+
3
+ Bag of Holding is a command line utility and library to help out with various
4
+ table top gaming needs. It currently contains a dice roller but will grow to
5
+ include a dungeon generator, various card deck functions and anything else I
6
+ think to include.
7
+
8
+ ## Install
9
+ Currently you can clone the repository directly to use it. I will eventually
10
+ distribute it as a ruby gem.
11
+ ```sh
12
+ git clone https://github.com/packetmonkey/bag_of_holding.git
13
+ cd bag_of_holding
14
+ bundle
15
+ ```
16
+
17
+ ## Usage
18
+ ```sh
19
+ ./bin/boh roll 1d20+5
20
+ 8 = 3 (1d20) + 5
21
+ ```
22
+
23
+ ## Found a bug?
24
+ Open a [github issue](https://github.com/packetmonkey/bag_of_holding/issues)
25
+
26
+ ## Contributing & Development
27
+ 1. Fork the project.
28
+ 2. Make your feature addition or bug fix. All specs should pass.
29
+ 3. Add specs for your changes.
30
+ 4. Commit
31
+ 5. Send a pull request. Bonus points for topic branches.
32
+
33
+ ## Licence
34
+ Bag of Holding is released under the [MIT Licence](http://choosealicense.com/licenses/mit/)
35
+
36
+ ## Authors
37
+ Bag of Holding is written and maintained by Evan Sharp.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require 'rspec/core/rake_task'
2
+ RSpec::Core::RakeTask.new(:spec)
3
+
4
+ require 'rubocop/rake_task'
5
+ RuboCop::RakeTask.new
6
+
7
+ $LOAD_PATH << File.expand_path('../lib', __FILE__)
8
+ require 'bag_of_holding'
9
+
10
+ desc 'Run a console with BagOfHolding loaded'
11
+ task :console do
12
+ require 'pry'
13
+ ARGV.clear
14
+ Pry.start
15
+ end
16
+
17
+ task default: [:spec, :rubocop]
data/bin/boh ADDED
@@ -0,0 +1,28 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'rubygems'
4
+ require 'bundler/setup'
5
+
6
+ require 'thor'
7
+
8
+ $LOAD_PATH << File.expand_path('../../lib', __FILE__)
9
+ require 'bag_of_holding'
10
+
11
+ module BagOfHolding
12
+ # Internal: Externally executable entry points to the library
13
+ class CLI < Thor
14
+ option :verbose, type: :boolean
15
+ desc 'roll <dice string>', 'Roll dice pools specified in <dice string>'
16
+ def roll(str)
17
+ ::BagOfHolding::Dice.roll(str).each do |result|
18
+ if options[:verbose]
19
+ puts "#{result.value} = #{result.inspect}"
20
+ else
21
+ puts "#{result.value} = #{result}"
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ BagOfHolding::CLI.start(ARGV)
@@ -0,0 +1,18 @@
1
+ module BagOfHolding
2
+ module Dice
3
+ # Internal: Encapsulates an operation such as adding two dice pools
4
+ class AdditionOperation < 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,23 @@
1
+ module BagOfHolding
2
+ module Dice
3
+ # Internal: Stores a constant value with a rollable API so that this
4
+ # object can be called like a pool or operation.
5
+ class Constant
6
+ attr_accessor :value
7
+
8
+ def initialize(value:)
9
+ self.value = value
10
+ end
11
+
12
+ def roll
13
+ BagOfHolding::Dice::ConstantResult.new value: value
14
+ end
15
+
16
+ def ==(other)
17
+ return false unless other.respond_to? :value
18
+ return false unless value == other.value
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ module BagOfHolding
2
+ module Dice
3
+ # Internal: Present the 'result' of a constant with a result API so it
4
+ # can be used interchangeably with an OperationResult or a PoolResult
5
+ class ConstantResult
6
+ attr_accessor :value
7
+
8
+ def initialize(value: nil)
9
+ self.value = value
10
+ end
11
+
12
+ def to_s
13
+ value.to_s
14
+ end
15
+
16
+ def inspect
17
+ value.to_s
18
+ end
19
+
20
+ def ==(other)
21
+ return false unless other.respond_to? :value
22
+ return false unless value == other.value
23
+ true
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,39 @@
1
+ module BagOfHolding
2
+ module Dice
3
+ # Internal: Represent a physical die and it's attributes
4
+ class Die
5
+ attr_accessor :sides, :reroll, :explode
6
+
7
+ def initialize(opts = {})
8
+ self.sides = opts[:sides]
9
+ self.explode = opts[:explode]
10
+ self.reroll = opts[:reroll]
11
+ end
12
+
13
+ def ==(other)
14
+ return false if sides != other.sides
15
+ return false if explode != other.explode
16
+ return false if reroll != other.reroll
17
+
18
+ true
19
+ end
20
+
21
+ def roll
22
+ fail StandardError unless valid?
23
+ BagOfHolding::Dice::DieRoller.roll self
24
+ end
25
+
26
+ def valid?
27
+ DieValidator.valid? self
28
+ end
29
+
30
+ def to_s
31
+ 'd'.tap do |str|
32
+ str << sides.to_s
33
+ str << "r#{reroll}" if reroll
34
+ str << "e#{explode}" if explode
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,26 @@
1
+ module BagOfHolding
2
+ module Dice
3
+ # Internal: Encapsulate the value of a die roll along with the rolls that
4
+ # where performed to achieve that value.
5
+ class DieResult
6
+ attr_accessor :rolls, :value, :die
7
+
8
+ def initialize(rolls: nil, value: nil, die: nil)
9
+ self.rolls = rolls || []
10
+ self.value = value
11
+ self.die = die
12
+ end
13
+
14
+ def ==(other)
15
+ return false if value != other.value
16
+ return false if rolls != other.rolls
17
+ return false if die != other.die
18
+ true
19
+ end
20
+
21
+ def add_roll(roll)
22
+ rolls.push roll
23
+ end
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,51 @@
1
+ module BagOfHolding
2
+ module Dice
3
+ # Contain all the logic around rolling a die given it's options
4
+ class DieRoller
5
+ def self.roll(die)
6
+ new(die: die).roll
7
+ end
8
+
9
+ attr_accessor :die, :current_roll
10
+
11
+ def initialize(die:)
12
+ self.die = die
13
+ end
14
+
15
+ def roll
16
+ loop do
17
+ self.current_roll = rng
18
+ result.add_roll current_roll
19
+
20
+ next if reroll?
21
+
22
+ result.value += current_roll
23
+
24
+ next if explode?
25
+
26
+ break
27
+ end
28
+
29
+ result
30
+ end
31
+
32
+ def result
33
+ @result ||= BagOfHolding::Dice::DieResult.new(
34
+ value: 0, rolls: [], die: self
35
+ )
36
+ end
37
+
38
+ def reroll?
39
+ die.reroll && current_roll <= die.reroll
40
+ end
41
+
42
+ def explode?
43
+ die.explode && current_roll >= die.explode
44
+ end
45
+
46
+ def rng
47
+ 1 + rand(die.sides)
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,23 @@
1
+ module BagOfHolding
2
+ module Dice
3
+ # Internal: Wraps the logic that determines if a die can be rolled
4
+ # successfully
5
+ class DieValidator
6
+ def self.valid?(die)
7
+ new(die: die).valid?
8
+ end
9
+
10
+ attr_accessor :die
11
+
12
+ def initialize(die:)
13
+ self.die = die
14
+ end
15
+
16
+ def valid?
17
+ return false unless die.sides
18
+ return false if die.reroll && die.explode && die.reroll >= die.explode
19
+ true
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,18 @@
1
+ module BagOfHolding
2
+ module Dice
3
+ # Internal: Encapsulates subtracting two expressions
4
+ class DivisionOperation < 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,18 @@
1
+ module BagOfHolding
2
+ module Dice
3
+ # Internal: Encapsulates subtracting two expressions
4
+ class MultiplicationOperation < 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,45 @@
1
+ module BagOfHolding
2
+ module Dice
3
+ # Internal: Encapsulates an operation such as adding two dice pools
4
+ class Operation
5
+ attr_accessor :left, :right
6
+
7
+ def initialize(left: nil, right: nil)
8
+ self.left = left
9
+ self.right = right
10
+ end
11
+
12
+ def roll
13
+ BagOfHolding::Dice::OperationResult.new left_result: left_result,
14
+ right_result: right_result,
15
+ operation: self,
16
+ value: value
17
+ end
18
+
19
+ def ==(other)
20
+ begin
21
+ return false unless left == other.left
22
+ return false unless right == other.right
23
+ rescue NoMethodError
24
+ return false
25
+ end
26
+
27
+ true
28
+ end
29
+
30
+ private
31
+
32
+ def value
33
+ fail NotImplementedError
34
+ end
35
+
36
+ def left_result
37
+ @left_result ||= left.roll
38
+ end
39
+
40
+ def right_result
41
+ @right_result ||= right.roll
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,38 @@
1
+ module BagOfHolding
2
+ module Dice
3
+ # Internal: Represent the results of an operation such as adding two
4
+ # dice pools.
5
+ class OperationResult
6
+ attr_accessor :left_result, :right_result, :operation, :value
7
+
8
+ def initialize(left_result: nil, right_result: nil,
9
+ operation: nil, value: nil)
10
+ self.left_result = left_result
11
+ self.right_result = right_result
12
+ self.operation = operation
13
+ self.value = value
14
+ end
15
+
16
+ def to_s
17
+ "#{left_result} #{operation.operator} #{right_result}"
18
+ end
19
+
20
+ def inspect
21
+ "#{left_result.inspect} #{operation.operator} #{right_result.inspect}"
22
+ end
23
+
24
+ def ==(other)
25
+ begin
26
+ return false unless left_result == other.left_result
27
+ return false unless right_result == other.right_result
28
+ return false unless operation == other.operation
29
+ return false unless value == other.value
30
+ rescue NoMethodError
31
+ return false
32
+ end
33
+
34
+ true
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,50 @@
1
+ require 'parslet'
2
+
3
+ module BagOfHolding
4
+ module Dice
5
+ # Internal: Parse a string defining a given roll into an ast
6
+ class Parser < Parslet::Parser
7
+ rule(:space) { match('\s') }
8
+ rule(:space?) { space.maybe }
9
+ rule(:integer) { match('[0-9]').repeat(1) }
10
+
11
+ rule(:label_text) { match('[^()]').repeat(1).as(:label) }
12
+ rule(:label) { space? >> str('(') >> label_text >> str(')') }
13
+ rule(:label?) { label.maybe }
14
+
15
+ rule(:exploding) { str('e') >> integer.maybe.as(:explode) }
16
+ rule(:reroll) { str('r') >> integer.maybe.as(:reroll) }
17
+ rule(:keep) { str('k') >> integer.maybe.as(:keep) }
18
+ rule(:option) { exploding | reroll | keep }
19
+ rule(:options) { option.repeat }
20
+ rule(:options?) { options.maybe }
21
+
22
+ rule(:sides) { integer.as(:sides).repeat(1) }
23
+
24
+ rule(:die) { str('d') >> sides >> options? }
25
+
26
+ rule(:count) { integer.as(:count) }
27
+ rule(:count?) { count.maybe }
28
+
29
+ rule(:pool) { count? >> die.as(:die) >> label? }
30
+ rule(:constant) { integer.as(:constant) }
31
+
32
+ rule(:addition) { str('+') }
33
+ rule(:subtraction) { str('-') }
34
+ rule(:division) { str('/') }
35
+ rule(:multiplication) { str('*') }
36
+ rule(:operators) do
37
+ addition | subtraction | division | multiplication
38
+ end
39
+ rule(:operator) { operators.as(:operator) >> space? }
40
+
41
+ rule(:sum) { value.as(:left) >> operator >> expression.as(:right) }
42
+
43
+ rule(:value) { (pool | constant) >> space? }
44
+ rule(:expression) { (sum | value) >> str(',').maybe >> space? }
45
+ rule(:expressions) { expression.repeat(1) }
46
+
47
+ root :expressions
48
+ end
49
+ end
50
+ end