tabletop 0.2.1 → 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.
@@ -1,50 +1,88 @@
1
1
  module Tabletop
2
2
 
3
3
  class NotEnoughTokensError < ArgumentError
4
+ def initialize(wanted, available)
5
+ w_t, a_t = "token", "token"
6
+
7
+ w_t << "s" if wanted > 1 or wanted == 0
8
+
9
+ a_t << "s" if available > 1 or available == 0
10
+
11
+ available = available > 0 ? available : "no"
12
+
13
+ super("tried to remove #{wanted} #{w_t} from a stack with #{available} #{a_t}")
14
+ end
15
+ end
16
+
17
+ class ExceedMaxTokensError < ArgumentError
4
18
  end
5
19
 
6
20
  class TokenStack
7
21
 
8
- # The number of tokens in the Stack
9
- attr_accessor :count
22
+ # The number of tokens in the stack, and the maximum number it can have
23
+ attr_accessor :count, :max
10
24
  include Comparable
11
25
 
12
- def initialize(num_tokens = 1)
26
+ def initialize(num_tokens = 1, hash={})
13
27
  @count = num_tokens
28
+ @max = hash[:max]
14
29
  end
15
30
 
16
31
  def <=>(operand)
17
32
  count <=> operand.to_int
18
33
  end
19
34
 
35
+ def count=(new_value)
36
+ raise_if_over_max(new_value)
37
+ @count = new_value
38
+ end
39
+
20
40
  def add(n = 1)
21
- raise ArgumentError unless n.instance_of?(Fixnum) and n > 0
41
+ raise ArgumentError unless n.respond_to?(:to_i)
42
+ raise ArgumentError if n < 0
43
+ n = n.to_i
44
+ raise_if_over_max(n + @count)
22
45
  @count += n
23
46
  end
24
47
 
48
+ def raise_if_over_max(value)
49
+ if @max
50
+ raise ExceedMaxTokensError if value > @max
51
+ end
52
+ end
53
+
25
54
  # Raises NotEnoughTokensError if there aren't enough tokens to remove
26
55
  def remove(n=1)
27
- raise ArgumentError unless n.instance_of?(Fixnum) and n > 0
56
+ raise ArgumentError unless n.respond_to?(:to_i)
57
+ n = n.to_i
58
+ raise ArgumentError if n < 0
28
59
  if n > @count
29
- n_t, c_t = "token", "token"
30
-
31
- n_t << "s" if n > 1 or n == 0
32
-
33
- c_t << "s" if @count > 1 or @count == 0
34
-
35
- c = @count > 0 ? @count : "no"
36
- errmsg = "tried to remove #{n} #{n_t} from a stack with #{c} #{c_t}"
37
- raise NotEnoughTokensError, errmsg
60
+ raise NotEnoughTokensError.new(n, @count)
38
61
  end
39
62
  @count -= n
40
63
  end
41
64
 
42
- # Removes N tokens from the receiver stack, then
43
- # adds them to the stack in opts[:to]
65
+ # Usage: stack_a.move(N, :to => stack_b)
66
+ # Removes N tokens from stack_a, and adds
67
+ # the same number to stack_b
44
68
  def move(n, opts)
45
- raise(ArgumentError, "target is #{opts[:to].class}, not TokenStack") unless opts[:to].instance_of?(TokenStack)
46
- remove(n)
47
- opts[:to].add(n)
69
+ begin
70
+ opts[:to].add(n)
71
+ rescue NoMethodError
72
+ raise ArgumentError
73
+ end
74
+
75
+ begin
76
+ remove(n)
77
+ rescue NotEnoughTokensError
78
+ opts[:to].remove(n)
79
+ raise
80
+ end
81
+ end
82
+
83
+ def refresh
84
+ raise NoMethodError if @max.nil?
85
+ @count = @max
48
86
  end
49
87
  end
50
88
  end
@@ -1,3 +1,3 @@
1
1
  module Tabletop
2
- VERSION = "0.2.1"
2
+ VERSION = "0.3.0"
3
3
  end
