ruby-player 0.4.1 → 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -14,6 +14,7 @@
14
14
 
15
15
  module Player
16
16
  # The position2d proxy provides an interface to a mobile robot base
17
+ # TODO: Implement PLAYER_POSITION2D_CMD_POS command
17
18
  #
18
19
  # @example
19
20
  # # get proxy object
@@ -43,6 +44,54 @@ module Player
43
44
  @geom = {px: 0.0, py: 0.0, pz: 0.0, proll: 0.0, ppitch: 0.0, pyaw: 0.0, sw: 0.0, sl: 0.0, sh: 0.0}
44
45
  end
45
46
 
47
+ # X position [m]
48
+ # @return [Float]
49
+ def px
50
+ state[:px]
51
+ end
52
+
53
+ # Y position [m]
54
+ # @return [Float]
55
+ def py
56
+ state[:py]
57
+ end
58
+
59
+ # Yaw [rad]
60
+ # @return [Float]
61
+ def pa
62
+ state[:pa]
63
+ end
64
+
65
+ # X speed [m/s]
66
+ # @return [Float]
67
+ def vx
68
+ state[:vx]
69
+ end
70
+
71
+ # Y speed [m/s]
72
+ # @return [Float]
73
+ def vy
74
+ state[:vy]
75
+ end
76
+
77
+ # Yaw speed [rad/s]
78
+ # @return [Float]
79
+ def va
80
+ state[:va]
81
+ end
82
+
83
+ # State of motor
84
+ # @return [Boolean] true - on
85
+ def power?
86
+ state[:stall] != 0
87
+ end
88
+
89
+ # @deprecated Use {#power?}
90
+ def power
91
+ warn "Method `power` is deprecated. Pleas use `power?`"
92
+ power?
93
+ end
94
+
46
95
  # @deprecated Use {#state}
47
96
  def position
48
97
  warn "Method `position` is deprecated. Pleas use `data` for access to position"
@@ -50,14 +99,14 @@ module Player
50
99
  end
51
100
 
52
101
  # Query robot geometry
53
- # @return self
102
+ # @return [Position2d] self
54
103
  def query_geom
55
104
  send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_GET_GEOM)
56
105
  self
57
106
  end
58
107
 
59
108
  # Turn on motor
60
- # @return self
109
+ # @return [Position2d] self
61
110
  def power_on!
62
111
  send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_MOTOR_POWER, [1].pack("N"))
63
112
  self
@@ -70,6 +119,7 @@ module Player
70
119
  end
71
120
 
72
121
  # Turn off motor
122
+ # @return [Position2d] self
73
123
  def power_off!
74
124
  send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_MOTOR_POWER, [0].pack("N"))
75
125
  self
@@ -81,21 +131,25 @@ module Player
81
131
  power_off!
82
132
  end
83
133
 
134
+ # @return [Position2d] self
84
135
  def direct_speed_control!
85
136
  send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_VELOCITY_MODE, [0].pack("N"))
86
137
  self
87
138
  end
88
139
 
140
+ # @return [Position2d] self
89
141
  def separate_speed_control!
90
142
  send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_VELOCITY_MODE, [1].pack("N"))
91
143
  self
92
144
  end
93
145
 
146
+ # @return [Position2d] self
94
147
  def position_control!
95
148
  send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_POSITION_MODE, [0].pack("N"))
96
149
  self
97
150
  end
98
151
 
152
+ # @return [Position2d] self
99
153
  def speed_control!
100
154
  send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_POSITION_MODE, [1].pack("N"))
101
155
  self
@@ -106,7 +160,7 @@ module Player
106
160
  # @option odom :px x position (m)
107
161
  # @option odom :py y position (m)
108
162
  # @option odom :pa angle (rad).
109
- # @return self
163
+ # @return [Position2d] self
110
164
  def set_odometry(odom)
