ruby-player 0.4.1 → 0.5.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.
@@ -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
+