data/lib/tabletop.rb CHANGED
@@ -3,3 +3,4 @@ require_relative 'tabletop/randomizers'
3
3
  require_relative 'tabletop/pool'
4
4
  require_relative 'tabletop/roll'
5
5
  require_relative 'tabletop/token'
6
+ require_relative 'tabletop/condition'
@@ -0,0 +1,15 @@
1
+ require 'spec_helper'
2
+
3
+ module Tabletop
4
+ describe Condition do
5
+ describe "#met_by?" do
6
+ it "it evaluates the block passed on initialization" do
7
+ c = Condition.new do |p|
8
+ p.sum > 7
9
+ end
10
+ c.met_by?(Pool.new("2/6 4/10")).should be_false
11
+ c.met_by?(Pool.new("4/8 4/12")).should be_true
12
+ end
13
+ end
14
+ end
15
+ end
data/spec/fixnum_spec.rb CHANGED
@@ -9,6 +9,10 @@ module Tabletop
9
9
  10.d100.should be_instance_of(Pool)
10
10
  end
11
11
 
12
+ it "raises an exception for invalid method names" do
13
+ expect {10.dthing}.to raise_error(NoMethodError)
14
+ end
15
+
12
16
  it "shows up in respond_to?(:dN)" do
13
17
  1.should respond_to(:d50)
14
18
  10.should_not respond_to(:dthing)
data/spec/pool_spec.rb CHANGED
@@ -2,121 +2,165 @@ require 'spec_helper'
2
2
 
3
3
  module Tabletop
4
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")
5
+
6
+ let(:d6_set) { Pool.new("2/6 1/6 3/6 4/6 5/6 6/6") }
7
+
8
+ describe ".new" do
9
+ it "can accept a string of d-notation" do
10
+ p = Pool.new("2d10 d20")
11
+ p.length.should == 3
12
+ p[0].sides.should == 10
13
+ p[1].sides.should == 10
14
+ p[2].sides.should == 20
15
+ end
16
+ it "can accept an array of dice objects" do
17
+ # mostly used internally
18
+ p = Pool.new([Die.new(value: 1), Die.new(sides: 4)])
19
+ p.length.should == 2
20
+ p[0].sides.should == 6
21
+ p[0].value.should == 1
22
+ p[1].sides.should == 4
23
+ end
24
+ it "can accept a string describing a specific dice configuration" do
25
+ pool = Pool.new("1/4 2/6 3/8")
26
+ pool.length.should == 3
27
+ pool[0].value.should == 1
28
+ pool[0].sides.should == 4
29
+ pool[1].value.should == 2
30
+ pool[1].sides.should == 6
31
+ pool[2].value.should == 3
32
+ pool[2].sides.should == 8
33
+ end
11
34
  end
12
35
 
13
36
  describe "#dice" do
14
37
  it "should return an array of dice notation" do
15
- @mixed.dice.should == ["2d10","d20"]
16
- @d6.dice.should == ["d6"]
17
- @d17s.dice.should == ["5d17"]
18
- @fudge.dice.should == ["3dF"]
19
38
  Pool.new("d20 2dF 2d10").dice.should == ["2d10","d20", "2dF"]
20
39
  end
21
40
  end
22
41
 
23
42
  describe "[]" do
24
- it "should access Die objects" do
25
- @d6[0].should be_instance_of(Die)
26
- @fudge[0].should be_instance_of(FudgeDie)
43
+ it "should access the objects " do
44
+ d = Pool.new("1/4")[0]
45
+ d.value.should == 1
46
+ d.sides.should == 4
27
47
  end
28
48
  end
29
49
 
30
50
  describe "+" do
31
- it "should join Pools into new Pools" do
32
- (@mixed + @d17s).should be_instance_of(Pool)
33
- (@d6 + @fudge).should be_instance_of(Pool)
34
- end
35
-
36
- it "should persist die types" do
37
- (@d6 + @fudge)[1].should be_instance_of(FudgeDie)
38
- end
39
-
40
- it "should join pools without rolling them" do
41
- merge = @d6 + @d17s
42
- merge.values.should == [2, 5, 16, 1, 17, 9]
43
- merge.roll
44
- merge.values.should == [4, 17, 5, 16, 12, 12]
45
- end
46
-
47
- it "creates genuinely new pools" do
48
- merge = @d6 + @d17s
49
- merge.roll
50
- @d6.values.should == [2]
51
- @d17s.values.should == [5, 16, 1, 17, 9]
51
+ before(:each) do
52
+ @a = 5.d6
52
53
  end
