polyhedra 0.3.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.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.rvmrc +1 -0
- data/Gemfile +10 -0
- data/Guardfile +47 -0
- data/README.md +209 -0
- data/Rakefile +1 -0
- data/lib/polyhedra.rb +3 -0
- data/lib/polyhedra/dice.rb +112 -0
- data/lib/polyhedra/version.rb +3 -0
- data/polyhedra.gemspec +24 -0
- data/spec/lib/dice_spec.rb +137 -0
- data/spec/spec_helper.rb +4 -0
- metadata +61 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--color
|
data/.rvmrc
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
rvm use ruby-1.9.3@polyhedra --create
|
data/Gemfile
ADDED
data/Guardfile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
# A sample Guardfile
|
2
|
+
# More info at https://github.com/guard/guard#readme
|
3
|
+
|
4
|
+
guard 'rspec', :version => 2 do
|
5
|
+
watch(%r{^spec/.+_spec\.rb$})
|
6
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
7
|
+
watch(%r{^lib/polyhedra/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
8
|
+
watch('spec/spec_helper.rb') { "spec" }
|
9
|
+
|
10
|
+
# Rails example
|
11
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
12
|
+
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
13
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
14
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
15
|
+
watch('config/routes.rb') { "spec/routing" }
|
16
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
17
|
+
|
18
|
+
# Capybara request specs
|
19
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
|
20
|
+
|
21
|
+
# Turnip features and steps
|
22
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
23
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
guard 'rspec', :version => 2 do
|
28
|
+
watch(%r{^spec/.+_spec\.rb$})
|
29
|
+
watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
|
30
|
+
watch('spec/spec_helper.rb') { "spec" }
|
31
|
+
|
32
|
+
# Rails example
|
33
|
+
watch(%r{^app/(.+)\.rb$}) { |m| "spec/#{m[1]}_spec.rb" }
|
34
|
+
watch(%r{^app/(.*)(\.erb|\.haml)$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
|
35
|
+
watch(%r{^app/controllers/(.+)_(controller)\.rb$}) { |m| ["spec/routing/#{m[1]}_routing_spec.rb", "spec/#{m[2]}s/#{m[1]}_#{m[2]}_spec.rb", "spec/acceptance/#{m[1]}_spec.rb"] }
|
36
|
+
watch(%r{^spec/support/(.+)\.rb$}) { "spec" }
|
37
|
+
watch('config/routes.rb') { "spec/routing" }
|
38
|
+
watch('app/controllers/application_controller.rb') { "spec/controllers" }
|
39
|
+
|
40
|
+
# Capybara request specs
|
41
|
+
watch(%r{^app/views/(.+)/.*\.(erb|haml)$}) { |m| "spec/requests/#{m[1]}_spec.rb" }
|
42
|
+
|
43
|
+
# Turnip features and steps
|
44
|
+
watch(%r{^spec/acceptance/(.+)\.feature$})
|
45
|
+
watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
|
46
|
+
end
|
47
|
+
|
data/README.md
ADDED
@@ -0,0 +1,209 @@
|
|
1
|
+
# Overview
|
2
|
+
|
3
|
+
Polyhedra is a tool to help you write programs that manage dice using
|
4
|
+
the kinds of expressions most RPG rulebooks use. Some examples of dice
|
5
|
+
expressions that can be handled:
|
6
|
+
|
7
|
+
* d20
|
8
|
+
* 3d6
|
9
|
+
* 6d6
|
10
|
+
* 1d4-1
|
11
|
+
* 2d6x10
|
12
|
+
* 2d6*10
|
13
|
+
* 4d6t3 # roll 4d6, take highest 3
|
14
|
+
* 3d6r2 # roll 3d6, re-rolling 1's or 2's
|
15
|
+
* 2d6-1x10 # note that +/- modifiers happen before multipliers; that's just how dice math works. 2d6-1x10 == 1-11x10 == 10-110
|
16
|
+
* 1d6 fire # you can assign units to the dice as well
|
17
|
+
|
18
|
+
Polyhedra understands dice math:
|
19
|
+
|
20
|
+
* 3d6 + 2d6 => 5d6
|
21
|
+
* 2d6 + 1d4 => 2d6 + 1d4 # dissimilar bases can't be added
|
22
|
+
* 1d6 fire + 1d4 fire + 1d6 earth => 1d6 + 1d4 fire + 1d6 earth # similar units are grouped before bases
|
23
|
+
|
24
|
+
Note that adding two dissimilar dice results in a "DiceSet", which is
|
25
|
+
a collection of Dice objects. You can still "roll" a DiceSet just like
|
26
|
+
you would a Dice object, but #sides won't work and #units will return
|
27
|
+
an array.
|
28
|
+
|
29
|
+
Polyhedra also understands units, and knows to avoid adding dice of
|
30
|
+
different units:
|
31
|
+
|
32
|
+
* 3d6 fire + 2d6 physical
|
33
|
+
* 1d4 frost + 2
|
34
|
+
|
35
|
+
# Dice Notation
|
36
|
+
|
37
|
+
The simplest RPG Dice notation is given as numbers and letters of the
|
38
|
+
form _NdS_, where N is the number of dice to roll and S is the number
|
39
|
+
of sides per die. When you roll all the dice in a Yahtzee cup, you are
|
40
|
+
rolling 5d6, or five dice, each with six sides. If you are rolling a
|
41
|
+
single die, you can omit the 1 and represent the notation as e.g d6.
|
42
|
+
|
43
|
+
Role-playing games make allowances for other alterations to the roll:
|
44
|
+
you can add or subtract (2d6+3, 1d4-1), multiply or divide (1d4x10,
|
45
|
+
1d6/2). (Note that most game systems round down, so 1d6/2 actually
|
46
|
+
yields a number between 0 and 3.)
|
47
|
+
|
48
|
+
Mathematically, none of the following are equivalent: 1d3, 1d4-1,
|
49
|
+
1d6/2. The first one cannot roll 0's, and although the second two have
|
50
|
+
the same min/max range, 1d4-1 has an even distribution--exactly a 25%
|
51
|
+
chance of rolling each number--while 1d6/2 has an uneven distribution
|
52
|
+
(a 17% chance of rolling a 0, 33% chance of rolling a 1, etc).
|
53
|
+
|
54
|
+
## Notational Affixes
|
55
|
+
|
56
|
+
* rR - Reroll any dice equal or below R. R must be < S. 3d6r3 will
|
57
|
+
roll 12-18 as each die must be 4 or higher to be kept.
|
58
|
+
Mathematically 3d6r3 is identical to 3d3+9 but the semantics are
|
59
|
+
considered to be different so the extra notation is allowed.
|
60
|
+
|
61
|
+
* RR - Reroll any dice equal or above R. R must be > 1 and <= S.
|
62
|
+
|
63
|
+
* kM - Keep highest M dice. 1 < M < N. 4d6k3 means roll 4d6, but only
|
64
|
+
count the highest 3 dice in the final tally.
|
65
|
+
|
66
|
+
* KM - Keep lowest M dice. 1 < M < N.
|
67
|
+
|
68
|
+
|
69
|
+
|
70
|
+
# Order of Operations
|
71
|
+
|
72
|
+
Rerolls are considered first, then keeps. Next--and this is the
|
73
|
+
reverse of typical algebraic precedence--addition or subtraction
|
74
|
+
happens and finally multiplication or division. Thus
|
75
|
+
|
76
|
+
4d6r1k3+1x10
|
77
|
+
|
78
|
+
Would roll dice until 4 dice showed 2 or higher, then the top 3 dice
|
79
|
+
would be kept. The range at this point would be 6-18. 1 would be added
|
80
|
+
to this bumping the range to 7-19. Finally the roll is mulitplied by
|
81
|
+
10 to yield a range of 70-190. Note that because multiplication is
|
82
|
+
performed last the final value will be in steps of the multiplier;
|
83
|
+
e.g. the above dice expression could roll a 70, 80, 90, etc., but
|
84
|
+
could not roll a 75 or an 81.
|
85
|
+
|
86
|
+
# Monkeypatch! Woo! (Fixnum shorthand)
|
87
|
+
|
88
|
+
Polyhedra will add .d* methods to Fixnum and to Kernel (d20 is the same
|
89
|
+
as 1.d20 is the same as Dice.new "1d20"). Since this is a monkeypatch,
|
90
|
+
it's optional. Require polyhedra/shorthand instead of polyhedra to get
|
91
|
+
these methods.
|
92
|
+
|
93
|
+
|
94
|
+
require 'polyhedra/shorthand'
|
95
|
+
dice = 3.d6
|
96
|
+
# => <#Dice: 3d6>
|
97
|
+
|
98
|
+
fire1 = 1.d6("fire")
|
99
|
+
fire2 = 2.d8("fire")
|
100
|
+
fire3 = 3.d6(:fire) # symbols and strings are okay
|
101
|
+
frost = 2.d6("frost")
|
102
|
+
|
103
|
+
fire1 + fire2 + fire3 + frost
|
104
|
+
# => <DiceSet: <Dice: 4d6, fire>, <Dice: 2d8, fire>, <Dice: 2d6 frost>]
|
105
|
+
|
106
|
+
Note that while the shorthand allows for modifiers like -1 or x10, it
|
107
|
+
looks kind of lame.
|
108
|
+
|
109
|
+
1.d6("r1-1")
|
110
|
+
# => <Dice: 1d6r1-1>
|
111
|
+
2.d10("+3x10")
|
112
|
+
# => <Dice: 2d10+3x10>
|
113
|
+
d20
|
114
|
+
# => <Dice: 1d20>
|
115
|
+
|
116
|
+
I am open to changing the interface to cascade like so:
|
117
|
+
|
118
|
+
4.d6.r1.k3("+1x10") but honestly this still looks unnecessarily
|
119
|
+
complicated. Better to just use Dice.new() at this point.
|
120
|
+
|
121
|
+
Then again, since we have to catch method_missing to allow for
|
122
|
+
arbitrary digits, maybe we could steal a page from the Rails playbook
|
123
|
+
and allow longer methods names that encode the entire sequence, such
|
124
|
+
as:
|
125
|
+
|
126
|
+
4.d6r1t3
|
127
|
+
# => <Dice: 4d6r1t3
|
128
|
+
|
129
|
+
The only gotcha is that we can't capture +/- in its proper place
|
130
|
+
(immediately after the dX sequence). Legal ruby would be `4.d6r1t3+3`
|
131
|
+
but proper dice notation would `4.d6+3r1t3` which won't work.
|
132
|
+
|
133
|
+
Hrm, if a Dice.new() or Fixnum#dXX statement returns a Dice object,
|
134
|
+
maybe we should define +,-,*,/ etc to return new Dice objects?
|
135
|
+
|
136
|
+
d6
|
137
|
+
# => <Dice: 1d6>
|
138
|
+
d6 + 1
|
139
|
+
# => <Dice: 1d6+1>
|
140
|
+
|
141
|
+
|
142
|
+
## Complex Notation
|
143
|
+
|
144
|
+
A set of Dice can be collected into a DiceSet and rolled as a unit:
|
145
|
+
|
146
|
+
bag = DiceSet.new
|
147
|
+
bag << Dice.new('3d6')
|
148
|
+
bag << '2d4' # bag#<< accepts Dice or string
|
149
|
+
bag
|
150
|
+
# => <DiceSet: <Dice: 3d6>, <Dice: 2d4>>
|
151
|
+
bag.roll
|
152
|
+
# => 19
|
153
|
+
bag.max
|
154
|
+
# => 26
|
155
|
+
bag.min
|
156
|
+
# => 5
|
157
|
+
|
158
|
+
## Units
|
159
|
+
|
160
|
+
Each Dice object can have its own units, and units are treated as a
|
161
|
+
higher-order grouping than number of sides. Note that #min, #max and
|
162
|
+
#roll will return an array of pairs containing the amount and unit for
|
163
|
+
each type of dice.
|
164
|
+
|
165
|
+
bag = DiceSet.new
|
166
|
+
bag << 1.d6("fire")
|
167
|
+
bag << 1.d6("fire")
|
168
|
+
bag << 1.d4("frost")
|
169
|
+
bag.min
|
170
|
+
# => [[2, :fire], [1, :frost]]
|
171
|
+
bag.min_without_units
|
172
|
+
# => 3
|
173
|
+
bag.max
|
174
|
+
# => [[12, :fire], [4, :frost]]
|
175
|
+
bag.max_without_units
|
176
|
+
# => 16
|
177
|
+
bag.roll
|
178
|
+
# => [[7, :fire], [2, :frost]]
|
179
|
+
bag.roll_without_units
|
180
|
+
# => 11
|
181
|
+
|
182
|
+
You can also pass a specific unit to #min, #max and #roll to isolate
|
183
|
+
part of the roll:
|
184
|
+
|
185
|
+
bag.max(:fire)
|
186
|
+
# => 12
|
187
|
+
bag.min(:frost)
|
188
|
+
# => 1
|
189
|
+
bag.roll(:fire)
|
190
|
+
# => 3
|
191
|
+
|
192
|
+
|
193
|
+
# Contributing
|
194
|
+
|
195
|
+
1. Pull requests are welcome!
|
196
|
+
2. Fork the repo
|
197
|
+
3. Write _good_ specs for your change: specs that fully cover and
|
198
|
+
clearly document the intent of your change
|
199
|
+
4. Send a pull request
|
200
|
+
|
201
|
+
*Note:* If you want to make a wild and crazy change (and have it
|
202
|
+
merged into the gem), I'm open to that--but please get in touch with
|
203
|
+
me and let's chat about the direction you want to go so you don't
|
204
|
+
waste your time (unless you're okay with your changes staying in
|
205
|
+
your fork forever)
|
206
|
+
|
207
|
+
|
208
|
+
|
209
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
data/lib/polyhedra.rb
ADDED
@@ -0,0 +1,112 @@
|
|
1
|
+
module Polyhedra
|
2
|
+
class Dice
|
3
|
+
include Comparable
|
4
|
+
|
5
|
+
attr_accessor :number, :sides, :offset, :multiplier, :divisor, :reroll_under, :take_top
|
6
|
+
attr_writer :rng
|
7
|
+
|
8
|
+
def initialize(dice_expression)
|
9
|
+
dice_expression = dice_expression.gsub(/s+/, '')
|
10
|
+
|
11
|
+
@offset = @reroll_under = 0
|
12
|
+
@multiplier = @divisor = 1
|
13
|
+
|
14
|
+
@take_top = @number = [1, dice_expression.to_i].max
|
15
|
+
dice_expression.sub!(/^\d+/, '')
|
16
|
+
|
17
|
+
while dice_expression.length > 0
|
18
|
+
action, amount, dice_expression = pop_expression(dice_expression)
|
19
|
+
amount = amount.to_i
|
20
|
+
case action
|
21
|
+
when 'd'
|
22
|
+
self.sides = amount
|
23
|
+
when '+'
|
24
|
+
self.offset = amount
|
25
|
+
when '-'
|
26
|
+
self.offset = -amount
|
27
|
+
when '*', 'x'
|
28
|
+
self.multiplier = amount
|
29
|
+
when '/'
|
30
|
+
self.divisor = amount
|
31
|
+
when 'r'
|
32
|
+
self.reroll_under = amount
|
33
|
+
when 't'
|
34
|
+
self.take_top = amount
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def to_s
|
40
|
+
str = ""
|
41
|
+
str << "%dd%d" % [number, sides]
|
42
|
+
str << "%+d" % offset unless offset.zero?
|
43
|
+
str << "x%d" % multiplier unless multiplier == 1
|
44
|
+
str << "/%d" % divisor unless divisor == 1
|
45
|
+
str << "r%d" % reroll_under unless reroll_under.zero?
|
46
|
+
str << "t%d" % take_top unless take_top == number
|
47
|
+
str
|
48
|
+
end
|
49
|
+
|
50
|
+
def roll
|
51
|
+
rolled_dice = []
|
52
|
+
|
53
|
+
while rolled_dice.size < number
|
54
|
+
roll=rand(sides)+1
|
55
|
+
rolled_dice << roll if roll > reroll_under
|
56
|
+
end
|
57
|
+
|
58
|
+
rolled_dice = rolled_dice.sort.reverse.take take_top
|
59
|
+
|
60
|
+
rolled_dice.inject {|a,b| a+b}
|
61
|
+
end
|
62
|
+
|
63
|
+
def min
|
64
|
+
((take_top + offset + (reroll_under*take_top)) * multiplier) / divisor
|
65
|
+
end
|
66
|
+
|
67
|
+
def max
|
68
|
+
((take_top * sides + offset) * multiplier) / divisor
|
69
|
+
end
|
70
|
+
|
71
|
+
def pop_expression(string)
|
72
|
+
%r|^([dr+-x\*/])(\d+)(.*)$|.match(string).to_a.dup.tap(&:shift)
|
73
|
+
end
|
74
|
+
|
75
|
+
# def dice_matrix
|
76
|
+
# # TODO: make this work with take_top--probably a sort/take in the each_slice?
|
77
|
+
# # TODO: make this work with reroll--probably change the 1..sides to reroll+1..sides ?
|
78
|
+
# Array.new(number) { (1..sides).to_a }.inject(:product).flatten.each_slice(number).map {|ray| ray.sort.take take_top }
|
79
|
+
# end
|
80
|
+
|
81
|
+
def rand(num)
|
82
|
+
rng.rand(num)
|
83
|
+
end
|
84
|
+
|
85
|
+
def inverted_divisor
|
86
|
+
1.0 / divisor
|
87
|
+
end
|
88
|
+
|
89
|
+
def <=>(other)
|
90
|
+
a,b = [:sides, :number, :multiplier, :inverted_divisor, :offset].map {|sym| [send(sym), other.send(sym)] }.detect {|a,b| (a <=> b) != 0 }
|
91
|
+
a <=> b
|
92
|
+
# if sides != other.sides
|
93
|
+
# sides <=> other.sides
|
94
|
+
# else
|
95
|
+
# if number != other.number
|
96
|
+
# number <=> other.number
|
97
|
+
# else
|
98
|
+
# if multiplier != other.multiplier
|
99
|
+
# multiplier <=> other.multiplier
|
100
|
+
# else
|
101
|
+
# -divisor <=> -other.divisor
|
102
|
+
# end
|
103
|
+
# end
|
104
|
+
# end
|
105
|
+
end
|
106
|
+
|
107
|
+
private
|
108
|
+
def rng
|
109
|
+
@rng ||= Random.new
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
data/polyhedra.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "polyhedra/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "polyhedra"
|
7
|
+
s.version = Polyhedra::VERSION
|
8
|
+
s.authors = ["David Brady"]
|
9
|
+
s.email = ["github@shinybit.com"]
|
10
|
+
s.homepage = ""
|
11
|
+
s.summary = %q{Dice manipulation and rolling gem capable of handling complex dice expressions}
|
12
|
+
s.description = %q{Dice manipulation and rolling gem capable of handling complex RPG dice expressions, from a simple "d6" up to "4d6r1k3-2x10 frost" and "3d6", etc.}
|
13
|
+
|
14
|
+
s.rubyforge_project = "polyhedra"
|
15
|
+
|
16
|
+
s.files = `git ls-files`.split("\n")
|
17
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
18
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
|
+
s.require_paths = ["lib"]
|
20
|
+
|
21
|
+
# specify any dependencies here; for example:
|
22
|
+
|
23
|
+
# s.add_runtime_dependency "treetop"
|
24
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'polyhedra/dice'
|
3
|
+
|
4
|
+
# I roll twenties! rand(n) always returns max possible value
|
5
|
+
class LuckyRng < Random
|
6
|
+
def rand(num)
|
7
|
+
num-1
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
# Returns 0, 1, 2, ..., n-1 in order.
|
12
|
+
class GaussianRng < Random
|
13
|
+
def rand(num)
|
14
|
+
@sequence ||= (0..num).cycle
|
15
|
+
@sequence.next
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
# Returns... whatever... you tell... it to...
|
20
|
+
class JediMindTrickRng < Random
|
21
|
+
def initialize(sequence)
|
22
|
+
@sequence = sequence.cycle
|
23
|
+
super(0)
|
24
|
+
end
|
25
|
+
|
26
|
+
def rand(num)
|
27
|
+
@sequence.next
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
module Polyhedra
|
32
|
+
describe Dice do
|
33
|
+
context "simple dice expression" do
|
34
|
+
let(:dice) { Dice.new("3d6") }
|
35
|
+
|
36
|
+
it { dice.offset.should == 0 }
|
37
|
+
it { dice.sides.should == 6 }
|
38
|
+
it { dice.number.should == 3 }
|
39
|
+
it { dice.roll.should >= 3 }
|
40
|
+
it { dice.roll.should <= 18 }
|
41
|
+
it { dice.min.should == 3 }
|
42
|
+
it { dice.max.should == 18 }
|
43
|
+
end
|
44
|
+
|
45
|
+
context "with offset" do
|
46
|
+
it { Dice.new("3d6+2").min.should == 5 }
|
47
|
+
it { Dice.new("3d6+2").max.should == 20 }
|
48
|
+
it { Dice.new("3d6-2").min.should == 1 }
|
49
|
+
it { Dice.new("3d6-2").max.should == 16 }
|
50
|
+
end
|
51
|
+
|
52
|
+
context "with scalar" do
|
53
|
+
it { Dice.new("3d6x10").min.should == 30 }
|
54
|
+
it { Dice.new("3d6x10").max.should == 180 }
|
55
|
+
it { Dice.new("3d6*10").min.should == 30 }
|
56
|
+
it { Dice.new("3d6*10").max.should == 180 }
|
57
|
+
it { Dice.new("1d6/2").min.should == 0 }
|
58
|
+
it { Dice.new("1d6/2").max.should == 3 }
|
59
|
+
end
|
60
|
+
|
61
|
+
context "with reroll_under" do
|
62
|
+
let(:dice) { Dice.new("3d6r1") }
|
63
|
+
before(:each) do
|
64
|
+
dice.rng = JediMindTrickRng.new [0, 1, 0, 2, 0, 3]
|
65
|
+
end
|
66
|
+
it { dice.reroll_under.should == 1 }
|
67
|
+
it { dice.min.should == 6 }
|
68
|
+
it { dice.max.should == 18 }
|
69
|
+
|
70
|
+
it { dice.roll.should == 9 }
|
71
|
+
end
|
72
|
+
|
73
|
+
context "with take_top" do
|
74
|
+
let(:dice) { Dice.new("4d6t3") }
|
75
|
+
it { dice.take_top.should == 3 }
|
76
|
+
it { dice.max.should == 18 }
|
77
|
+
it { dice.min.should == 3 }
|
78
|
+
context "with loaded dice" do
|
79
|
+
before :each do
|
80
|
+
dice.rng = JediMindTrickRng.new [2,0,4,5]
|
81
|
+
end
|
82
|
+
it { dice.roll.should == 14 }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
context "when I roll twenties" do
|
87
|
+
let(:dice) { Dice.new("4d6") }
|
88
|
+
before :each do
|
89
|
+
dice.rng = LuckyRng.new
|
90
|
+
end
|
91
|
+
it { dice.roll.should == 24 }
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "#pop_expression" do
|
95
|
+
describe "returns subexpression, rest_of_string" do
|
96
|
+
it { Dice.new("1d6").pop_expression("r3string").should == ["r", "3", "string"] }
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
describe "#to_s" do
|
101
|
+
it { Dice.new("d20").to_s.should == "1d20" }
|
102
|
+
it { Dice.new("1d4*10").to_s.should =="1d4x10" }
|
103
|
+
|
104
|
+
# These expressions should all parse and to_s as themselves
|
105
|
+
[ "3d6",
|
106
|
+
"1d4+1",
|
107
|
+
"3d6r1",
|
108
|
+
"4d6t3",
|
109
|
+
"6d6+3r2t4",
|
110
|
+
"1d10x10",
|
111
|
+
"1d4+2x10",
|
112
|
+
"1d6/2",
|
113
|
+
"9d8+2x5r2t6"
|
114
|
+
].each do |str|
|
115
|
+
it "correctly parses '#{str}'" do
|
116
|
+
Dice.new(str).to_s.should eq str
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
describe "comparison" do
|
122
|
+
it { Dice.new("3d6").should == Dice.new("3d6") }
|
123
|
+
it("favors bases first") { Dice.new("10d4").should < Dice.new("1d5") }
|
124
|
+
it("favors number second") { Dice.new("3d6").should > Dice.new("2d6") }
|
125
|
+
it("favors multipliers third") { Dice.new("3d6x10").should > Dice.new("3d6x8") }
|
126
|
+
it("favors divisors third") { Dice.new("3d6/10").should < Dice.new("3d6/8") }
|
127
|
+
it("favors offset fourth") { Dice.new("3d6+3").should > Dice.new("3d6") }
|
128
|
+
end
|
129
|
+
|
130
|
+
describe "dice math" do
|
131
|
+
describe "addition" do
|
132
|
+
it { Dice.new("3d6")}
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: polyhedra
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.3.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- David Brady
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-09-17 00:00:00.000000000 Z
|
13
|
+
dependencies: []
|
14
|
+
description: Dice manipulation and rolling gem capable of handling complex RPG dice
|
15
|
+
expressions, from a simple "d6" up to "4d6r1k3-2x10 frost" and "3d6", etc.
|
16
|
+
email:
|
17
|
+
- github@shinybit.com
|
18
|
+
executables: []
|
19
|
+
extensions: []
|
20
|
+
extra_rdoc_files: []
|
21
|
+
files:
|
22
|
+
- .gitignore
|
23
|
+
- .rspec
|
24
|
+
- .rvmrc
|
25
|
+
- Gemfile
|
26
|
+
- Guardfile
|
27
|
+
- README.md
|
28
|
+
- Rakefile
|
29
|
+
- lib/polyhedra.rb
|
30
|
+
- lib/polyhedra/dice.rb
|
31
|
+
- lib/polyhedra/version.rb
|
32
|
+
- polyhedra.gemspec
|
33
|
+
- spec/lib/dice_spec.rb
|
34
|
+
- spec/spec_helper.rb
|
35
|
+
homepage: ''
|
36
|
+
licenses: []
|
37
|
+
post_install_message:
|
38
|
+
rdoc_options: []
|
39
|
+
require_paths:
|
40
|
+
- lib
|
41
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
42
|
+
none: false
|
43
|
+
requirements:
|
44
|
+
- - ! '>='
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
48
|
+
none: false
|
49
|
+
requirements:
|
50
|
+
- - ! '>='
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: '0'
|
53
|
+
requirements: []
|
54
|
+
rubyforge_project: polyhedra
|
55
|
+
rubygems_version: 1.8.23
|
56
|
+
signing_key:
|
57
|
+
specification_version: 3
|
58
|
+
summary: Dice manipulation and rolling gem capable of handling complex dice expressions
|
59
|
+
test_files:
|
60
|
+
- spec/lib/dice_spec.rb
|
61
|
+
- spec/spec_helper.rb
|