tabletop 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +3 -0
- data/Gemfile +3 -0
- data/LICENSE +20 -0
- data/README.markdown +44 -0
- data/Rakefile +37 -0
- data/lib/fixnum.rb +28 -0
- data/lib/tabletop/die.rb +59 -0
- data/lib/tabletop/pool.rb +115 -0
- data/lib/tabletop/version.rb +3 -0
- data/lib/tabletop.rb +3 -0
- data/spec/die_spec.rb +189 -0
- data/spec/fixnum_spec.rb +24 -0
- data/spec/pool_spec.rb +193 -0
- data/spec/spec_helper.rb +19 -0
- data/tabletop.gemspec +33 -0
- metadata +148 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Nick Novitski
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.markdown
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# tabletop
|
2
|
+
|
3
|
+
Tabletop aims to provide a simple way of describing, automating and tracking the tools and tasks involved in "analog" games, determining results from the motions and properties of various dice and chips.
|
4
|
+
|
5
|
+
Currently, you can roll dice with it.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
gem install tabletop
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Just `require 'tabletop'` and off you go! The easiest way to create a pool of dice is exactly the way you'd expect.
|
14
|
+
|
15
|
+
3.d6 #=> [3 (d6), 3 (d6), 4 (d6)]
|
16
|
+
2.d10 + 1.d8 #=> [2 (d10), 6 (d10), 8 (d8)]
|
17
|
+
6.d17 #=> [8 (d17), 3 (d17), 7 (d17), 16 (d17), 11 (d17), 10 (d17)]
|
18
|
+
|
19
|
+
Pools are arrays of Dice objects that have a few nice extra functions.
|
20
|
+
|
21
|
+
d&d_strength = 3.d6.roll.sum #=> 13
|
22
|
+
ore_character = 10.d10.sets #=> ["3x2", "2x8", "1x7", "1x6", "1x4", "1x3", "1x1"]
|
23
|
+
cortex_result = (1.d8 + 2.d6 + 1.d4).highest(2).sum #=> 9
|
24
|
+
|
25
|
+
Dice are pretty straightforward.
|
26
|
+
|
27
|
+
d = Die.new
|
28
|
+
d.sides #=> 6
|
29
|
+
d.result #=> 4
|
30
|
+
d.roll #=> "2 (d6)"
|
31
|
+
|
32
|
+
## Note on Patches/Pull Requests
|
33
|
+
|
34
|
+
* Fork the project.
|
35
|
+
* Create a topic branch
|
36
|
+
* Make tests that describe your feature addition or bug fix.
|
37
|
+
* Write code that passes those tests
|
38
|
+
* Commit, without altering the rakefile or version.
|
39
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
40
|
+
* Send me a pull request.
|
41
|
+
|
42
|
+
## Copyright
|
43
|
+
|
44
|
+
Copyright (c) 2011 Nick Novitski. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'bundler'
|
5
|
+
rescue LoadError
|
6
|
+
$stderr.puts "You must install bundler - run `gem install bundler`"
|
7
|
+
end
|
8
|
+
|
9
|
+
begin
|
10
|
+
Bundler.setup
|
11
|
+
rescue Bundler::BundlerError => e
|
12
|
+
$stderr.puts e.message
|
13
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
14
|
+
exit e.status_code
|
15
|
+
end
|
16
|
+
require 'rake'
|
17
|
+
|
18
|
+
require 'bueller'
|
19
|
+
Bueller::Tasks.new
|
20
|
+
|
21
|
+
require 'rspec/core/rake_task'
|
22
|
+
RSpec::Core::RakeTask.new(:examples) do |examples|
|
23
|
+
examples.rspec_opts = '-Ispec'
|
24
|
+
end
|
25
|
+
|
26
|
+
task :default => :examples
|
27
|
+
|
28
|
+
require 'rdoc/task'
|
29
|
+
RDoc::Task.new do |rdoc|
|
30
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
31
|
+
|
32
|
+
rdoc.main = 'README.rdoc'
|
33
|
+
rdoc.rdoc_dir = 'rdoc'
|
34
|
+
rdoc.title = "tabletop #{version}"
|
35
|
+
rdoc.rdoc_files.include('README*')
|
36
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
37
|
+
end
|
data/lib/fixnum.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'tabletop'
|
2
|
+
|
3
|
+
class Fixnum
|
4
|
+
def dX(sides)
|
5
|
+
dice = []
|
6
|
+
times { dice << Tabletop::Die.new(sides) }
|
7
|
+
Tabletop::Pool.new(dice)
|
8
|
+
end
|
9
|
+
def dF
|
10
|
+
dice = []
|
11
|
+
times {dice << Tabletop::FudgeDie.new}
|
12
|
+
Tabletop::Pool.new(dice)
|
13
|
+
end
|
14
|
+
def method_missing(symbol, *args, &block)
|
15
|
+
if symbol =~ /^d(.*)$/
|
16
|
+
dX($1.to_i)
|
17
|
+
else
|
18
|
+
super
|
19
|
+
end
|
20
|
+
end
|
21
|
+
def respond_to?(symbol, include_private = false)
|
22
|
+
if symbol.to_s =~ /^d(.*)$/
|
23
|
+
true if $1.to_i > 0
|
24
|
+
else
|
25
|
+
super
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
data/lib/tabletop/die.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
module Tabletop
|
2
|
+
class Die
|
3
|
+
include Comparable
|
4
|
+
attr_reader :sides, :result
|
5
|
+
def initialize(sides=6, result=nil)
|
6
|
+
if sides <= 0
|
7
|
+
raise ArgumentError, "Die cannot have #{sides} sides"
|
8
|
+
end
|
9
|
+
unless sides.kind_of? Integer
|
10
|
+
raise ArgumentError, "Parameter must be Integer, not #{sides.class}"
|
11
|
+
end
|
12
|
+
@sides = sides
|
13
|
+
if result.nil?
|
14
|
+
result = roll
|
15
|
+
else
|
16
|
+
raise ArgumentError unless valid_result?(result)
|
17
|
+
end
|
18
|
+
@result = result
|
19
|
+
end
|
20
|
+
def roll
|
21
|
+
@result = rand(sides)+1
|
22
|
+
end
|
23
|
+
def inspect
|
24
|
+
"#{@result} (d#{@sides})"
|
25
|
+
end
|
26
|
+
def result=(new_result)
|
27
|
+
raise ArgumentError unless valid_result?(new_result)
|
28
|
+
@result = new_result
|
29
|
+
end
|
30
|
+
def <=>(operand)
|
31
|
+
@result <=> operand.to_int
|
32
|
+
end
|
33
|
+
def to_int
|
34
|
+
@result
|
35
|
+
end
|
36
|
+
|
37
|
+
protected
|
38
|
+
def valid_result?(result)
|
39
|
+
result > 0 and result <= @sides
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class FudgeDie < Die
|
44
|
+
def initialize(result = nil)
|
45
|
+
super(3, result)
|
46
|
+
end
|
47
|
+
def roll
|
48
|
+
@result = rand(sides)-1
|
49
|
+
end
|
50
|
+
def inspect
|
51
|
+
"[#{['-', ' ', '+'][@result+1]}] (dF)"
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
def valid_result?(result)
|
56
|
+
[1,0,-1].include?(result)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,115 @@
|
|
1
|
+
require_relative 'die'
|
2
|
+
require 'delegate'
|
3
|
+
|
4
|
+
module Tabletop
|
5
|
+
class Pool < DelegateClass(Array)
|
6
|
+
include Comparable
|
7
|
+
def initialize(init_dice)
|
8
|
+
return super(init_dice) if init_dice.class == Array
|
9
|
+
d_groups = init_dice.split
|
10
|
+
dice = []
|
11
|
+
d_groups.each do |d_notation|
|
12
|
+
number, sides = d_notation.split('d')
|
13
|
+
number = number.to_i
|
14
|
+
number += 1 if number == 0
|
15
|
+
if sides.to_i > 0
|
16
|
+
number.times { dice << Die.new(sides.to_i)}
|
17
|
+
elsif sides == "F"
|
18
|
+
number.times {dice << FudgeDie.new}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
super(dice)
|
22
|
+
end
|
23
|
+
def +(operand)
|
24
|
+
# if the operator is a pool, or an array only of Die objects...
|
25
|
+
if operand.class == Pool or (operand.class == Array and !(operand.detect{|obj| obj.class != Die}))
|
26
|
+
new_union(operand)
|
27
|
+
elsif operand.kind_of? Numeric
|
28
|
+
sum + operand
|
29
|
+
else
|
30
|
+
raise ArgumentError, "Cannot add operand of class #{operand.class}"
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def <=>(operand)
|
35
|
+
sum <=> operand.to_int
|
36
|
+
end
|
37
|
+
|
38
|
+
def results
|
39
|
+
map {|die| die.result}
|
40
|
+
end
|
41
|
+
def dice
|
42
|
+
fudge = nil
|
43
|
+
result = {}
|
44
|
+
each do |die|
|
45
|
+
if die.class == FudgeDie
|
46
|
+
fudge = count {|d| d.class == FudgeDie}
|
47
|
+
else
|
48
|
+
result[die.sides] = count {|d| d.sides == die.sides}
|
49
|
+
end
|
50
|
+
end
|
51
|
+
d_array = result.sort.collect do |d_group|
|
52
|
+
number = d_group[1]
|
53
|
+
number = "" if number == 1
|
54
|
+
sides = d_group[0]
|
55
|
+
"#{number}d#{sides}"
|
56
|
+
end
|
57
|
+
if fudge
|
58
|
+
d_array << "#{fudge}dF"
|
59
|
+
end
|
60
|
+
d_array
|
61
|
+
end
|
62
|
+
def roll
|
63
|
+
each do |die|
|
64
|
+
die.roll
|
65
|
+
end
|
66
|
+
self
|
67
|
+
end
|
68
|
+
def sum
|
69
|
+
inject(0) {|sum, d| sum + d.result}
|
70
|
+
end
|
71
|
+
def to_int
|
72
|
+
sum
|
73
|
+
end
|
74
|
+
def sets
|
75
|
+
result = {}
|
76
|
+
each do |die|
|
77
|
+
result[die.result] = count {|d| d.result == die.result}
|
78
|
+
end
|
79
|
+
result.sort_by{|height, width| [width, height] }.collect {|i| i[1].to_s+"x"+i[0].to_s}.reverse
|
80
|
+
end
|
81
|
+
def highest(n=1)
|
82
|
+
sorted = sort_by {|d| d.result}.reverse
|
83
|
+
Pool.new(sorted.first(n))
|
84
|
+
end
|
85
|
+
def lowest(n=1)
|
86
|
+
sorted = sort_by {|d| d.result}
|
87
|
+
Pool.new(sorted.first(n))
|
88
|
+
end
|
89
|
+
def drop_highest(n=1)
|
90
|
+
Pool.new(self-highest(n))
|
91
|
+
end
|
92
|
+
def drop_lowest(n=1)
|
93
|
+
Pool.new(self-lowest(n))
|
94
|
+
end
|
95
|
+
def drop(to_drop)
|
96
|
+
to_drop = [to_drop].flatten #turn it into an array if it isn't one.
|
97
|
+
kept = reject{|die| to_drop.any?{|drop_value| die.result == drop_value }}
|
98
|
+
return Pool.new(kept)
|
99
|
+
end
|
100
|
+
|
101
|
+
private
|
102
|
+
def new_union(array)
|
103
|
+
union = [self, array].flatten
|
104
|
+
new_pool =[]
|
105
|
+
union.each do |die|
|
106
|
+
if die.class == FudgeDie
|
107
|
+
new_pool << FudgeDie.new(die.result)
|
108
|
+
else
|
109
|
+
new_pool << Die.new(die.sides, die.result)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
Pool.new(new_pool)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
data/lib/tabletop.rb
ADDED
data/spec/die_spec.rb
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Tabletop
|
4
|
+
describe Die do
|
5
|
+
before :each do
|
6
|
+
@d6_2 = Die.new(6, 2)
|
7
|
+
@d6_3 = Die.new(6, 3)
|
8
|
+
end
|
9
|
+
describe "#sides" do
|
10
|
+
it "can be accessed" do
|
11
|
+
d = Die.new(6)
|
12
|
+
d.sides.should == 6
|
13
|
+
d = Die.new(20)
|
14
|
+
d.sides.should == 20
|
15
|
+
d = Die.new(7)
|
16
|
+
d.sides.should equal(7)
|
17
|
+
end
|
18
|
+
it "is 6 by default" do
|
19
|
+
d = Die.new
|
20
|
+
d.sides.should equal(6)
|
21
|
+
end
|
22
|
+
it "cannot be 0 or less" do
|
23
|
+
lambda { Die.new(0) }.should raise_error(ArgumentError)
|
24
|
+
lambda { Die.new(-5) }.should raise_error(ArgumentError)
|
25
|
+
end
|
26
|
+
it "cannot be a non-integer" do
|
27
|
+
lambda { Die.new(0.1) }.should raise_error(ArgumentError)
|
28
|
+
lambda { Die.new(5.7694) }.should raise_error(ArgumentError)
|
29
|
+
lambda { Die.new("foof") }.should raise_error(ArgumentError)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
describe "#result" do
|
33
|
+
before :each do
|
34
|
+
Random.srand(10)
|
35
|
+
end
|
36
|
+
it "should be random on instantiation by default" do
|
37
|
+
d = Die.new
|
38
|
+
d.result.should equal(2)
|
39
|
+
d = Die.new(10)
|
40
|
+
d.result.should equal(5)
|
41
|
+
d = Die.new(50)
|
42
|
+
d.result.should equal(16)
|
43
|
+
end
|
44
|
+
it "can be set to a given value on instantiation" do
|
45
|
+
Die.new(6, 5).result.should == 5
|
46
|
+
Die.new(10, 2).result.should == 2
|
47
|
+
end
|
48
|
+
it "cannot be a non-integer" do
|
49
|
+
lambda { Die.new(0.1) }.should raise_error(ArgumentError)
|
50
|
+
lambda { Die.new(5.7694) }.should raise_error(ArgumentError)
|
51
|
+
lambda { Die.new("foof") }.should raise_error(ArgumentError)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
describe "#result=" do
|
55
|
+
it "can only be set to an integer i, where 0 < i <= sides" do
|
56
|
+
d = Die.new
|
57
|
+
lambda { d.result = 0 }.should raise_error(ArgumentError)
|
58
|
+
lambda { d.result = -5 }.should raise_error(ArgumentError)
|
59
|
+
lambda { d.result = 7 }.should raise_error(ArgumentError)
|
60
|
+
d = Die.new(10)
|
61
|
+
d.result = 7
|
62
|
+
lambda { d.result = 22 }.should raise_error(ArgumentError)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
describe "#roll" do
|
66
|
+
before(:each) do
|
67
|
+
Random.srand(10)
|
68
|
+
end
|
69
|
+
context "six sides" do
|
70
|
+
it "should return a random result between 1 and @sides" do
|
71
|
+
d = Die.new
|
72
|
+
#d.roll.should == 2 # This result gets swallowed by the init roll
|
73
|
+
d.roll.should == 6
|
74
|
+
d.roll.should == 5
|
75
|
+
d.roll.should == 1
|
76
|
+
end
|
77
|
+
it "should alter the result appropriately" do
|
78
|
+
d = Die.new
|
79
|
+
#d.roll # Covered by the initial roll
|
80
|
+
d.result.should == 2
|
81
|
+
d.roll
|
82
|
+
d.result.should == 6
|
83
|
+
d.roll
|
84
|
+
d.result.should == 5
|
85
|
+
d.roll
|
86
|
+
d.result.should == 1
|
87
|
+
end
|
88
|
+
end
|
89
|
+
context "eleven sides" do
|
90
|
+
it "should return a random result between 1 and @sides" do
|
91
|
+
d = Die.new(11)
|
92
|
+
# d.roll.should == 10 # This result gets swallowed by the init roll
|
93
|
+
d.roll.should == 5
|
94
|
+
d.roll.should == 1
|
95
|
+
d.roll.should == 2
|
96
|
+
end
|
97
|
+
it "should alter the result appropriately" do
|
98
|
+
d = Die.new(11)
|
99
|
+
#d.roll # covered by the initial roll
|
100
|
+
d.result.should == 10
|
101
|
+
d.roll
|
102
|
+
d.result.should == 5
|
103
|
+
d.roll
|
104
|
+
d.result.should == 1
|
105
|
+
d.roll
|
106
|
+
d.result.should == 2
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
describe "#inspect" do
|
111
|
+
before(:each) do
|
112
|
+
Random.srand(10)
|
113
|
+
end
|
114
|
+
it "should be interesting" do
|
115
|
+
Die.new.inspect.should == "2 (d6)"
|
116
|
+
Die.new.inspect.should == "6 (d6)"
|
117
|
+
end
|
118
|
+
end
|
119
|
+
describe "#to_int" do
|
120
|
+
it "returns the result" do
|
121
|
+
d = Die.new
|
122
|
+
d.to_int.should == d.result
|
123
|
+
end
|
124
|
+
end
|
125
|
+
describe "<=>" do
|
126
|
+
it "compares numeric objects with the die's value" do
|
127
|
+
(@d6_3 < 4).should be_true
|
128
|
+
(@d6_3 < 2).should be_false
|
129
|
+
(@d6_3 > 2).should be_true
|
130
|
+
(@d6_3 > 4).should be_false
|
131
|
+
(@d6_3 >= 3).should be_true
|
132
|
+
(@d6_3 >= 10).should be_false
|
133
|
+
(@d6_3 <= 3).should be_true
|
134
|
+
(@d6_3 <= 2).should be_false
|
135
|
+
(@d6_3 == 3).should be_true
|
136
|
+
(@d6_3 == 6).should be_false
|
137
|
+
end
|
138
|
+
it "compares dice with each other by value" do
|
139
|
+
(@d6_3 > @d6_2).should be_true
|
140
|
+
(@d6_3 < @d6_2).should be_false
|
141
|
+
(@d6_2 < @d6_3).should be_true
|
142
|
+
(@d6_2 > @d6_3).should be_false
|
143
|
+
(@d6_3 == @d6_2).should be_false
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
describe FudgeDie do
|
148
|
+
before(:each) do
|
149
|
+
Random.srand(10)
|
150
|
+
@fudge = FudgeDie.new
|
151
|
+
end
|
152
|
+
describe "#sides" do
|
153
|
+
it "is always 3" do
|
154
|
+
@fudge.sides.should == 3
|
155
|
+
end
|
156
|
+
end
|
157
|
+
describe "#result" do
|
158
|
+
it "can be set on instantiation" do
|
159
|
+
FudgeDie.new(1).result.should == 1
|
160
|
+
FudgeDie.new(0).result.should == 0
|
161
|
+
FudgeDie.new(-1).result.should == -1
|
162
|
+
end
|
163
|
+
it "is randomly rolled if not set" do
|
164
|
+
@fudge.result.should == 0
|
165
|
+
end
|
166
|
+
it "can only be one of either -1, 0, or 1" do
|
167
|
+
lambda {FudgeDie.new(2)}.should raise_error(ArgumentError)
|
168
|
+
lambda {FudgeDie.new(0.6)}.should raise_error(ArgumentError)
|
169
|
+
lambda {FudgeDie.new("5")}.should raise_error(ArgumentError)
|
170
|
+
end
|
171
|
+
end
|
172
|
+
describe "#result=" do
|
173
|
+
it "cannot be set to anything but -1, 0, or 1" do
|
174
|
+
lambda {@fudge.result = 2}.should raise_error(ArgumentError)
|
175
|
+
lambda {@fudge.result = 0.6}.should raise_error(ArgumentError)
|
176
|
+
lambda {@fudge.result = "5"}.should raise_error(ArgumentError)
|
177
|
+
@fudge.result = 1
|
178
|
+
@fudge.result.should == 1
|
179
|
+
end
|
180
|
+
end
|
181
|
+
describe "#inspect" do
|
182
|
+
it "should look like plusses, minuses and spaces" do
|
183
|
+
FudgeDie.new(1).inspect.should == "[+] (dF)"
|
184
|
+
FudgeDie.new(0).inspect.should == "[ ] (dF)"
|
185
|
+
FudgeDie.new(-1).inspect.should == "[-] (dF)"
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
data/spec/fixnum_spec.rb
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Tabletop
|
4
|
+
describe Fixnum do
|
5
|
+
describe "#dX" do
|
6
|
+
it "generates a pool of the appropriate size and type" do
|
7
|
+
1.d6.class.should == Pool
|
8
|
+
4.d7.dice.should == ["4d7"]
|
9
|
+
10.d100.class.should == Pool
|
10
|
+
end
|
11
|
+
it "shows up in respond_to?(:dN)" do
|
12
|
+
1.respond_to?(:d50).should be_true
|
13
|
+
end
|
14
|
+
end
|
15
|
+
describe "#dF" do
|
16
|
+
it "generates a pool of fudge dice" do
|
17
|
+
sotc = 4.dF
|
18
|
+
sotc.class.should == Pool
|
19
|
+
sotc.all? { |d| d.class == FudgeDie }.should be_true
|
20
|
+
sotc.dice.should == ["4dF"]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
data/spec/pool_spec.rb
ADDED
@@ -0,0 +1,193 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module Tabletop
|
4
|
+
describe Pool do
|
5
|
+
before :each do
|
6
|
+
Random.srand(10)
|
7
|
+
@d6 = Pool.new("d6")
|
8
|
+
@d17s = Pool.new("5d17")
|
9
|
+
@mixed = Pool.new("2d10 d20")
|
10
|
+
@fudge = Pool.new("3dF")
|
11
|
+
end
|
12
|
+
describe "#dice" do
|
13
|
+
it "should return an array of dice notation" do
|
14
|
+
@mixed.dice.should == ["2d10","d20"]
|
15
|
+
@d6.dice.should == ["d6"]
|
16
|
+
@d17s.dice.should == ["5d17"]
|
17
|
+
@fudge.dice.should == ["3dF"]
|
18
|
+
Pool.new("d20 2dF 2d10").dice.should == ["2d10","d20", "2dF"]
|
19
|
+
end
|
20
|
+
end
|
21
|
+
describe "[]" do
|
22
|
+
it "should access Die objects" do
|
23
|
+
@d6[0].class.should == Die
|
24
|
+
@fudge[0].class.should == FudgeDie
|
25
|
+
end
|
26
|
+
end
|
27
|
+
describe "+" do
|
28
|
+
it "should join Pools into new Pools" do
|
29
|
+
(@mixed + @d17s).class == Pool
|
30
|
+
(@d6 + @fudge).class == Pool
|
31
|
+
end
|
32
|
+
it "should persist die types" do
|
33
|
+
(@d6 + @fudge)[1].class.should == FudgeDie
|
34
|
+
end
|
35
|
+
it "should join pools without rolling them" do
|
36
|
+
merge = @d6 + @d17s
|
37
|
+
merge.results.should == [2, 5, 16, 1, 17, 9]
|
38
|
+
merge.roll
|
39
|
+
merge.results.should == [4, 17, 5, 16, 12, 12]
|
40
|
+
end
|
41
|
+
it "creates genuinely new pools" do
|
42
|
+
merge = @d6 + @d17s
|
43
|
+
merge.roll
|
44
|
+
@d6.results.should == [2]
|
45
|
+
@d17s.results.should == [5, 16, 1, 17, 9]
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should alter #dice accordingly" do
|
49
|
+
@d6 = Pool.new("d6")
|
50
|
+
@d17s = Pool.new("5d17")
|
51
|
+
@mixed = Pool.new("2d10 d20")
|
52
|
+
(@d6 + @d17s).dice.should == ["d6", "5d17"]
|
53
|
+
(@d17s + @d6).dice.should == ["d6", "5d17"]
|
54
|
+
(@d17s + @mixed).dice.should == ["2d10","5d17","d20"]
|
55
|
+
(@mixed + @fudge).dice.should == ["2d10", "d20", "3dF"]
|
56
|
+
end
|
57
|
+
it "should understand adding a number as looking for a sum result" do
|
58
|
+
(@d17s + 5).should == 53
|
59
|
+
(@mixed + @d6 + 10).should == 34
|
60
|
+
(@fudge + 3).should == 2
|
61
|
+
end
|
62
|
+
it "should add literal dice arrays as if they were pools" do
|
63
|
+
g = @d6 + [Die.new(6,3), Die.new(10, 4)]
|
64
|
+
g.results.should == [2, 3, 4]
|
65
|
+
g.dice.should == ["2d6", "d10"]
|
66
|
+
g.roll
|
67
|
+
@d6.results.should == [2]
|
68
|
+
end
|
69
|
+
it "should reject adding anything else" do
|
70
|
+
lambda {@d6 + "foof"}.should raise_error(ArgumentError)
|
71
|
+
lambda {@d6 + [Die.new, Object.new]}.should raise_error(ArgumentError)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
describe "#results" do
|
75
|
+
it "should be an array of random numbers" do
|
76
|
+
@d6.results.should == [2]
|
77
|
+
@d17s.results.should == [5, 16, 1, 17, 9]
|
78
|
+
@mixed.results.should == [10, 1, 11]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
describe "#roll" do
|
82
|
+
it "should return the Pool itself" do
|
83
|
+
@d6.roll.length.should == @d6.length
|
84
|
+
@d6.roll.class.should == @d6.class
|
85
|
+
end
|
86
|
+
it "should store the new values" do
|
87
|
+
@d6.roll
|
88
|
+
@d6.results.should == [4]
|
89
|
+
@d17s.roll
|
90
|
+
@d17s.results.should == [17, 5, 16, 12, 12]
|
91
|
+
@mixed.roll
|
92
|
+
@mixed.results.should == [2, 9, 5]
|
93
|
+
end
|
94
|
+
end
|
95
|
+
describe "#sum" do
|
96
|
+
it "should sum the dice values" do
|
97
|
+
@d6.sum.should == 2
|
98
|
+
@d17s.sum.should == 48
|
99
|
+
@mixed.sum.should == 22
|
100
|
+
@fudge.sum.should == -1
|
101
|
+
end
|
102
|
+
it "should be aliased to #to_int" do
|
103
|
+
@d6.to_int.should == @d6.sum
|
104
|
+
@d17s.to_int.should == @d17s.sum
|
105
|
+
@mixed.to_int.should == @mixed.sum
|
106
|
+
@fudge.to_int.should == @fudge.sum
|
107
|
+
end
|
108
|
+
end
|
109
|
+
describe "<=>" do
|
110
|
+
it "should compare the sums of different pools" do
|
111
|
+
@d17s.should >= @d6
|
112
|
+
@d6.should < Pool.new([Die.new(4, 4)])
|
113
|
+
end
|
114
|
+
it "should compare pools to numbers" do
|
115
|
+
@d6.should < 10
|
116
|
+
@d6.should == 2
|
117
|
+
@d17s.should <= 49
|
118
|
+
end
|
119
|
+
end
|
120
|
+
describe "#sets" do
|
121
|
+
it "should list the sets, in order by height and width" do
|
122
|
+
ore = Pool.new("10d10")
|
123
|
+
ore.sets.should == ["2x9", "2x5", "2x4", "2x2", "1x7", "1x1"]
|
124
|
+
ore.roll
|
125
|
+
ore.sets.should == ["3x10", "2x7", "1x6", "1x5", "1x4", "1x3", "1x2"]
|
126
|
+
ore.roll
|
127
|
+
ore.sets.should == ["3x9", "2x8", "2x7", "1x10", "1x3", "1x1"]
|
128
|
+
end
|
129
|
+
end
|
130
|
+
describe "#highest" do
|
131
|
+
it "should return a pool of the highest-value die" do
|
132
|
+
@d6.highest.class.should == Pool
|
133
|
+
@d6.highest.results.should == [2]
|
134
|
+
@d17s.highest.results.should == [17]
|
135
|
+
@mixed.highest.results.should == [11]
|
136
|
+
end
|
137
|
+
it "should return as many items as are specified" do
|
138
|
+
@d6.highest(5).results.should == [2]
|
139
|
+
@d17s.highest(3).results.should == [17, 16, 9]
|
140
|
+
@mixed.highest(2).results.should == [11, 10]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
describe "#lowest" do
|
144
|
+
it "should return a pool of the lowest-value die." do
|
145
|
+
@d6.lowest.results.should == [2]
|
146
|
+
@d17s.lowest.class.should == Pool
|
147
|
+
@d17s.lowest.results.should == [1]
|
148
|
+
@mixed.lowest.results.should == [1]
|
149
|
+
end
|
150
|
+
it "should return as many items as are specified" do
|
151
|
+
@d6.lowest(5).results.should == [2]
|
152
|
+
@d17s.lowest(3).results.should == [1, 5, 9]
|
153
|
+
@mixed.lowest(2).results.should == [1, 10]
|
154
|
+
end
|
155
|
+
end
|
156
|
+
describe "#drop_highest" do
|
157
|
+
it "should return a new pool missing the highest result" do
|
158
|
+
p = @d17s.drop_highest
|
159
|
+
p.results.should == [5, 16, 1, 9]
|
160
|
+
@d17s.results.should == [5, 16, 1, 17, 9]
|
161
|
+
end
|
162
|
+
it "should drop as many items as are specified and are possible" do
|
163
|
+
p = @d17s.drop_highest(2)
|
164
|
+
p.results.should == [5, 1, 9]
|
165
|
+
p = @d6.drop_highest(10)
|
166
|
+
p.results.should == []
|
167
|
+
end
|
168
|
+
end
|
169
|
+
describe "#drop_lowest" do
|
170
|
+
it "should return a pool missing the lowest result" do
|
171
|
+
p = @d17s.drop_lowest
|
172
|
+
p.results.should == [5, 16, 17, 9]
|
173
|
+
@d17s.results.should == [5, 16, 1, 17, 9]
|
174
|
+
end
|
175
|
+
it "should drop as many items as are specified" do
|
176
|
+
p = @d17s.drop_lowest(2)
|
177
|
+
p.results.should == [16, 17, 9]
|
178
|
+
end
|
179
|
+
end
|
180
|
+
describe "#drop" do
|
181
|
+
it "should drop any dice of the specified value" do
|
182
|
+
ore = Pool.new("10d10")
|
183
|
+
ore.results.should == [4, 1, 5, 7, 9, 2, 9, 5, 2, 4]
|
184
|
+
at_least_two = ore.drop(1)
|
185
|
+
at_least_two.results.should == [4, 5, 7, 9, 2, 9, 5, 2, 4]
|
186
|
+
at_least_three = ore.drop([1,2])
|
187
|
+
at_least_three.results.should == [4, 5, 7, 9, 9, 5, 4]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
context "pool has been emptied" do
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
begin
|
3
|
+
Bundler.setup
|
4
|
+
rescue Bundler::BundlerError => e
|
5
|
+
$stderr.puts e.message
|
6
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
7
|
+
exit e.status_code
|
8
|
+
end
|
9
|
+
|
10
|
+
require 'rspec'
|
11
|
+
require 'tabletop'
|
12
|
+
|
13
|
+
# Requires supporting files with custom matchers and macros, etc,
|
14
|
+
# in ./support/ and its subdirectories.
|
15
|
+
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
|
19
|
+
end
|
data/tabletop.gemspec
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
$:.push File.expand_path("../lib", __FILE__)
|
2
|
+
require 'tabletop/version'
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = 'tabletop'
|
6
|
+
s.version = Tabletop::VERSION
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.date = '2011-08-10'
|
9
|
+
s.authors = ['Nick Novitski']
|
10
|
+
s.email = 'nicknovitski@gmail.com'
|
11
|
+
s.homepage = 'http://github.com/njay/tabletop'
|
12
|
+
s.summary = 'A Ruby DSL for role-playing games'
|
13
|
+
s.description = 'Tabletop aims to provide a simple way of describing, automating and tracking the tools and tasks involved in "analog" games, determining results from the motions and properties of various dice and chips.'
|
14
|
+
s.extra_rdoc_files = [
|
15
|
+
'LICENSE',
|
16
|
+
'README.markdown',
|
17
|
+
]
|
18
|
+
|
19
|
+
s.required_rubygems_version = Gem::Requirement.new('>= 1.3.7')
|
20
|
+
s.rubygems_version = '1.3.7'
|
21
|
+
s.specification_version = 3
|
22
|
+
|
23
|
+
s.files = `git ls-files`.split("\n")
|
24
|
+
s.test_files = `git ls-files -- {spec,features}/*`.split("\n")
|
25
|
+
s.require_paths = ['lib']
|
26
|
+
|
27
|
+
s.add_development_dependency 'rspec'
|
28
|
+
s.add_development_dependency 'bundler'
|
29
|
+
s.add_development_dependency 'bueller'
|
30
|
+
s.add_development_dependency 'rake'
|
31
|
+
s.add_development_dependency 'rdoc'
|
32
|
+
end
|
33
|
+
|
metadata
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: tabletop
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Nick Novitski
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2011-08-10 00:00:00 -07:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: rspec
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
version: "0"
|
31
|
+
type: :development
|
32
|
+
version_requirements: *id001
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: bundler
|
35
|
+
prerelease: false
|
36
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
37
|
+
none: false
|
38
|
+
requirements:
|
39
|
+
- - ">="
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
segments:
|
42
|
+
- 0
|
43
|
+
version: "0"
|
44
|
+
type: :development
|
45
|
+
version_requirements: *id002
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: bueller
|
48
|
+
prerelease: false
|
49
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
type: :development
|
58
|
+
version_requirements: *id003
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: rake
|
61
|
+
prerelease: false
|
62
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
63
|
+
none: false
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
segments:
|
68
|
+
- 0
|
69
|
+
version: "0"
|
70
|
+
type: :development
|
71
|
+
version_requirements: *id004
|
72
|
+
- !ruby/object:Gem::Dependency
|
73
|
+
name: rdoc
|
74
|
+
prerelease: false
|
75
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
76
|
+
none: false
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
segments:
|
81
|
+
- 0
|
82
|
+
version: "0"
|
83
|
+
type: :development
|
84
|
+
version_requirements: *id005
|
85
|
+
description: Tabletop aims to provide a simple way of describing, automating and tracking the tools and tasks involved in "analog" games, determining results from the motions and properties of various dice and chips.
|
86
|
+
email: nicknovitski@gmail.com
|
87
|
+
executables: []
|
88
|
+
|
89
|
+
extensions: []
|
90
|
+
|
91
|
+
extra_rdoc_files:
|
92
|
+
- LICENSE
|
93
|
+
- README.markdown
|
94
|
+
files:
|
95
|
+
- .gitignore
|
96
|
+
- Gemfile
|
97
|
+
- LICENSE
|
98
|
+
- README.markdown
|
99
|
+
- Rakefile
|
100
|
+
- lib/fixnum.rb
|
101
|
+
- lib/tabletop.rb
|
102
|
+
- lib/tabletop/die.rb
|
103
|
+
- lib/tabletop/pool.rb
|
104
|
+
- lib/tabletop/version.rb
|
105
|
+
- spec/die_spec.rb
|
106
|
+
- spec/fixnum_spec.rb
|
107
|
+
- spec/pool_spec.rb
|
108
|
+
- spec/spec_helper.rb
|
109
|
+
- tabletop.gemspec
|
110
|
+
has_rdoc: true
|
111
|
+
homepage: http://github.com/njay/tabletop
|
112
|
+
licenses: []
|
113
|
+
|
114
|
+
post_install_message:
|
115
|
+
rdoc_options: []
|
116
|
+
|
117
|
+
require_paths:
|
118
|
+
- lib
|
119
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
120
|
+
none: false
|
121
|
+
requirements:
|
122
|
+
- - ">="
|
123
|
+
- !ruby/object:Gem::Version
|
124
|
+
segments:
|
125
|
+
- 0
|
126
|
+
version: "0"
|
127
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
128
|
+
none: false
|
129
|
+
requirements:
|
130
|
+
- - ">="
|
131
|
+
- !ruby/object:Gem::Version
|
132
|
+
segments:
|
133
|
+
- 1
|
134
|
+
- 3
|
135
|
+
- 7
|
136
|
+
version: 1.3.7
|
137
|
+
requirements: []
|
138
|
+
|
139
|
+
rubyforge_project:
|
140
|
+
rubygems_version: 1.3.7
|
141
|
+
signing_key:
|
142
|
+
specification_version: 3
|
143
|
+
summary: A Ruby DSL for role-playing games
|
144
|
+
test_files:
|
145
|
+
- spec/die_spec.rb
|
146
|
+
- spec/fixnum_spec.rb
|
147
|
+
- spec/pool_spec.rb
|
148
|
+
- spec/spec_helper.rb
|