53
-
54
- it "should alter #dice accordingly" do
55
- @d6 = Pool.new("d6")
56
- @d17s = Pool.new("5d17")
57
- @mixed = Pool.new("2d10 d20")
58
- (@d6 + @d17s).dice.should == ["d6", "5d17"]
59
- (@d17s + @d6).dice.should == ["d6", "5d17"]
60
- (@d17s + @mixed).dice.should == ["2d10","5d17","d20"]
61
- (@mixed + @fudge).dice.should == ["2d10", "d20", "3dF"]
54
+ context "adding a number" do
55
+ it "should return the pool's sum plus the number" do
56
+ (@a + 5).should == @a.sum + 5
57
+ end
62
58
  end
63
-
64
- it "should understand adding a number as looking for a sum result" do
65
- (@d17s + 5).should == 53
66
- (@mixed + @d6 + 10).should == 34
67
- (@fudge + 3).should == 2
59
+ context "adding a randomizer" do
60
+ it "adds to the pool" do
61
+ mixed = @a + Die.new
62
+ mixed.length.should == 6
63
+ end
64
+ it "preserves class" do
65
+ (@a + FudgeDie.new(value:-1))[-1].value.should == -1
66
+ (@a + Coin.new)[-1].should respond_to :flip
67
+ end
68
68
  end
69
-
70
- it "should add literal dice arrays as if they were pools" do
71
- g = @d6 + [Die.new(6,3), Die.new(10, 4)]
72
- g.values.should == [2, 3, 4]
73
- g.dice.should == ["2d6", "d10"]
74
- g.roll
75
- @d6.values.should == [2]
69
+ context "adding another pool" do
70
+ before(:each) do
71
+ @b = 4.d4
72
+ @merge = @a+@b
73
+ end
74
+ it "should make a union of the pools" do
75
+ @merge.values.should == @a.values + @b.values
76
+ end
77
+ it "should make new die objects" do
78
+ @merge.roll
79
+ @merge.values.should_not == @a.values + @b.values
80
+ end
81
+ it "should persist die types" do
82
+ (Pool.new("d6")+Pool.new("dF"))[1].should be_instance_of(FudgeDie)
83
+ (Pool.new("d6")+Pool.new([Coin.new]))[1].should respond_to(:flip)
84
+ end
85
+ it "should alter #dice accordingly" do
86
+ (Pool.new("2d17 d6")+Pool.new("3d17")).dice.should == ["d6", "5d17"]
87
+ end
76
88
  end
77
-
78
- it "should reject adding anything else" do
79
- expect {@d6 + "foof"}.to raise_error(ArgumentError)
80
- expect {@d6 + [Die.new, Object.new]}.to raise_error(ArgumentError)
89
+ context "adding anything else" do
90
+ it "should raise an exception" do
91
+ expect {Pool.new("d6") + "foof"}.to raise_error(ArgumentError)
92
+ expect {Pool.new("d6") + [Die.new, Object.new]}.to raise_error(ArgumentError)
93
+ end
81
94
  end
82
95
  end
83
96
 
84
97
  describe "*" do
85
98
  it "should multiply by the sum of the pool" do
86
99
  (1..10).each do |v|
87
- p = Pool.new([Die.new(10, v)])
100
+ p = Pool.new("#{v}/10")
88
101
  (p * 5).should == (v * 5)
89
102
  (5 * p).should == (5 * v)
90
103
  end
91
104
  end
92
105
  end
106
+
107
+ describe "-" do
108
+ context "subtracting a number" do
109
+ it "should return the pool's sum minus the number" do
110
+ (d6_set - 1).should == 20
111
+ end
112
+ end
113
+ end
93
114
 