111
165
  data = [
112
166
  odom[:px].to_f,
@@ -123,7 +177,7 @@ module Player
123
177
  # @option params :kp P
124
178
  # @option params :ki I
125
179
  # @option params :kd D
126
- # @return self
180
+ # @return [Position2d] self
127
181
  def set_speed_pid(params={})
128
182
  data = [
129
183
  params[:kp].to_f,
@@ -139,7 +193,7 @@ module Player
139
193
  # @option params :kp P
140
194
  # @option params :ki I
141
195
  # @option params :kd D
142
- # @return self
196
+ # @return [Position2d] self
143
197
  def set_position_pid(params={})
144
198
  data = [
145
199
  params[:kp].to_f,
@@ -154,7 +208,7 @@ module Player
154
208
  # @param [Hash] params profile prarams
155
209
  # @option params :spped max speed (m/s)
156
210
  # @option params :acc max acceleration (m/s^2)
157
- # @return self
211
+ # @return [Position2d] self
158
212
  def set_speed_profile(params={})
159
213
  data = [
160
214
  params[:speed].to_f,
@@ -166,7 +220,7 @@ module Player
166
220
  end
167
221
 
168
222
  # Reset odometry to zero
169
- # @return self
223
+ # @return [Position2d] self
170
224
  def reset_odometry
171
225
  send_message(PLAYER_MSGTYPE_REQ, PLAYER_POSITION2D_REQ_RESET_ODOM)
172
226
  self
@@ -178,7 +232,7 @@ module Player
178
232
  # @option speeds :vy sideways speed (m/s); this field is used by omni-drive robots only.
179
233
  # @option speeds :va rotational speed (rad/s).
180
234
  # @option speeds :stall state of motor
181
- # @return self
235
+ # @return [Position2d] self
182
236
  def set_speed(speeds)
183
237
  data = [
184
238
  speeds[:vx] || @state[:vx],
@@ -194,6 +248,7 @@ module Player
194
248
  # @param [Hash] speeds
195
249
  # @option speeds :vx forward speed (m/s)
196
250
  # @option speeds :a turning angle (rad).
251
+ # @return [Position2d] self
197
252
  def set_car(speeds)
198
253
  data = [
199
254
  speeds[:vx] || @state[:vx],
@@ -208,6 +263,7 @@ module Player
208
263
  # @param [Hash] speeds
209
264
  # @option speeds :vx forward speed (m/s)
210
265
  # @option speeds :a absolutle angle (rad).
266
+ # @return [Position2d] self
211
267
  def set_speed_head(speeds)
212
268
  data = [
213
269
  speeds[:vx] || @state[:vx],
@@ -217,15 +273,8 @@ module Player
217
273
  self
218
274
  end
219
275
 
220
-
221
- # State of motor
222
- # @return [Boolean] true - on
223
- def power
224
- @state[:stall] == 1
225
- end
226
-
227
276
  # Stop robot set speed to 0
228
- # @return self
277
+ # @return [Position2d] self
229
278
  def stop!
230
279
  set_speed(vx: 0, vy: 0, va: 0)
231
280
  end
@@ -260,12 +309,12 @@ module Player
260
309
  private
261
310
  def read_state(msg)
262
311
  fill_hash!(@state, msg.unpack("GGGGGGN"))
263
- debug "Get state: " + hash_to_sft(@state)
312
+ debug "Got state: " + hash_to_sft(@state)
264
313
  end
265
314
 
266
315
  def read_geom(msg)
267
316
  fill_hash!(@geom, msg.unpack("G*"))
268
- debug "Get geom: " + hash_to_sft(@geom)
317
+ debug "Got geom: " + hash_to_sft(@geom)
269
318
  end
270
319
  end
271
320
  end
@@ -37,17 +37,47 @@ module Player
37
37
  super
38
38
  @state = { valid: 0, volts: 0.0, percent: 0.0, joules: 0.0, watts: 0.0, charging: 0 }
39
39
  end
40
+
41
+ # Battery voltage [V].
42
+ # @see #state
43
+ # @retrun [Float]
44
+ def volts
45
+ state[:volts]
46
+ end
47
+
48
+ # Percent of full charge [%].
49
+ # @see #state
50
+ # @retrun [Float]
51
+ def percent
52
+ state[:percent]
53
+ end
40
54
 
55
+ # Energy stored [J].
56
+ # @see #state
57
+ # @retrun [Float]
58
+ def joules
59
+ state[:joules]
60
+ end
61
+
62
+ # Estimated current energy consumption (negative values) or aquisition (positive values) [W].
63
+ # @see #state
64
+ # @retrun [Float]
65
+ def watts
66
+ state[:watts]
67
+ end
68
+
41
69
  # Request to change the charging policy
42
70
  # @param [Hash] policy
43
71
  # @option policy [Boolean] :enable_input boolean controlling recharging
44
72
  # @option policy [Boolean] :enable_output bolean controlling whether others can recharge from this device
73
+ # @return [Power] self
45
74
  def set_charging_policy(policy={})
46
75
  data = [
47
76
  policy[:enable_input] ? 1 : 0,
48
77
  policy[:enable_output] ? 1 : 0
49
78
  ]
50
79
  send_message(PLAYER_MSGTYPE_REQ, PLAYER_POWER_REQ_SET_CHARGING_POLICY, data.pack("NN"))
80
+ self
51
81
  end
52
82
 
53
83
  # Check volts valid
@@ -96,7 +126,7 @@ module Player
96
126
  private
97
127
  def read_state(msg)
98
128
  fill_hash!(@state, msg.unpack("NggggN"))
99
- debug "Get state: " + hash_to_sft(@state)
129
+ debug "Got state: " + hash_to_sft(@state)
100
130
  end
101
131
  end
102
132
  end
@@ -41,25 +41,25 @@ module Player
41
41
 
42
42
  # @deprecated use `ranger.collect { |r| r.range }
43
43
  def rangers
44
- warn "Method `rangers` is deprecated. Pleas use `ranger.collect { |r| r.state[:range] }`"
44
+ warn "Method `rangers` is deprecated. Pleas use `ranger.collect { |r| r.range }`"
45
45
  @sensors.collect { |s| s.range }
46
46
  end
47
47
 
48
48
  # @deprecated use `ranger.collect { |r| r.intensity }
49
49
  def intensities
50
- warn "Method `intensities` is deprecated. Pleas use `ranger.collect { |r| r.state[:intensity] }`"
50
+ warn "Method `intensities` is deprecated. Pleas use `ranger.collect { |r| r.intensity }`"
51
51
  @sensors.collect { |s| s.intensity }
52
52
  end
53
53
 
54
54
  # Query ranger geometry
55
- # @return self
55
+ # @return [Ranger] self
56
56
  def query_geom
57
57
  send_message(PLAYER_MSGTYPE_REQ, PLAYER_RANGER_REQ_GET_GEOM)
58
58
  self
59
59
  end
60
60
 
61
61
  # Turn on ranger
62
- # @return self
62
+ # @return [Ranger] self
63
63
  def power_on!
64
64
  send_message(PLAYER_MSGTYPE_REQ, PLAYER_RANGER_REQ_POWER, [1].pack("N"))
65
65
  self
@@ -72,6 +72,7 @@ module Player
72
72
  end
73
73
 
74
74
  # Turn off ranger
75
+ # @return [Ranger] self
75
76
  def power_off!
76
77
  send_message(PLAYER_MSGTYPE_REQ, PLAYER_RANGER_REQ_POWER, [0].pack("N"))
77
78
  self
@@ -83,17 +84,20 @@ module Player
83
84
  power_off!
84
85
  end
85
86
 
87
+ # @return [Ranger] self
86
88
  def intensity_enable!
87
89
  send_message(PLAYER_MSGTYPE_REQ, PLAYER_RANGER_REQ_INTNS, [1].pack("N"))
88
90
  self
89
91
  end
90
92
 
93
+ # @return [Ranger] self
91
94
  def intensity_disable!
92
95
  send_message(PLAYER_MSGTYPE_REQ, PLAYER_RANGER_REQ_INTNS, [0].pack("N"))
93
96
  self
94
97
  end
95
98
 
96
99
  # Query ranger configuration
100
+ # @return [Ranger] self
97
101
  def query_config
98
102
  send_message(PLAYER_MSGTYPE_REQ, PLAYER_RANGER_REQ_GET_CONFIG)
99
103
  self
@@ -108,18 +112,11 @@ module Player
108
112
  # @option config :max_range minimum range [m]
109
113
  # @option config :range_res range resolution [m]
110
114
  # @option config :frequency scanning frequency [Hz]
111
- def set_config(config)
112
- data = [
113
- config[:min_angle].to_f || @ranger[:min_angle],
114
- config[:max_angle].to_f || @ranger[:max_angle],
115
- config[:angular_res].to_f || @ranger[:angular_res],
116
- config[:min_range].to_f || @ranger[:min_range],
117
- config[:max_range].to_f || @ranger[:max_range],
118
- config[:range_res].to_f || @ranger[:range_res],
119
- config[:frequecy].to_f || @ranger[:frequecy]
120
- ]
121
-
115
+ # @return [Ranger] self
116
+ def set_config(config={})
117
+ data = to_a_by_default(config, @config)
122
118
  send_message(PLAYER_MSGTYPE_REQ, PLAYER_RANGER_REQ_SET_CONFIG, data.pack("G*"))
119
+ self
123
120
  end
124
121
 
125
122
  def fill(hdr, msg)
@@ -130,14 +127,14 @@ module Player
130
127
  self[i].state[:range] = r
131
128
  end
132
129
 
133
- debug "Get rangers #{@sensors.collect { |s| s.state[:range] }}"
130
+ debug "Got rangers #{@sensors.collect { |s| s.state[:range] }}"
134
131
  when PLAYER_RANGER_DATA_INTNS
135
132
  data = msg.unpack("NNG*")
136
133
  data[2..-1].each_with_index do |ints, i|
137
134
  self[i].state[:intensity] = ints
138
135
  end
139
136
 
140
- debug "Get intensities #{@sensors.collect { |s| s.state[:intensity]}}"
137
+ debug "Got intensities #{@sensors.collect { |s| s.state[:intensity]}}"
141
138
  when PLAYER_RANGER_DATA_GEOM
142
139
  read_geom(msg)
143
140
  else
@@ -158,6 +155,9 @@ module Player
158
155
  end
159
156
  end
160
157
 
158
+ # Get sensor
159
+ # @param [Integer] index
160
+ # @retrun [Sensor] sensor
161
161
  def [](index)
162
162
  @sensors[index] ||= Sensor.new(index, self)
163
163
  end
@@ -174,7 +174,7 @@ module Player
174
174
  def read_geom(msg)
175
175
  data = msg[0,72].unpack("G*")
176
176
  fill_hash!(@geom, data)
177
- debug "Get geom: " + hash_to_sft(@geom)
177
+ debug "Got geom: " + hash_to_sft(@geom)
178
178
 
179
179
 
180
180
  p_count = msg[72,8].unpack("NN")
@@ -188,13 +188,13 @@ module Player
188
188
  p_count.times do |i|
189
189
  [:px, :py, :pz, :proll, :ppitch, :pyaw]
190
190
  .each_with_index { |k,j| self[i].geom[k] = poses[6*i + j] }
191
- debug("Get poses for ##{i} sensor: px=%.2f, py=%.2f, pz=%.2f, proll=%.2f, ppitch=%.2f, pyaw=%.2f" % @sensors[i].geom.values[0,6])
191
+ debug("Got poses for ##{i} sensor: px=%.2f, py=%.2f, pz=%.2f, proll=%.2f, ppitch=%.2f, pyaw=%.2f" % @sensors[i].geom.values[0,6])
192
192
  end
193
193
 
194
194
  s_count.times do |i|
195
195
  [:sw, :sl, :sh]
196
196
  .each_with_index { |k,j| self[i].geom[k] = sizes[3*i + j] }
197
- debug("Get sizes for ##{i} sensor: sw=%.2f, sl=%.2f, sh=%.2f" % @sensors[i].geom.values[6,3])
197
+ debug("Got sizes for ##{i} sensor: sw=%.2f, sl=%.2f, sh=%.2f" % @sensors[i].geom.values[6,3])
198
198
  end
199
199
 
200
200
  end
@@ -13,5 +13,5 @@
13
13
  # GNU General Public License for more details.
14
14
 
15
15
  module Player
16
- VERSION = "0.4.1"
16
+ VERSION = "0.5.0"
17
17
  end
data/lib/ruby-player.rb CHANGED
@@ -25,6 +25,8 @@ require "ruby-player/client"
25
25
  #interfaces
26
26
  require "ruby-player/actuator"
27
27
  require "ruby-player/actarray"
28
+ require "ruby-player/blob"
29
+ require "ruby-player/blob_finder"
28
30
  require "ruby-player/gripper"
29
31
  require "ruby-player/position2d"
30
32
  require "ruby-player/power"
@@ -18,6 +18,14 @@ describe Player::ActArray do
18
18
  @actarray.geom.should eql(px: 0.0, py: 0.0, pz: 0.0, proll: 0.0, ppitch: 0.0, pyaw: 0.0)
19
19
  end
20
20
 
21
+ it 'should have :power? method' do
22
+ @actarray.should_receive(:state).and_return(motor_state: 0)
23
+ @actarray.power?.should be_false
24
+
25
+ @actarray.should_receive(:state).and_return(motor_state: 1)
26
+ @actarray.power?.should be_true
27
+ end
28
+
21
29
  it 'should set power state for all actuators' do
22
30
  should_send_message(PLAYER_MSGTYPE_REQ, PLAYER_ACTARRAY_REQ_POWER, [0].pack("N"))
23
31
  @actarray.power_off!
@@ -28,6 +28,26 @@ describe Player::Actuator do
28
28
  )
29
29
  end
30
30
 
31
+ it 'should have #position attr' do
32
+ @act.should_receive(:state).and_return(position: 1.2)
33
+ @act.position.should eql(1.2)
34
+ end
35
+
36
+ it 'should have #speed attr' do
37
+ @act.should_receive(:state).and_return(speed: 1.8)
38
+ @act.speed.should eql(1.8)
39
+ end
40
+
41
+ it 'should have #acceleration attr' do
42
+ @act.should_receive(:state).and_return(acceleration: 0.2)
43
+ @act.acceleration.should eql(0.2)
44
+ end
45
+
46
+ it 'should have #current attr' do
47
+ @act.should_receive(:state).and_return(current: 3.2)
48
+ @act.current.should eql(3.2)
49
+ end
50
+
31
51
  it 'should set speed config ' do
32
52
  should_send_message(PLAYER_MSGTYPE_REQ, PLAYER_ACTARRAY_REQ_SPEED, [0, 0.2].pack("Ng"))
33
53
  @act.set_speed_config(0.2)
@@ -0,0 +1,96 @@
1
+ require File.dirname(__FILE__) + "/spec_helper"
2
+
3
+ include Player
4
+ describe Player::BlobFinder do
5
+ before do
6
+ client = mock_client
7
+
8
+ @bf = Player::BlobFinder.new(
9
+ Player::DevAddr.new(host: 0, robot:0, interface: PLAYER_BLOBFINDER_CODE, index: 0),
10
+ client
11
+ )
12
+
13
+ mock_sending_message(@bf)
14
+ end
15
+
16
+ it 'should have default state' do
17
+ @bf.state.should eql(width: 0, height: 0, blobs: [])
18
+ @bf.color.should eql(channel: 0, rmin: 0, rmax: 0, gmin: 0, gmax: 0, bmin: 0, bmax: 0)
19
+ @bf.imager_params.should eql(brightness: 0, contrast: 0, colormode: 0, autogain: 0)
20
+ end
21
+
22
+ it 'should query color' do
23
+ should_send_message(PLAYER_MSGTYPE_REQ, PLAYER_BLOBFINDER_REQ_GET_COLOR)
24
+ @bf.query_color
25
+ end
26
+
27
+ it 'should set color' do
28
+ color = [0, 1, 251, 2, 252, 3, 253]
29
+ should_send_message(PLAYER_MSGTYPE_REQ, PLAYER_BLOBFINDER_REQ_SET_COLOR, color.pack("N*"))
30
+ @bf.set_color(channel: 0, rmin: 1, rmax: 251, gmin: 2, gmax: 252, bmin: 3, bmax: 253)
31
+ end
32
+
33
+ it 'should set imager params' do
34
+ params = [100, 200, 3, 1]
35
+ should_send_message(PLAYER_MSGTYPE_REQ, PLAYER_BLOBFINDER_REQ_SET_IMAGER_PARAMS, params.pack("N*"))
36
+ @bf.set_imager_params(brightness: 100, contrast: 200, colormode: 3, autogain: 1)
37
+ end
38
+
39
+ it 'should have #width attr' do
40
+ @bf.should_receive(:state).and_return(width: 23)
41
+ @bf.width.should eql(23)
42
+ end
43
+
44
+ it 'should have #height attr' do
45
+ @bf.should_receive(:state).and_return(height: 40)
46
+ @bf.height.should eql(40)
47
+ end
48
+
49
+ it 'should have #blobs attr' do
50
+ @bf.should_receive(:state).and_return(blobs: [nil])
51
+ @bf.blobs.should eql([nil])
52
+ end
53
+
54
+ it 'should fill blobs data' do
55
+ blobs = [10, 20, 2, 0,
56
+ 0, 222, 20, 5, 4, 10, 10, 20, 20, 0.5,
57
+ 1, 111, 40, 5, 8, 20, 30, 40, 50, 2.5]
58
+ msg = blobs.pack("NNNNN9gN9g")
59
+
60
+ @bf.fill(
61
+ Player::Header.from_a([0,0,PLAYER_BLOBFINDER_CODE, 0,
62
+ PLAYER_MSGTYPE_DATA, PLAYER_BLOBFINDER_DATA_BLOBS, 0.0, 0, msg.bytesize]),
63
+ msg
64
+ )
65
+ @bf.width.should eql(10)
66
+ @bf.height.should eql(20)
67
+ @bf[0].state.values.should eql(blobs[4,10])
68
+ @bf[1].state.values.should eql(blobs[14,10])
69
+ end
70
+
71
+ it 'should include Enumerable' do
72
+ @bf.state[:blobs] = Array.new(10) { |i| Blob.new(i, nil) }
73
+ @bf.map { |b| b.should be_kind_of(Blob) }
74
+ end
75
+
76
+ it 'should get color config by request' do
77
+ color = [1, 1, 2, 3, 4, 5, 6 ]
78
+
79
+ msg = color.pack("N*")
80
+ @bf.handle_response(
81
+ Player::Header.from_a([0,0,PLAYER_BLOBFINDER_CODE, 0, PLAYER_MSGTYPE_RESP_ACK, PLAYER_BLOBFINDER_REQ_GET_COLOR, 0.0, 0, msg.bytesize]),
82
+ msg
83
+ )
84
+
85
+ @bf.color.should eql(channel: 1, rmin: 1, rmax: 2, gmin: 3, gmax: 4, bmin: 5, bmax: 6)
86
+ end
87
+
88
+ it 'should not puts warn message for ACK subtypes 1,2' do
89
+ @bf.should_not_receive(:unexpected_message)
90
+ (1..2).each do |i|
91
+ @bf.handle_response(
92
+ Player::Header.from_a([0,0,PLAYER_BLOBFINDER_CODE,0, PLAYER_MSGTYPE_RESP_ACK, i, 0.0, 0, 0]),
93
+ "")
94
+ end
95
+ end
96
+ end
data/spec/blob_spec.rb ADDED
@@ -0,0 +1,63 @@
1
+ require File.dirname(__FILE__) + "/spec_helper"
2
+
3
+ describe Player::Blob do
4
+ before do
5
+ @blob = Player::Blob.new(nil, nil)
6
+ end
7
+
8
+ it 'should have default values' do
9
+ @blob.state.should eql(id: 0, color: 0, area: 0, x: 0, y: 0, left: 0, right: 0, top: 0, bottom: 0, range: 0.0)
10
+ end
11
+
12
+ it 'should have #id attr' do
13
+ @blob.should_receive(:state).and_return(id: 23)
14
+ @blob.id.should eql(23)
15
+ end
16
+
17
+ it 'should have #color attr' do
18
+ @blob.should_receive(:state).and_return(color: 0xfff)
19
+ @blob.color.should eql(0xfff)
20
+ end
21
+
22
+ it 'should have #area attr' do
23
+ @blob.should_receive(:state).and_return(area: 100)
24
+ @blob.area.should eql(100)
25
+ end
26
+
27
+ it 'should have #x attr' do
28
+ @blob.should_receive(:state).and_return(x: 10)
29
+ @blob.x.should eql(10)
30
+ end
31
+
32
+ it 'should have #y attr' do
33
+ @blob.should_receive(:state).and_return(y: 20)
34
+ @blob.y.should eql(20)
35
+ end
36
+
37
+ it 'should have #left attr' do
38
+ @blob.should_receive(:state).and_return(left: 15)
39
+ @blob.left.should eql(15)
40
+ end
41
+
42
+ it 'should have #right attr' do
43
+ @blob.should_receive(:state).and_return(right: 30)
44
+ @blob.right.should eql(30)
45
+ end
46
+
47
+ it 'should have #top attr' do
48
+ @blob.should_receive(:state).and_return(top: 45)
49
+ @blob.top.should eql(45)
50
+ end
51
+
52
+ it 'should have #bottom attr' do
53
+ @blob.should_receive(:state).and_return(bottom: 35)
54
+ @blob.bottom.should eql(35)
55
+ end
56
+
57
+ it 'should have #ranger attr' do
58
+ @blob.should_receive(:state).and_return(range: 0.2)
59
+ @blob.range.should eql(0.2)
60
+ end
61
+ end
62
+
63
+