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