94
115
  describe "#values" do
95
- it "should be an array of random numbers" do
96
- @d6.values.should == [2]
97
- @d17s.values.should == [5, 16, 1, 17, 9]
98
- @mixed.values.should == [10, 1, 11]
116
+ it "should be an array of the values of the dice" do
117
+ d6_set.values.each_with_index do |v, i|
118
+ v.should == d6_set[i].value
119
+ end
99
120
  end
100
121
  end
101
122
 
102
123
  describe "#roll" do
103
124
  it "should return the Pool itself" do
104
- @d6.roll.length.should == @d6.length
105
- @d6.roll.should be_instance_of(Pool)
125
+ actual = d6_set.roll
126
+ d6_set.length.times do |i|
127
+ actual[i].value.should == d6_set[i].value
128
+ actual[i].sides.should == d6_set[i].sides
129
+ end
106
130
  end
107
131
 
108
- it "should store the new values" do
109
- @d6.roll
110
- @d6.values.should == [4]
111
- @d17s.roll
112
- @d17s.values.should == [17, 5, 16, 12, 12]
113
- @mixed.roll
114
- @mixed.values.should == [2, 9, 5]
132
+ it "calls roll on its contents" do
133
+ d = double("a die")
134
+ d.should_receive(:roll)
135
+ Pool.new([d]).roll
115
136
  end
116
137
  it "can roll only dice below a certain value"
117
138
  it "can roll only dice above a certain value"
118
139
  it "can roll only dice equal to a certain value"
119
140
  end
141
+
142
+ describe "#roll_if" do
143
+ before :each do
144
+ @d1 = double("a die")
145
+ @d2 = double("a die")
146
+ end
147
+ it "rolls dice when the block returns true" do
148
+ @d1.should_receive(:roll)
149
+ Pool.new([@d1]).roll_if {|die| true}
150
+ end
151
+ it "doesn't roll dice when the block returns false" do
152
+ @d1.should_not_receive(:roll)
153
+ Pool.new([@d1]).roll_if {|die| false}
154
+ end
155
+ it "rolls dice that satisfy the block condition" do
156
+ @d1.stub(:sides).and_return(3)
157
+ @d2.stub(:sides).and_return(4)
158
+
159
+ @d1.should_not_receive(:roll)
160
+ @d2.should_receive(:roll)
161
+ Pool.new([@d1, @d2]).roll_if {|die| die.sides > 3}
162
+ end
163
+ end
120
164
 
121
165
  describe "#sum" do
122
166
  it "should sum the dice values" do
@@ -136,94 +180,74 @@ module Tabletop
136
180
 
137
181
  describe "<=>" do
138
182
  it "should compare the sums of different pools" do
139
- @d17s.should >= @d6
140
- @d6.should < Pool.new([Die.new(4, 4)])
183
+ Pool.new("1/4 1/4").should == Pool.new("2/6")
184
+ Pool.new("10/10").should == Pool.new("10/50")
185
+ Pool.new("3/6").should < Pool.new("4/4")
141
186
  end
142
187
 
143
188
  it "should compare pools to numbers" do
144
- @d6.should < 10
145
- @d6.should == 2
146
- @d17s.should <= 49
189
+ Pool.new("4/8 5/10").should < 10
190
+ Pool.new("1/6 1/8").should == 2
191
+ Pool.new("49/50").should <= 49
147
192
  end
148
193
  end
149
194
 
150
195
  describe "#sets" do
151
- it "should list the sets, in order by height and width" do
152
- ore = Pool.new("10d10")
153
- ore.sets.should == ["2x9", "2x5", "2x4", "2x2", "1x7", "1x1"]
154
- ore.roll
155
- ore.sets.should == ["3x10", "2x7", "1x6", "1x5", "1x4", "1x3", "1x2"]
156
- ore.roll
157
- ore.sets.should == ["3x9", "2x8", "2x7", "1x10", "1x3", "1x1"]
196
+ it "should group dice in sets, by order of height, then width" do
197
+ Pool.new("9/10 1/10 5/10 4/10 9/10 5/10 7/10 4/10").sets.should == ["2x9", "2x5", "2x4", "1x7", "1x1"]
158
198
  end
