para_dice 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|