bag_of_holding 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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 [![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
|