159
199
  end
160
200
 
161
201
  describe "#highest" do
162
202
  it "should return a pool of the highest-value die" do
163
- @d6.highest.should be_instance_of(Pool)
164
- @d6.highest.values.should == [2]
165
- @d17s.highest.values.should == [17]
166
- @mixed.highest.values.should == [11]
203
+ d6_set.highest.values.should == [6]
167
204
  end
168
205
 
169
206
  it "should return as many items as are specified" do
170
- @d6.highest(5).values.should == [2]
171
- @d17s.highest(3).values.should == [17, 16, 9]
172
- @mixed.highest(2).values.should == [11, 10]
207
+ d6_set.highest(3).values.should == [4,5,6]
208
+ d6_set.highest(10).values.should == [2,1,3,4,5,6]
173
209
  end
174
210
  end
175
211
 
176
212
  describe "#lowest" do
177
213
  it "should return a pool of the lowest-value die." do
178
- @d6.lowest.values.should == [2]
179
- @d17s.lowest.should be_instance_of(Pool)
180
- @d17s.lowest.values.should == [1]
181
- @mixed.lowest.values.should == [1]
214
+ d6_set.lowest.values.should == [1]
182
215
  end
183
216
 
184
217
  it "should return as many items as are specified" do
185
- @d6.lowest(5).values.should == [2]
186
- @d17s.lowest(3).values.should == [1, 5, 9]
187
- @mixed.lowest(2).values.should == [1, 10]
218
+ d6_set.lowest(3).values.should == [2,1,3]
219
+ d6_set.lowest(10).values.should == [2,1,3,4,5,6]
188
220
  end
189
221
  end
190
222
 
191
223
  describe "#drop_highest" do
192
224
  it "should return a new pool missing the highest result" do
193
- p = @d17s.drop_highest
194
- p.values.should == [5, 16, 1, 9]
195
- @d17s.values.should == [5, 16, 1, 17, 9]
225
+ d6_set.drop_highest.values.should == [2,1,3,4,5]
196
226
  end
197
227
 
198
228
  it "should drop as many items as are specified and are possible" do
199
- p = @d17s.drop_highest(2)
200
- p.values.should == [5, 1, 9]
201
- p = @d6.drop_highest(10)
202
- p.values.should == []
229
+ d6_set.drop_highest(3).values.should == [2,1,3]
230
+ d6_set.drop_highest(10).values.should == []
203
231
  end
204
232
  end
205
233
 
206
234
  describe "#drop_lowest" do
207
235
  it "should return a pool missing the lowest result" do
208
- p = @d17s.drop_lowest
209
- p.values.should == [5, 16, 17, 9]
210
- @d17s.values.should == [5, 16, 1, 17, 9]
236
+ d6_set.drop_lowest.values.should == [2, 3, 4, 5, 6]
211
237
  end
212
238
 
213
- it "should drop as many items as are specified" do
214
- p = @d17s.drop_lowest(2)
215
- p.values.should == [16, 17, 9]
239
+ it "should drop as many items as are specified and are possible" do
240
+ d6_set.drop_lowest(2).values.should == [3,4,5,6]
241
+ d6_set.drop_lowest(10).values.should == []
216
242
  end
217
243
  end
218
244
 
219
245
  describe "#drop" do
220
246
  it "should drop any dice of the specified value" do
221
247
  ore = Pool.new("10d10")
222
- ore.values.should == [4, 1, 5, 7, 9, 2, 9, 5, 2, 4]
223
- at_least_two = ore.drop(1)
224
- at_least_two.values.should == [4, 5, 7, 9, 2, 9, 5, 2, 4]
225
- at_least_three = ore.drop([1,2])
226
- at_least_three.values.should == [4, 5, 7, 9, 9, 5, 4]
248
+ (10..1).each do |i|
249
+ ore.drop(i).should_not include(i)
250
+ end
227
251
  end
228
252
  end
229
253