tabletop 0.0.1
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 +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
|