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.
- 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
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 [](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
|