para_dice 0.6.0
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/.gitignore +15 -0
- data/.pullreview.yml +77 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/Gemfile +9 -0
- data/Guardfile +27 -0
- data/LICENSE.txt +22 -0
- data/README.md +51 -0
- data/Rakefile +10 -0
- data/VERSION +1 -0
- data/bin/dice_tests.rb +27 -0
- data/data/face_types.yml +27 -0
- data/data/gotham_dice.yml +55 -0
- data/data/specs/not_really_yaml.yml +22 -0
- data/data/specs/simple_array.yml +7 -0
- data/data/specs/simple_hash.yml +4 -0
- data/data/star_wars_dice.yml +87 -0
- data/lib/gotham_dice.rb +44 -0
- data/lib/para_dice/bag.rb +44 -0
- data/lib/para_dice/cup.rb +19 -0
- data/lib/para_dice/die.rb +64 -0
- data/lib/para_dice/faces/arrayed.rb +34 -0
- data/lib/para_dice/faces/ranged.rb +38 -0
- data/lib/para_dice/numbered_die.rb +13 -0
- data/lib/para_dice/poly_bag.rb +31 -0
- data/lib/para_dice/results/and_keep.rb +34 -0
- data/lib/para_dice/results/array.rb +13 -0
- data/lib/para_dice/results/cancel_opposing.rb +46 -0
- data/lib/para_dice/results/hash.rb +14 -0
- data/lib/para_dice/results/number.rb +39 -0
- data/lib/para_dice/results/split_faces.rb +23 -0
- data/lib/para_dice/string_die.rb +15 -0
- data/lib/para_dice/utility.rb +54 -0
- data/lib/para_dice.rb +9 -0
- data/lib/star_wars_dice.rb +41 -0
- data/para_dice.gemspec +34 -0
- data/spec/gotham_dice_spec.rb +19 -0
- data/spec/para_dice/bag_spec.rb +53 -0
- data/spec/para_dice/cup_spec.rb +73 -0
- data/spec/para_dice/die_spec.rb +21 -0
- data/spec/para_dice/faces/ranged_spec.rb +42 -0
- data/spec/para_dice/faces/string_arrayed_spec.rb +41 -0
- data/spec/para_dice/numbered_die_spec.rb +19 -0
- data/spec/para_dice/poly_bag_spec.rb +9 -0
- data/spec/para_dice/results/and_keep_spec.rb +30 -0
- data/spec/para_dice/results/array_spec.rb +8 -0
- data/spec/para_dice/results/cancel_opposing_spec.rb +18 -0
- data/spec/para_dice/results/hash_spec.rb +6 -0
- data/spec/para_dice/results/number_spec.rb +44 -0
- data/spec/para_dice/results/parse_faces_spec.rb +9 -0
- data/spec/para_dice/string_die_spec.rb +14 -0
- data/spec/para_dice/utility_spec.rb +82 -0
- data/spec/para_dice_spec.rb +10 -0
- data/spec/spec_helper.rb +82 -0
- data/spec/star_wars_dice_spec.rb +19 -0
- metadata +274 -0
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'virtus'
|
2
|
+
module ParaDice
|
3
|
+
module Faces
|
4
|
+
# a module to be included into a ParaDice::Die or similar object.
|
5
|
+
# Uses a Range object to provide a faces, and provides special count and
|
6
|
+
# random_face options
|
7
|
+
module Ranged
|
8
|
+
include Virtus.module
|
9
|
+
|
10
|
+
# @!attribute range
|
11
|
+
# return [Range]
|
12
|
+
attribute :range, Range, default: :quit_if_no_range
|
13
|
+
|
14
|
+
# @return [Fixnum] size of range
|
15
|
+
def face_count
|
16
|
+
range.size
|
17
|
+
end
|
18
|
+
|
19
|
+
# @note this can be an unexpectedly expensive action for large ranges,
|
20
|
+
# please use carefully
|
21
|
+
# @return [Array<Fixnum>] result of range.to_a
|
22
|
+
def faces
|
23
|
+
@faces ||= range.to_a
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param [nil,Random] random_face_rng defaults to self.rng
|
27
|
+
# @return [Fixnum] random element of range
|
28
|
+
def random_face(random_face_rng = rng)
|
29
|
+
random_face_rng.rand(range.size) + range.first
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def quit_if_no_range
|
34
|
+
raise ArgumentError.new('no :range entry found in initialization hash')
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'para_dice/die'
|
2
|
+
require 'para_dice/faces/ranged'
|
3
|
+
module ParaDice
|
4
|
+
# a standard die like d6 d10, d20, d100 or exotic like d17, d32, d11-10, d100-999
|
5
|
+
class NumberedDie
|
6
|
+
include Die
|
7
|
+
include Faces::Ranged
|
8
|
+
|
9
|
+
def self.get(max)
|
10
|
+
self.new(name: "d#{max}", range: 1..max)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'virtus' # TODO check on usage later
|
2
|
+
|
3
|
+
require 'para_dice/bag'
|
4
|
+
require 'para_dice/numbered_die'
|
5
|
+
require 'para_dice/results/number'
|
6
|
+
module ParaDice
|
7
|
+
# A ParaDice bag that has all of the standard dice: d6, d10, d10, and that you
|
8
|
+
# can add more like d33
|
9
|
+
class PolyBag
|
10
|
+
# contains all of the dice to start the bag with
|
11
|
+
DEFAULT_DICE = [2, 3, 4, 6, 8, 10, 12, 20, 30, 100]
|
12
|
+
# the result stack that Bag will pass by default to cups.
|
13
|
+
DEFAULT_READERS = [Results::Number]
|
14
|
+
|
15
|
+
# generate a new poly bag with optional rng
|
16
|
+
# @param [nil, Random, #rand] random number generator to pass to all cups
|
17
|
+
# (which will in turn pass it to each)
|
18
|
+
# @return [PolyBag]
|
19
|
+
def self.get(rng = nil)
|
20
|
+
a = ParaDice::Bag.new(name: 'Poly Dice',
|
21
|
+
rng: rng,
|
22
|
+
default_readers: DEFAULT_READERS)
|
23
|
+
|
24
|
+
DEFAULT_DICE.each do |num|
|
25
|
+
die = NumberedDie.get(num)
|
26
|
+
a.add_die(die)
|
27
|
+
end
|
28
|
+
return a
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module ParaDice
|
2
|
+
module Results
|
3
|
+
# Results class to implement a roll 5 and keep 3 style of play
|
4
|
+
class AndKeep
|
5
|
+
# @!attribute [rw] :default_keep
|
6
|
+
# @return [Fixnum] the default number of dice to keep
|
7
|
+
# @!attribute [rw] :default_low_results
|
8
|
+
# @return [Boolean] keep dice off of the low end of the order. false by default
|
9
|
+
# @!attribute [rw] :default_sort_by
|
10
|
+
# @return [Proc] the sort by block defaults to passing the object through to sort
|
11
|
+
DEFAULT_SORT_BY = ->(obj) { obj }
|
12
|
+
attr_accessor :default_keep, :default_low_results, :default_sort_by
|
13
|
+
# @param [Regex,String] default_keep
|
14
|
+
def initialize(default_keep = nil)
|
15
|
+
@default_keep = default_keep
|
16
|
+
end
|
17
|
+
|
18
|
+
# @param [Array<#to_s>] faces
|
19
|
+
# @param [Regex,String] keep default: default_keep
|
20
|
+
# @param [Boolean] low_results if true take the bottom of the order
|
21
|
+
# @yield [obj] provides obj to call methods on to determine value for use
|
22
|
+
# in Array.order_by. Numbers sort easy, names are trickier
|
23
|
+
# @return [Array<String>]
|
24
|
+
def resolve(faces, keep = default_keep, low_results = false, &blk)
|
25
|
+
results_method = low_results ? :first : :last
|
26
|
+
if block_given?
|
27
|
+
faces.sort_by(&blk).to_a.send(results_method, keep)
|
28
|
+
else
|
29
|
+
faces.sort_by(&default_sort_by).to_a.send(results_method, keep)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module ParaDice
|
2
|
+
module Results
|
3
|
+
# Results class to output the list of faces. It's a Noop that sets the
|
4
|
+
# Api for Results files
|
5
|
+
module Array
|
6
|
+
# @param [Array] faces
|
7
|
+
# @return array of faces passed in
|
8
|
+
def self.resolve(faces)
|
9
|
+
faces
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'para_dice/utility'
|
2
|
+
|
3
|
+
module ParaDice
|
4
|
+
module Results
|
5
|
+
# Results class to have some faces cancel other faces out of the results,
|
6
|
+
# can also be used to drop faces entirely by defining them as their own opposite
|
7
|
+
class CancelOpposing
|
8
|
+
# @!attribute rw default_opposing
|
9
|
+
# default to DEFAULT_OPPOSING = []
|
10
|
+
# @return [Array<Array>] list of pairs of faces that cancel each other out
|
11
|
+
# use [['blank','blank']] as opposing to eliminate blank results
|
12
|
+
# use [['good','bad'], ['hero','villain']] to have good and bad, heroes
|
13
|
+
# villains to cancel each other out
|
14
|
+
attr_accessor :default_opposing
|
15
|
+
|
16
|
+
# set empty here so it will work if no values passed to it.
|
17
|
+
DEFAULT_OPPOSING = []
|
18
|
+
# @param [Array<Array>] opposing defaults to DEFAULT_OPPOSING=[]
|
19
|
+
def initialize(default_opposing = DEFAULT_OPPOSING)
|
20
|
+
@default_opposing = default_opposing
|
21
|
+
end
|
22
|
+
|
23
|
+
# @param [Array<#to_s>] faces
|
24
|
+
# @param [Array<Array>] opposing defaults to default_opposing
|
25
|
+
# @return [Array<String>]
|
26
|
+
def resolve(faces, opposing = default_opposing)
|
27
|
+
summary = Utility.to_summary(faces)
|
28
|
+
opposing.each do |one_face, another_face|
|
29
|
+
one_face = one_face.to_s
|
30
|
+
another_face = another_face.to_s
|
31
|
+
next unless summary.key?(one_face) && summary.key?(another_face)
|
32
|
+
if summary[one_face] == summary[another_face]
|
33
|
+
summary.delete(one_face)
|
34
|
+
summary.delete(another_face)
|
35
|
+
else
|
36
|
+
low, high = [one_face, another_face].sort_by { |e| summary[e] }
|
37
|
+
summary[high] -= summary[low]
|
38
|
+
summary.delete(low)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
Utility.from_summary(summary)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'para_dice/utility'
|
2
|
+
module ParaDice
|
3
|
+
module Results
|
4
|
+
# Results class to return a hash summary of a dice result
|
5
|
+
module Hash
|
6
|
+
# @param [Array] faces
|
7
|
+
# @return [Hash{Object=>Fixnum}]summary hash
|
8
|
+
# @see Utility#to_summary
|
9
|
+
def self.resolve(faces)
|
10
|
+
Utility.to_summary(faces)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
module ParaDice
|
2
|
+
module Results
|
3
|
+
# Number is meant to run a single operation across all faces of the results in
|
4
|
+
# order to do things like add up all of the results. This class gets inserted
|
5
|
+
# as a result responder as opposed to the Module uses. The default is to add all
|
6
|
+
# of the elements together and return the result
|
7
|
+
|
8
|
+
# @note this class could also be used for more complex operations using the
|
9
|
+
# reduce with operator and initial values. Blocks seem like another likely
|
10
|
+
# refinement. But I couldn't come with a reason to use them yet that didn't
|
11
|
+
# seem more confusing than coding a new result resolver
|
12
|
+
class Number
|
13
|
+
# If not overridden the initial value is 0
|
14
|
+
DEFAULT_INITIAL = 0
|
15
|
+
# If not overridden the operator is :+
|
16
|
+
DEFAULT_OPERATOR = :+
|
17
|
+
# @!attribute [rw] default_initial_value
|
18
|
+
# @return [Numeric,Obj]
|
19
|
+
# @!attribute [rw] default_operator
|
20
|
+
# @return [Symbol]
|
21
|
+
attr_accessor :default_initial_value, :default_operator
|
22
|
+
|
23
|
+
# @param [Numeric, Obj] default_operator
|
24
|
+
# @param [Symbol] default_operator
|
25
|
+
def initialize(initial_val = DEFAULT_INITIAL, default_operator = DEFAULT_OPERATOR)
|
26
|
+
@default_initial_value = initial_val
|
27
|
+
@default_operator = default_operator
|
28
|
+
end
|
29
|
+
|
30
|
+
# @param [Array<Numeric,Object>] faces
|
31
|
+
# @param [Numeric,Object] initial value, defaults to DEFAULT_INITIAL = 0
|
32
|
+
# @param [Symbol] operator, defaults to DEFAULT_OPERATOR = :+
|
33
|
+
# @return [Numeric, Object] result of inject with faces, initial_value, and operator
|
34
|
+
def resolve(faces, initial_value = default_initial_value, operator = default_operator)
|
35
|
+
faces.inject(initial_value, operator)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ParaDice
|
2
|
+
module Results
|
3
|
+
# Results class to take faces and split them given a string or regexp
|
4
|
+
class SplitFaces
|
5
|
+
# @!attribute default_split_token
|
6
|
+
# @return [Regexp, String] used by split to separate token into pieces
|
7
|
+
attr_accessor :default_split_token
|
8
|
+
# when performing the split, use this regexp by default
|
9
|
+
DEFAULT_SPLIT_TOKEN = /-|_/
|
10
|
+
# @param [Regex,String] split_token defaults to DEFAULT_SPLIT_TOKEN=/-|_/
|
11
|
+
def initialize(default_split_token = DEFAULT_SPLIT_TOKEN)
|
12
|
+
@default_split_token = default_split_token
|
13
|
+
end
|
14
|
+
|
15
|
+
# @param [Array<#to_s>] faces
|
16
|
+
# @param [Regex,String] split_token defaults to default_split_token
|
17
|
+
# @return [Array<String>]
|
18
|
+
def resolve(faces, split_token = default_split_token)
|
19
|
+
faces.map { |f| f.to_s.split(split_token) }.flatten(1)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'para_dice/die'
|
2
|
+
require 'para_dice/faces/arrayed'
|
3
|
+
|
4
|
+
module ParaDice
|
5
|
+
# A Simple die using an array of string faces
|
6
|
+
# @see ParaDice::Die and ParaDice::Faces::StringArrayed
|
7
|
+
class StringDie
|
8
|
+
include ParaDice::Die
|
9
|
+
include ParaDice::Faces::Arrayed
|
10
|
+
|
11
|
+
# @!attribute [rw] description
|
12
|
+
# @return [String] defaults to a string like 'faces: foo, bar, baz'
|
13
|
+
attribute :description, String, default: ->(s, a) { 'faces: ' + s.faces.join(', ') }
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'facets/string/modulize'
|
2
|
+
require 'hashie'
|
3
|
+
|
4
|
+
module ParaDice
|
5
|
+
module Utility
|
6
|
+
class IndifferentHash < Hash
|
7
|
+
include Hashie::Extensions::IndifferentAccess
|
8
|
+
end
|
9
|
+
|
10
|
+
# TODO ADD EXAMPLE
|
11
|
+
# @param [Class,Module,#to_s] name
|
12
|
+
# @param [Class, Module, #to_s] namespace
|
13
|
+
# @return [false] if no name is found at top level or namespace
|
14
|
+
# @return [Class,Module] the class or module represented by name
|
15
|
+
# and optionally in namespace
|
16
|
+
def self.module_from(name, namespace = nil)
|
17
|
+
return name if name.is_a? Module
|
18
|
+
return false if name.empty?
|
19
|
+
self.module_from_top(name) || self.module_from_namespace(name, namespace)
|
20
|
+
end
|
21
|
+
|
22
|
+
# to_summary creates a hash and populates it with uniq elements of the
|
23
|
+
# array, and the count of how many there are
|
24
|
+
# @param [Array] arr a list to summarize
|
25
|
+
# @return [Hash]
|
26
|
+
def self.to_summary(arr)
|
27
|
+
arr.reduce(Hash.new(0)) { |h, e| h[e] += 1; h }
|
28
|
+
end
|
29
|
+
|
30
|
+
# from_summary creates an array and populates with a number of each key
|
31
|
+
# equal to the value for that key
|
32
|
+
# TODO ADD EXAMPLE
|
33
|
+
# @param [Hash<Object,Fixnum>] summary a hash to inflate a summary into an array
|
34
|
+
# @return [Array<Object>]
|
35
|
+
def self.from_summary(summary)
|
36
|
+
summary.each_pair.map { |e, c| Array.new(c) { e } }.flatten
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def self.module_from_top(raw_name)
|
41
|
+
name = raw_name.to_s.modulize
|
42
|
+
return false if name.empty?
|
43
|
+
return false unless Kernel.const_defined?(name)
|
44
|
+
Kernel.const_get(name)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.module_from_namespace(raw_name, namespace)
|
48
|
+
return false unless namespace
|
49
|
+
name = raw_name.to_s.modulize
|
50
|
+
return false unless namespace.const_defined?(name)
|
51
|
+
namespace.const_get(name)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/para_dice.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'para_dice/string_die'
|
2
|
+
require 'para_dice/results/cancel_opposing'
|
3
|
+
require 'para_dice/results/split_faces'
|
4
|
+
require 'para_dice/results/hash'
|
5
|
+
require 'para_dice/bag'
|
6
|
+
require 'yaml'
|
7
|
+
|
8
|
+
# A class for representing the game dice from Star Wars: Edge of the Empire,
|
9
|
+
# Star Wars: Age of Rebellion, Star Wars: Force and Destiny. Mostly here as
|
10
|
+
# an example of how to create a custom dice bag.
|
11
|
+
# @note no claim is made for the name StarWarsDice
|
12
|
+
class StarWarsDice < ParaDice::Bag
|
13
|
+
# yaml file containing dice definitions
|
14
|
+
DATA_FILE_NAME = 'data/star_wars_dice.yml'
|
15
|
+
# opposite facing to be used by the reader CancelOpposing
|
16
|
+
OPPOSING = [
|
17
|
+
['blank', 'blank'],
|
18
|
+
['success', 'failure'],
|
19
|
+
['advantage', 'threat']
|
20
|
+
]
|
21
|
+
# The normal reader order for StarWarsDice. First split the face names using
|
22
|
+
# '_', then cancel opposing faces, finally return as a hash
|
23
|
+
DEFAULT_READERS = [
|
24
|
+
ParaDice::Results::SplitFaces.new('_'),
|
25
|
+
ParaDice::Results::CancelOpposing.new(OPPOSING),
|
26
|
+
ParaDice::Results::Hash
|
27
|
+
]
|
28
|
+
|
29
|
+
# @!attribute [rw] dice
|
30
|
+
# @return [Hash<String,StringDie>]
|
31
|
+
attribute :dice, Hash[String => ParaDice::StringDie]
|
32
|
+
# @!attribute [rw] default_readers
|
33
|
+
# @return [Array<#resolve>]
|
34
|
+
attribute :default_readers, Array, default: ->(*a) { DEFAULT_READERS }
|
35
|
+
|
36
|
+
# load and return a bag of StarWarsDice
|
37
|
+
def self.get
|
38
|
+
hash = YAML.load_file(DATA_FILE_NAME)
|
39
|
+
self.new(name: 'Star Wars Dice', dice: hash)
|
40
|
+
end
|
41
|
+
end
|
data/para_dice.gemspec
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "para_dice"
|
7
|
+
spec.version = File.read('VERSION')
|
8
|
+
spec.authors = ["Scott M Parrish"]
|
9
|
+
spec.email = ["anithri@gmail.com"]
|
10
|
+
spec.summary = %q{Game Dev Toolbox.}
|
11
|
+
spec.description = %q{A Toolbox to help me build a board game.}
|
12
|
+
spec.homepage = ""
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files -z`.split("\x0")
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
|
20
|
+
spec.add_runtime_dependency 'virtus', '~> 1.0.3'
|
21
|
+
spec.add_runtime_dependency 'facets', '~> 2.9.3'
|
22
|
+
spec.add_runtime_dependency 'hashie', '~> 3.3.1'
|
23
|
+
|
24
|
+
spec.add_development_dependency 'bundler', '~> 1.7.6'
|
25
|
+
spec.add_development_dependency 'yard', '~> 0.8.7.6'
|
26
|
+
spec.add_development_dependency 'rake', '~> 10.3.2'
|
27
|
+
spec.add_development_dependency 'rspec', '~> 3.1.0'
|
28
|
+
spec.add_development_dependency 'guard-rspec', '~> 4.3.1'
|
29
|
+
spec.add_development_dependency 'guard-bundler', '~> 2.0.0'
|
30
|
+
spec.add_development_dependency 'version', '~> 1.0.0'
|
31
|
+
spec.add_development_dependency 'fuubar', '~> 2.0.0'
|
32
|
+
|
33
|
+
end
|
34
|
+
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'gotham_dice'
|
3
|
+
|
4
|
+
describe GothamDice do
|
5
|
+
subject { described_class.get }
|
6
|
+
it { is_expected.to be_a described_class }
|
7
|
+
it { expect(subject.dice.keys.count).to eq 6 }
|
8
|
+
it { expect(subject.dice.values.all? { |d| d.is_a? ParaDice::StringDie }).to be true }
|
9
|
+
it { expect(subject.default_readers.size).to eq(3) }
|
10
|
+
it { expect(subject.default_readers[0]).to be_a ParaDice::Results::SplitFaces }
|
11
|
+
it { expect(subject.default_readers[1]).to be_a ParaDice::Results::CancelOpposing }
|
12
|
+
it { expect(subject.default_readers[2]).to eq ParaDice::Results::Hash }
|
13
|
+
|
14
|
+
describe '.get_cup' do
|
15
|
+
let(:dice) { ['combat', 'combat', 'detect', 'crime', 'move', 'move', 'crime', 'combat'] }
|
16
|
+
subject { described_class.get.get_cup(*dice) }
|
17
|
+
it { expect(subject.roll!.size).to eq 8 }
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
require 'para_dice/bag'
|
4
|
+
require 'para_dice/numbered_die'
|
5
|
+
require 'para_dice/results/number'
|
6
|
+
describe ParaDice::Bag do
|
7
|
+
let(:d4) { ParaDice::NumberedDie.get(4) }
|
8
|
+
let(:d8) { ParaDice::NumberedDie.get(8) }
|
9
|
+
let(:d12) { ParaDice::NumberedDie.get(12) }
|
10
|
+
let(:bag) do
|
11
|
+
bag = described_class.new
|
12
|
+
bag.add_die d4
|
13
|
+
bag.add_die d12
|
14
|
+
bag.add_die d8
|
15
|
+
bag.default_readers << ParaDice::Results::Number.new
|
16
|
+
bag
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'with all defaults' do
|
20
|
+
subject { described_class.new }
|
21
|
+
it do
|
22
|
+
is_expected.to have_attributes(name: 'Dice Bag',
|
23
|
+
dice: {},
|
24
|
+
default_readers: [])
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '.add_die(die)' do
|
29
|
+
let(:new_die) { ParaDice::NumberedDie.get(20) }
|
30
|
+
subject { described_class.new }
|
31
|
+
specify { expect(subject.dice).to be_empty }
|
32
|
+
it 'adds die to hash' do
|
33
|
+
subject.add_die(new_die)
|
34
|
+
expect(subject.dice['d20']).to eq new_die
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe 'get_dice(*dice_names)' do
|
39
|
+
let(:names) { ['d8', 'd8', 'd12'] }
|
40
|
+
subject { bag.get_dice(*names) }
|
41
|
+
it { expect(subject[0]).to eq d8 }
|
42
|
+
it { expect(subject[1]).to eq d8 }
|
43
|
+
it { expect(subject[2]).to eq d12 }
|
44
|
+
end
|
45
|
+
|
46
|
+
describe 'get_cup(*dice_names)' do
|
47
|
+
let(:names) { ['d8', 'd8', 'd12'] }
|
48
|
+
subject { bag.get_cup(*names) }
|
49
|
+
it { is_expected.to be_a ParaDice::Cup }
|
50
|
+
it { expect(subject.roll).to be_a Fixnum }
|
51
|
+
it { expect(subject.roll).to be_between 3, 28 }
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
require 'para_dice/cup'
|
4
|
+
require 'para_dice/results/cancel_opposing'
|
5
|
+
require 'para_dice/results/hash'
|
6
|
+
|
7
|
+
describe ParaDice::Cup do
|
8
|
+
|
9
|
+
describe 'initialize' do
|
10
|
+
context 'with no arguments' do
|
11
|
+
subject { described_class.new }
|
12
|
+
|
13
|
+
it { is_expected.to have_attributes(dice: [], readers: [], rng: Random) }
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'with rng seed' do
|
17
|
+
subject { described_class.new(rng: Random.new(111)) }
|
18
|
+
it { is_expected.to have_attributes(dice: [], readers: [], rng: Random) }
|
19
|
+
|
20
|
+
it { expect(subject.rng.seed).to eq 111 }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '.roll(roll_rng)' do
|
25
|
+
let(:fbb) { ParaDice::StringDie.new(name: 'FBB', faces: [:foo, :bar, :baz]) }
|
26
|
+
|
27
|
+
it 'call roll on all dice...' do
|
28
|
+
cup = described_class.new(dice: [fbb, fbb, fbb])
|
29
|
+
rng = double('rng')
|
30
|
+
allow(rng).to receive(:rand).and_return(2, 0, 2)
|
31
|
+
expect(rng).to receive(:rand).exactly(3).times
|
32
|
+
cup.roll(rng)
|
33
|
+
end
|
34
|
+
|
35
|
+
it '...return array' do
|
36
|
+
cup = described_class.new(dice: [fbb, fbb, fbb])
|
37
|
+
rng = double('rng')
|
38
|
+
allow(rng).to receive(:rand).and_return(2, 0, 2)
|
39
|
+
expect(cup.roll(rng)).to eq ['baz', 'foo', 'baz']
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '.roll' do
|
44
|
+
let(:fbb) { ParaDice::StringDie.new(name: 'FBB', faces: [:foo, :bar, :baz]) }
|
45
|
+
let(:rng) do
|
46
|
+
rng = double('rng')
|
47
|
+
allow(rng).to receive(:rand).and_return(2, 0, 2, 1, 2)
|
48
|
+
rng
|
49
|
+
end
|
50
|
+
let(:dice_list) { [fbb, fbb, fbb, fbb, fbb] }
|
51
|
+
let(:cup) { described_class.new(dice: dice_list) }
|
52
|
+
context 'with no readers' do
|
53
|
+
subject { cup.roll(rng) }
|
54
|
+
it { is_expected.to eq ['baz', 'foo', 'baz', 'bar', 'baz'] }
|
55
|
+
end
|
56
|
+
context 'with a hash_reader' do
|
57
|
+
subject { cup.roll(rng) }
|
58
|
+
let(:readers) { [ParaDice::Results::Hash] }
|
59
|
+
let(:cup) { described_class.new(dice: dice_list, readers: readers)
|
60
|
+
it { is_expected.to eq('baz' => 3, 'foo' => 1, 'bar' => 1) } }
|
61
|
+
end
|
62
|
+
context 'with a cancel_opposing reader' do
|
63
|
+
subject { cup.roll(rng) }
|
64
|
+
let(:cancel_out) { [['foo', 'baz']] }
|
65
|
+
let(:readers) do
|
66
|
+
[ParaDice::Results::CancelOpposing.new(cancel_out),
|
67
|
+
ParaDice::Results::Hash]
|
68
|
+
end
|
69
|
+
let(:cup) { described_class.new(dice: dice_list, readers: readers) }
|
70
|
+
it { is_expected.to eq('baz' => 2, 'bar' => 1) }
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'para_dice/die'
|
3
|
+
describe ParaDice::Die do
|
4
|
+
before(:all) do
|
5
|
+
class AbstractDie
|
6
|
+
include ParaDice::Die
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
let(:name) { 'fake_die' }
|
11
|
+
subject { AbstractDie.new(name: name) }
|
12
|
+
it { is_expected.to be_a ParaDice::Die }
|
13
|
+
it { is_expected.to have_attributes(name: 'fake_die', description: '') }
|
14
|
+
it { expect { subject.random_face }.to raise_error NotImplementedError, /random_face:/ }
|
15
|
+
it { expect { subject.faces }.to raise_error NotImplementedError, /faces:/ }
|
16
|
+
it { expect { subject.face_count }.to raise_error NotImplementedError, /face_count:/ }
|
17
|
+
it 'will raise an error if no id is given to initializer' do
|
18
|
+
expect { AbstractDie.new({}) }.to raise_error ArgumentError, /:name/
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
require 'para_dice/die'
|
3
|
+
require 'para_dice/faces/ranged'
|
4
|
+
|
5
|
+
describe ParaDice::Faces::Ranged do
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
class RangedFaceDie
|
9
|
+
include ParaDice::Die
|
10
|
+
include ParaDice::Faces::Ranged
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'initialize' do
|
15
|
+
let(:name) { 'd10-19' }
|
16
|
+
let(:range) { 10..19 }
|
17
|
+
subject { RangedFaceDie.new(name: name, range: range) }
|
18
|
+
it { is_expected.to be_a ParaDice::Die }
|
19
|
+
it do
|
20
|
+
is_expected.to have_attributes(faces: [10, 11, 12, 13, 14, 15, 16, 17, 18, 19],
|
21
|
+
name: name,
|
22
|
+
range: range,
|
23
|
+
face_count: 10)
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'will call rand with size on rng' do
|
27
|
+
rng = double('random')
|
28
|
+
expect(rng).to receive(:rand).with(10).and_return(2)
|
29
|
+
subject.random_face(rng)
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'will return the right value for a give rnd.rand result' do
|
33
|
+
rng = double('random')
|
34
|
+
allow(rng).to receive(:rand).with(10).and_return 4
|
35
|
+
expect(subject.random_face(rng)).to eq 14
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'will raise an error if no range is given to initializer' do
|
39
|
+
expect { RangedFaceDie.new(name: 'oops').range }.to raise_error ArgumentError, /:range/
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
require 'rspec'
|
2
|
+
|
3
|
+
require 'para_dice/faces/arrayed'
|
4
|
+
|
5
|
+
describe ParaDice::Faces::Arrayed do
|
6
|
+
|
7
|
+
before(:all) do
|
8
|
+
class StringFaceDie
|
9
|
+
include ParaDice::Die
|
10
|
+
include ParaDice::Faces::Arrayed
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
describe 'initialize' do
|
15
|
+
let(:name) { 'FBB' }
|
16
|
+
let(:faces) { [:foo, :bar, :baz] }
|
17
|
+
subject { StringFaceDie.new(name: name, faces: faces) }
|
18
|
+
it { is_expected.to be_a ParaDice::Die }
|
19
|
+
it do
|
20
|
+
is_expected.to have_attributes(faces: ['foo', 'bar', 'baz'],
|
21
|
+
name: name,
|
22
|
+
face_count: 3)
|
23
|
+
end
|
24
|
+
|
25
|
+
it 'will call rand with size on rng' do
|
26
|
+
rng = double('random')
|
27
|
+
expect(rng).to receive(:rand).with(3).and_return(2)
|
28
|
+
subject.random_face(rng)
|
29
|
+
end
|
30
|
+
|
31
|
+
it 'will return the right value for a give rnd.rand result' do
|
32
|
+
rng = double('random')
|
33
|
+
allow(rng).to receive(:rand).with(3).and_return 1
|
34
|
+
expect(subject.random_face(rng)).to eq 'bar'
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'will raise an error if no range is given to initializer' do
|
38
|
+
expect { StringFaceDie.new(name: 'oops') }.to raise_error ArgumentError, /:faces/
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|