zyps 0.6.2 → 0.6.3

Sign up to get free protection for your applications and to get access to all the features.
data/bin/zyps CHANGED
@@ -20,10 +20,12 @@
20
20
 
21
21
  gems_loaded = false
22
22
  begin
23
+ require 'optparse'
23
24
  require 'zyps'
24
25
  require 'zyps/actions'
25
26
  require 'zyps/conditions'
26
27
  require 'zyps/environmental_factors'
28
+ require 'zyps/remote'
27
29
  require 'zyps/views/trails'
28
30
  rescue LoadError
29
31
  if gems_loaded == false
@@ -39,14 +41,36 @@ end
39
41
  include Zyps
40
42
 
41
43
 
44
+ DEFAULT_VIEW_WIDTH = 800
45
+ DEFAULT_VIEW_HEIGHT = 600
46
+ DEFAULT_MAX_SPEED = 200
47
+ DEFAULT_MAX_POPULATION = 100
48
+ DEFAULT_FPS = 60
49
+
50
+
42
51
  class Application
43
52
 
53
+ #Port to open service on.
54
+ attr_accessor :uri
55
+ #Maximum allowed number of objects.
56
+ attr_accessor :max_population
57
+ #View dimensions.
58
+ attr_accessor :view_width, :view_height
59
+
44
60
  #Create app window, game environment, and view.
45
- def initialize(width, height)
61
+ #Set up default values.
62
+ def initialize(
63
+ uri = nil,
64
+ view_width = DEFAULT_VIEW_WIDTH,
65
+ view_height = DEFAULT_VIEW_HEIGHT,
66
+ fps = DEFAULT_FPS,
67
+ max_population = DEFAULT_MAX_POPULATION,
68
+ max_speed = DEFAULT_MAX_SPEED,
69
+ enclosure = true
70
+ )
46
71
 
47
- @width, @height = width, height
48
- @birth_rate = 1
49
- @fps = 30.0
72
+ @uri, @view_width, @view_height, @fps, @max_population, @max_speed, @enclosure =
73
+ uri, view_width, view_height, fps, max_population, max_speed, enclosure
50
74
 
51
75
  #Create a window, and set GTK up to quit when it is closed.
52
76
  window = Gtk::Window.new
@@ -54,15 +78,16 @@ class Application
54
78
  window.signal_connect("delete_event") {false}
55
79
  window.signal_connect("destroy") {Gtk.main_quit}
56
80
 
81
+ #Create environment.
82
+ @environment = Environment.new
83
+
57
84
  #Set up controls.
85
+ #Also initializes @view.
58
86
  window.add(create_controls)
59
87
 
60
88
  #Show all widgets.
61
89
  window.show_all
62
-
63
- #Create environment.
64
- @environment = Environment.new
65
-
90
+
66
91
  #Point view at environment.
67
92
  @environment.add_observer(@view)
68
93
 
@@ -71,20 +96,21 @@ class Application
71
96
 
72
97
  def main
73
98
 
74
-
75
- #Keep all objects within a boundary.
76
- enclosure = Enclosure.new
77
- enclosure.left = 0
78
- enclosure.bottom = 0
79
- enclosure.top = @height
80
- enclosure.right = @width
81
- @environment.environmental_factors << enclosure
99
+ #Keep objects on screen.
100
+ if @enclosure
101
+ enclosure = Enclosure.new()
102
+ enclosure.left = 0
103
+ enclosure.bottom = 0
104
+ enclosure.top = @view_height
105
+ enclosure.right = @view_width
106
+ @environment.environmental_factors << enclosure
107
+ end
82
108
 
83
109
  #Keep all objects under a certain speed.
84
- @environment.environmental_factors << SpeedLimit.new(100)
110
+ @environment.environmental_factors << SpeedLimit.new(@max_speed) if @max_speed
85
111
 
86
112
  #Limit population.
87
- @environment.environmental_factors << PopulationLimit.new(@environment, 25)
113
+ @environment.environmental_factors << PopulationLimit.new(@environment, @max_population) if @max_population
88
114
 
89
115
  #Set up a creature generator.
90
116
  @generator = CreatureGenerator.new(@environment)
@@ -98,7 +124,7 @@ class Application
98
124
  time_per_frame = 1.0 / @fps
99
125
 
100
126
  loop do
101
-
127
+
102
128
  @environment.interact
103
129
 
104
130
  #Determine how much time is left in this frame.
@@ -119,6 +145,16 @@ class Application
119
145
 
120
146
  end
121
147
 
148
+
149
+ #Start a network service.
150
+ if @uri
151
+ server = EnvironmentServer.new(@environment, @uri)
152
+ server.start
153
+ #Disable file system access.
154
+ $SAFE = 2
155
+ end
156
+
157
+
122
158
  #Activate the GUI.
123
159
  Gtk.main
124
160
 
@@ -126,13 +162,13 @@ class Application
126
162
 
127
163
 
128
164
  #Create a view and controls.
129
- def create_controls(width = @width, height = @height, homogeneous = false, spacing = 0, expand = false, fill = false, padding = 0)
165
+ def create_controls(view_width = @view_width, view_height = @view_height, homogeneous = false, spacing = 0, expand = false, fill = false, padding = 0)
130
166
 
131
167
  #Create a container for the view and controls.
132
168
  interface = Gtk::HBox.new(homogeneous, spacing)
133
169
 
134
170
  #Add view to interface.
135
- @view = TrailsView.new(width, height)
171
+ @view = TrailsView.new(view_width, view_height)
136
172
  interface.pack_start(@view.canvas, expand, fill, padding)
137
173
 
138
174
  #When mouse button pressed, record location for use in release event handler.
@@ -156,6 +192,7 @@ class Application
156
192
  :flee => @flee_flag.active?,
157
193
  :push => @push_flag.active?,
158
194
  :pull => @pull_flag.active?,
195
+ :breed => @breed_flag.active?,
159
196
  :eat => @eat_flag.active?
160
197
  )
161
198
  end
@@ -178,18 +215,88 @@ class Application
178
215
  action_controls.pack_start(@push_flag, expand, fill, padding)
179
216
  @pull_flag = Gtk::CheckButton.new("Pull")
180
217
  action_controls.pack_start(@pull_flag, expand, fill, padding)
218
+ @breed_flag = Gtk::CheckButton.new("Breed")
219
+ action_controls.pack_start(@breed_flag, expand, fill, padding)
181
220
  @eat_flag = Gtk::CheckButton.new("Eat")
182
221
  action_controls.pack_start(@eat_flag, expand, fill, padding)
183
222
  #Add the action controls to the panel.
184
223
  control_panel.pack_start(action_controls, expand, fill, padding)
185
224
 
225
+ #Create a group for environment controls.
226
+ environment_controls = Gtk::VBox.new(homogeneous, spacing)
227
+ environment_controls.pack_start(Gtk::Label.new("Environment"), expand, fill, padding)
228
+ @clear_button = Gtk::Button.new("Clear")
229
+ @clear_button.signal_connect("clicked") {
230
+ @environment.objects = []
231
+ }
232
+ environment_controls.pack_start(@clear_button, expand, fill, padding)
233
+ #Add the environment controls to the panel.
234
+ control_panel.pack_start(environment_controls, expand, fill, padding)
235
+
186
236
  #Add the control panel to the interface.
187
237
  interface.pack_start(control_panel, expand, fill, padding)
188
238
 
189
239
  interface
190
240
 
191
241
  end
242
+
243
+
244
+ #Set attributes according to command-line arguments.
245
+ def process_options(arguments)
192
246
 
247
+ #Set up option parser.
248
+ options = OptionParser.new
249
+
250
+ #Define valid options.
251
+ options.on("-h", "--help", TrueClass, "Display program help.") {
252
+ puts options.help
253
+ exit
254
+ }
255
+ options.on(
256
+ "-m",
257
+ "--max-population [number]",
258
+ Integer,
259
+ "The maximum number of allowed game objects. #{DEFAULT_MAX_POPULATION} by default."
260
+ ) {|value| @max_population = value}
261
+ options.on(
262
+ "-s",
263
+ "--max-speed [number]",
264
+ Integer,
265
+ "The fastest an object can go. #{DEFAULT_MAX_SPEED ? DEFAULT_MAX_SPEED : 'No limit'} by default."
266
+ ) {|value| @max_speed = value}
267
+ options.on(
268
+ "-n",
269
+ "--no-enclosure",
270
+ "Disables the barrier that normally keeps objects on the screen."
271
+ ) {|value| @enclosure = false}
272
+ options.on(
273
+ "-u",
274
+ "--uri [uri]",
275
+ String,
276
+ "dRuby URI to run the server on. If not defined, one will be selected and printed to STDOUT."
277
+ ) {|value| @uri = value}
278
+ options.on(
279
+ "-f",
280
+ "--fps [frames]",
281
+ Integer,
282
+ "Number of frames to draw per second. #{DEFAULT_FPS} by default."
283
+ ) {|value| @fps = value}
284
+ options.on(
285
+ "--view-width [pixels]",
286
+ Integer,
287
+ "Window width. #{DEFAULT_VIEW_WIDTH} by default."
288
+ ) {|value| @view_width = value}
289
+ options.on(
290
+ "--view-height [pixels]",
291
+ Integer,
292
+ "Window height. #{DEFAULT_VIEW_HEIGHT} by default."
293
+ ) {|value| @view_height = value}
294
+
295
+ #Parse the options, printing usage if parsing fails.
296
+ options.parse(arguments) rescue puts "#{$!}\nType '#{$0} --help' for valid options."
297
+
298
+ end
299
+
193
300
 
194
301
  end
195
302
 
@@ -224,8 +331,9 @@ class CreatureGenerator
224
331
  @turn_rate = 90
225
332
  @approach_turn_rate = 720
226
333
  @flee_turn_rate = @approach_turn_rate
227
- @pull_strength = 1000
334
+ @pull_strength = 100
228
335
  @push_strength = @pull_strength
336
+ @breed_rate = 10
229
337
 
230
338
  end
231
339
 
@@ -233,7 +341,7 @@ class CreatureGenerator
233
341
  #Create a creature and add it to the environment.
234
342
  def create_creature(options = {})
235
343
 
236
- {
344
+ options = {
237
345
  :x => 0,
238
346
  :y => 0,
239
347
  :speed => 1,
@@ -245,77 +353,96 @@ class CreatureGenerator
245
353
  :flee => false,
246
354
  :push => false,
247
355
  :pull => false,
356
+ :breed => false,
248
357
  :eat => false,
249
- }.merge!(options)
358
+ }.merge(options)
250
359
 
251
- #Set up the creature's vector.
252
- vector = Vector.new(options[:speed], options[:pitch])
360
+ #Create a creature.
361
+ creature = Creature.new(
362
+ '',
363
+ Location.new(options[:x], options[:y]),
364
+ Color.new,
365
+ Vector.new(options[:speed], options[:pitch]),
366
+ 0,
367
+ 5
368
+ )
253
369
 
254
370
  #Set up actions and merge colors according to selected behaviors.
255
- behavior = Behavior.new
256
- behavior.conditions << ProximityCondition.new(@action_proximity)
257
371
  color = Color.new(0.25, 0.25, 0.25)
258
372
  if options[:accelerate]
259
373
  color.red += 0.25
260
374
  color.green += 0.25
261
375
  color.blue += 0.25
262
- behavior.actions << AccelerateAction.new(@acceleration_rate)
376
+ creature.behaviors << create_behavior(:actions => [AccelerateAction.new(@acceleration_rate)])
263
377
  end
264
378
  if options[:turn]
265
379
  color.blue += 1
266
- behavior.actions << TurnAction.new(@turn_rate)
380
+ creature.behaviors << create_behavior(:actions => [TurnAction.new(@turn_rate)])
267
381
  end
268
382
  if options[:approach]
269
383
  color.red += 1
270
- behavior.actions << ApproachAction.new(vector, @approach_turn_rate)
384
+ creature.behaviors << create_behavior(:actions => [ApproachAction.new(@approach_turn_rate, creature.vector)])
271
385
  end
272
386
  if options[:flee]
273
387
  color.red += 0.5; color.green += 0.5 #Yellow.
274
- behavior.actions << FleeAction.new(vector, @flee_turn_rate)
388
+ creature.behaviors << create_behavior(:actions => [FleeAction.new(@flee_turn_rate, creature.vector)])
275
389
  end
276
390
  if options[:push]
277
391
  color.red += 0.5; color.blue += 0.5 #Purple.
278
- behavior.actions << PushAction.new(@push_strength)
392
+ creature.behaviors << create_behavior(:actions => [PushAction.new(@push_strength)])
279
393
  end
280
394
  if options[:pull]
281
395
  color.blue += 0.75; color.green += 0.75 #Aqua.
282
- behavior.actions << PullAction.new(@pull_strength)
396
+ creature.behaviors << create_behavior(:actions => [PullAction.new(@pull_strength)])
397
+ end
398
+ if options[:breed]
399
+ color.green -= 0.1 #Make a bit redder.
400
+ color.blue -= 0.1
401
+ creature.behaviors << create_behavior(
402
+ :actions => [BreedAction.new(@environment, @breed_rate)],
403
+ :conditions => [CollisionCondition.new] #The default ProximityCondition won't do.
404
+ )
283
405
  end
284
-
285
- #Create a creature.
286
- creature = Creature.new(
287
- '',
288
- Location.new(options[:x], options[:y]),
289
- color,
290
- vector,
291
- 0,
292
- 5
293
- )
294
- creature.behaviors << behavior
295
-
296
- #Special handling for eat action, which needs a CollisionCondition.
297
406
  if options[:eat]
298
- creature.color.green += 1
299
- behavior = Behavior.new
300
- behavior.actions << EatAction.new(@environment)
301
- behavior.conditions << CollisionCondition.new
302
- creature.behaviors << behavior
407
+ color.green += 1
408
+ creature.behaviors << create_behavior(
409
+ :actions => [EatAction.new(@environment)],
410
+ :conditions => [CollisionCondition.new] #The default ProximityCondition won't do.
411
+ )
303
412
  end
304
413
 
414
+ creature.color = color
415
+
305
416
  @environment.objects << creature
306
417
 
307
418
  end
419
+
420
+
421
+ def create_behavior(options = {})
422
+
423
+ options = {
424
+ :actions => [],
425
+ :conditions => [ProximityCondition.new(@action_proximity)],
426
+ }.merge(options)
427
+
428
+ behavior = Behavior.new
429
+ behavior.actions = options[:actions]
430
+ behavior.conditions = options[:conditions]
431
+ behavior
432
+
433
+ end
434
+
435
+
308
436
  end
309
437
 
310
438
 
311
439
 
312
440
  begin
313
- #The view width.
314
- WIDTH = 500
315
- #The view height.
316
- HEIGHT = 600
317
- #Run the application.
318
- application = Application.new(WIDTH, HEIGHT)
441
+ #Create a server.
442
+ application = Application.new
443
+ #Parse the command line.
444
+ application.process_options(ARGV)
445
+ #Start the server.
319
446
  application.main
320
447
  rescue => exception
321
448
  #Print error to STDERR and exit with an abnormal status.
data/bin/zyps_demo CHANGED
@@ -353,7 +353,7 @@ class Demo
353
353
  say("We also add a condition that it should only target food.")
354
354
  @environment.objects.each do |creature|
355
355
  approach = Behavior.new
356
- approach.actions << ApproachAction.new(creature.vector)
356
+ approach.actions << ApproachAction.new(360, creature.vector)
357
357
  approach.conditions << TagCondition.new("food")
358
358
  creature.behaviors << approach
359
359
  end
@@ -384,7 +384,7 @@ class Demo
384
384
  say("A FleeAction is just like an ApproachAction, but we head in the OPPOSITE direction.")
385
385
  @environment.objects.each do |creature|
386
386
  flee = Behavior.new
387
- flee.actions << FleeAction.new(creature.vector)
387
+ flee.actions << FleeAction.new(360, creature.vector)
388
388
  flee.conditions << TagCondition.new("predator")
389
389
  creature.behaviors << flee
390
390
  end
data/lib/zyps/actions.rb CHANGED
@@ -21,6 +21,41 @@ require 'zyps'
21
21
  module Zyps
22
22
 
23
23
 
24
+ #Superclass for actions that need to happen at a specific rate.
25
+ class TimedAction < Action
26
+
27
+ #A Clock that tracks time between actions.
28
+ attr_accessor :clock
29
+ #Units per second for action.
30
+ attr_accessor :rate
31
+
32
+ def initialize(rate, *arguments)
33
+ self.rate = rate
34
+ @clock = Clock.new
35
+ end
36
+
37
+ #Make a deep copy.
38
+ def copy
39
+ copy = super
40
+ #Copies should have their own Clock.
41
+ copy.clock = Clock.new
42
+ copy
43
+ end
44
+
45
+ #Units to add to the attribute being changed.
46
+ def delta
47
+ @clock.elapsed_time * rate
48
+ end
49
+
50
+ #Begin tracking time between actions.
51
+ def start(actor, target)
52
+ super
53
+ @clock.reset_elapsed_time
54
+ end
55
+
56
+ end
57
+
58
+
24
59
  #Head toward a target.
25
60
  class FaceAction < Action
26
61
  #Set the actor's heading to point directly at target.
@@ -31,61 +66,44 @@ end
31
66
 
32
67
 
33
68
  #Increase/decrease speed over time.
34
- class AccelerateAction < Action
69
+ class AccelerateAction < TimedAction
35
70
  #Units per second to accelerate.
36
71
  #Can be negative to slow down or go in reverse.
37
72
  attr_accessor :rate
38
- def initialize(rate = 0)
39
- self.rate = rate
40
- @clock = Clock.new
41
- end
42
- #Begin tracking time between actions.
43
- def start(actor, target)
44
- super
45
- @clock.reset_elapsed_time
46
- end
47
73
  #Increase or decrease speed according to elapsed time.
48
74
  def do(actor, target)
49
- actor.vector.speed += @clock.elapsed_time * rate
75
+ actor.vector.speed += delta
50
76
  end
51
77
  end
52
78
 
53
79
 
54
80
  #Turn over time.
55
- class TurnAction < Action
81
+ class TurnAction < TimedAction
56
82
  #Degrees per second to turn.
57
83
  #Positive turns clockwise, negative turns counter-clockwise.
58
84
  attr_accessor :rate
59
- def initialize(rate = 0)
60
- self.rate = rate
61
- @clock = Clock.new
62
- end
63
- #Begin tracking time between actions.
64
- def start(actor, target)
65
- super
66
- @clock.reset_elapsed_time
67
- end
68
85
  #Turn according to elapsed time.
69
86
  def do(actor, target)
70
- actor.vector.pitch += @clock.elapsed_time * rate
87
+ actor.vector.pitch += delta
71
88
  end
72
89
  end
73
90
 
74
91
 
75
92
  #Approaches the target, but obeys law of inertia.
76
- class ApproachAction < Action
93
+ class ApproachAction < TimedAction
77
94
  #Direction/speed actor is accelerating in.
78
95
  attr_accessor :heading
79
96
  #Degrees per second the direction of acceleration can change.
80
- attr_accessor :turn_rate
81
- def initialize(heading = Vector.new, turn_rate = 360)
82
- self.heading, self.turn_rate = heading, turn_rate
83
- @clock = Clock.new
84
- end
85
- #Begin tracking time between actions.
86
- def start(actor, target)
97
+ attr_accessor :rate
98
+ def initialize(rate = 360, heading = Vector.new)
87
99
  super
88
- @clock.reset_elapsed_time
100
+ self.heading = heading
101
+ end
102
+ #Make a deep copy.
103
+ def copy
104
+ copy = super
105
+ copy.heading = @heading.copy
106
+ copy
89
107
  end
90
108
  #Accelerate toward the target, but limited by turn rate.
91
109
  def do(actor, target)
@@ -98,7 +116,7 @@ class ApproachAction < Action
98
116
  turn_angle += 360.0
99
117
  end
100
118
  #The creature can only turn as fast as the elapsed time, of course.
101
- maximum_turn = turn_rate * @clock.elapsed_time
119
+ maximum_turn = delta
102
120
  if turn_angle.abs > maximum_turn
103
121
  if turn_angle > 0
104
122
  turn_angle = maximum_turn
@@ -115,19 +133,20 @@ end
115
133
 
116
134
 
117
135
  #Flees from the target, but obeys law of inertia.
118
- class FleeAction < Action
136
+ class FleeAction < TimedAction
119
137
  #Direction/speed actor is accelerating in.
120
138
  attr_accessor :heading
121
139
  #Degrees per second the direction of acceleration can change.
122
- attr_accessor :turn_rate
123
- def initialize(heading = Vector.new, turn_rate = 360)
124
- self.heading, self.turn_rate = heading, turn_rate
125
- @clock = Clock.new
126
- end
127
- #Begin tracking time between actions.
128
- def start(actor, target)
140
+ attr_accessor :rate
141
+ def initialize(rate = 360, heading = Vector.new)
129
142
  super
130
- @clock.reset_elapsed_time
143
+ self.heading = heading
144
+ end
145
+ #Make a deep copy.
146
+ def copy
147
+ copy = super
148
+ copy.heading = @heading.copy
149
+ copy
131
150
  end
132
151
  #Accelerate away from the target, but limited by turn rate.
133
152
  def do(actor, target)
@@ -140,7 +159,7 @@ class FleeAction < Action
140
159
  turn_angle += 360.0
141
160
  end
142
161
  #The creature can only turn as fast as the elapsed time, of course.
143
- maximum_turn = turn_rate * @clock.elapsed_time
162
+ maximum_turn = delta
144
163
  if turn_angle.abs > maximum_turn
145
164
  if turn_angle > 0
146
165
  turn_angle = maximum_turn
@@ -166,8 +185,6 @@ class DestroyAction < Action
166
185
  #Remove the target from the environment.
167
186
  def do(actor, target)
168
187
  @environment.objects.delete(target)
169
- #Discontinue action.
170
- return false
171
188
  end
172
189
  end
173
190
 
@@ -213,24 +230,15 @@ end
213
230
 
214
231
 
215
232
  #Pushes target away.
216
- class PushAction < Action
233
+ class PushAction < TimedAction
217
234
  #Units/second to accelerate target by.
218
- attr_accessor :force
219
- def initialize(force = 1)
220
- @force = force
221
- @clock = Clock.new
222
- end
223
- #Begin tracking time between actions.
224
- def start(actor, target)
225
- super
226
- @clock.reset_elapsed_time
227
- end
235
+ attr_accessor :rate
228
236
  #Accelerate the target away from the actor, but limited by elapsed time.
229
237
  def do(actor, target)
230
238
  #Angle to target is also angle of push force.
231
239
  push_angle = Utility.find_angle(actor.location, target.location)
232
240
  #Acceleration will be limited by elapsed time.
233
- push_force = @force * @clock.elapsed_time
241
+ push_force = delta
234
242
  #Apply the force to the creature's movement vector.
235
243
  target.vector += Vector.new(push_force, push_angle)
236
244
  end
@@ -238,28 +246,59 @@ end
238
246
 
239
247
 
240
248
  #Pulls target toward actor.
241
- class PullAction < Action
249
+ class PullAction < TimedAction
242
250
  #Units/second to accelerate target by.
243
- attr_accessor :force
244
- def initialize(force = 1)
245
- @force = force
246
- @clock = Clock.new
247
- end
248
- #Begin tracking time between actions.
249
- def start(actor, target)
250
- super
251
- @clock.reset_elapsed_time
252
- end
251
+ attr_accessor :rate
253
252
  #Accelerate away from the target, but limited by turn rate.
254
253
  def do(actor, target)
255
254
  #Angle from target to actor is also angle of pull force (opposite of that for push).
256
255
  pull_angle = Utility.find_angle(target.location, actor.location)
257
256
  #Acceleration will be limited by elapsed time.
258
- pull_force = @force * @clock.elapsed_time
257
+ pull_force = delta
259
258
  #Apply the force to the creature's movement vector.
260
259
  target.vector += Vector.new(pull_force, pull_angle)
261
260
  end
262
261
  end
263
262
 
264
263
 
264
+ class BreedAction < Action
265
+ DEFAULT_DELAY = 60
266
+ #Environment to place children into.
267
+ attr_accessor :environment
268
+ #Delay between actions.
269
+ attr_accessor :delay
270
+ def initialize(environment, delay = DEFAULT_DELAY)
271
+ self.environment, self.delay = environment, delay
272
+ @clock = Clock.new
273
+ @time_since_last_action = 0
274
+ end
275
+ def do(actor, target)
276
+ #Skip action if target is not a Creature.
277
+ return unless target.is_a?(Creature)
278
+ #Get time since last action, and skip if it hasn't been long enough.
279
+ @time_since_last_action += @clock.elapsed_time
280
+ return unless @time_since_last_action >= @delay
281
+ #Create a child.
282
+ child = Creature.new
283
+ #Combine colors.
284
+ child.color = actor.color + target.color
285
+ #Combine behaviors EXCEPT those with BreedActions.
286
+ behaviors = (actor.behaviors + target.behaviors).find_all do |behavior|
287
+ ! behavior.actions.any?{|action| action.is_a?(BreedAction)}
288
+ end
289
+ behaviors.each {|behavior| child.behaviors << behavior.copy}
290
+ #Location should equal actor's.
291
+ child.location = actor.location.copy
292
+ #Add parents' vectors to get child's vector.
293
+ child.vector = actor.vector + target.vector
294
+ #Child's size should be half the average size of the parents'.
295
+ child.size = ((actor.size + target.size) / 2) / 2
296
+ #Add child to environment.
297
+ @environment.objects << child
298
+ #Reset elapsed time.
299
+ @time_since_last_action = 0
300
+ end
301
+ end
302
+
303
+
265
304
  end #module Zyps
data/lib/zyps.rb CHANGED
@@ -117,7 +117,19 @@ class GameObject
117
117
 
118
118
  def initialize (name = nil, location = Location.new, color = Color.new, vector = Vector.new, age = 0, size = 1, tags = [])
119
119
  self.name, self.location, self.color, self.vector, self.age, self.size, self.tags = name, location, color, vector, age, size, tags
120
- @identifier = rand(99999999) #TODO: Current setup won't necessarily be unique.
120
+ @identifier = generate_identifier
121
+ end
122
+
123
+ #Make a deep copy.
124
+ def copy
125
+ copy = self.clone
126
+ copy.vector = @vector.copy
127
+ copy.color = @color.copy
128
+ copy.location = @location.copy
129
+ copy.tags = @tags.clone
130
+ copy.identifier = generate_identifier
131
+ copy.name = @name ? "Copy of " + @name : nil
132
+ copy
121
133
  end
122
134
 
123
135
  #Size must be positive.
@@ -133,6 +145,19 @@ class GameObject
133
145
  def age; Time.new.to_f - @birth_time; end
134
146
  def age=(age); @birth_time = Time.new.to_f - age; end
135
147
 
148
+ #Set identifier.
149
+ #Not part of API; copy() needs this to make copy's ID unique.
150
+ def identifier=(value) #:nodoc:
151
+ @identifier = value
152
+ end
153
+
154
+ private
155
+
156
+ #Make a unique GameObject identifier.
157
+ def generate_identifier
158
+ rand(99999999) #TODO: Current setup won't necessarily be unique.
159
+ end
160
+
136
161
  end
137
162
 
138
163
 
@@ -149,6 +174,15 @@ class Creature < GameObject
149
174
  self.behaviors = behaviors
150
175
  end
151
176
 
177
+ #Make a deep copy.
178
+ def copy
179
+ copy = super
180
+ #Make deep copy of each behavior.
181
+ copy.behaviors = []
182
+ @behaviors.each {|behavior| copy.behaviors << behavior.copy}
183
+ copy
184
+ end
185
+
152
186
  #Call Behavior.perform(self, environment) on each of the creature's assigned Behaviors.
153
187
  def act(environment)
154
188
  behaviors.each {|behavior| behavior.perform(self, environment)}
@@ -175,15 +209,18 @@ class Action
175
209
  @started = false
176
210
  end
177
211
 
212
+ #Make a deep copy.
213
+ def copy; self.clone; end
214
+
178
215
  #Start the action.
179
216
  #Overriding subclasses must either call "super" or set the @started attribute to true.
180
217
  def start(actor, target)
181
218
  @started = true
182
219
  end
183
220
 
184
- #Perform the action.
185
- #Subclasses should override this.
221
+ #Action subclasses must implement a do(actor, target) instance method.
186
222
  def do(actor, target)
223
+ raise NotImplementedError.new("Action subclasses must implement a do(actor, target) instance method.")
187
224
  end
188
225
 
189
226
  #Stop the action.
@@ -197,8 +234,16 @@ end
197
234
 
198
235
 
199
236
  #A condition for one Creature to act on another.
200
- #Conditions must implement a met?(actor, target) instance method.
201
237
  class Condition
238
+
239
+ #Make a deep copy.
240
+ def copy; self.clone; end
241
+
242
+ #Condition subclasses must implement a met?(actor, target) instance method.
243
+ def met?(actor, target)
244
+ raise NotImplementedError.new("Condition subclasses must implement a met?(actor, target) instance method.")
245
+ end
246
+
202
247
  end
203
248
 
204
249
 
@@ -220,6 +265,18 @@ class Behavior
220
265
  @active_target = nil
221
266
  end
222
267
 
268
+ #Make a deep copy.
269
+ def copy
270
+ copy = self.clone #Currently, we overwrite everything anyway, but we may add some clonable attributes later.
271
+ #Make a deep copy of all actions.
272
+ copy.actions = []
273
+ @actions.each {|action| copy.actions << action.copy}
274
+ #Make a deep copy of all conditions.
275
+ copy.conditions = []
276
+ @conditions.each {|condition| copy.conditions << condition.copy}
277
+ copy
278
+ end
279
+
223
280
  #Test all conditions against each object in the evironment.
224
281
  #For the first object that meets all of them, mark it active (and operate on it first next time).
225
282
  #Then call start() (if applicable) and perform() for all actions against the active target.
@@ -294,6 +351,9 @@ class Color
294
351
  self.red, self.green, self.blue = red, green, blue
295
352
  end
296
353
 
354
+ #Make a deep copy.
355
+ def copy; self.clone; end
356
+
297
357
  #Automatically constrains value to the range 0 - 1.
298
358
  def red=(v); v = 0 if v < 0; v = 1 if v > 1; @red = v; end
299
359
  #Automatically constrains value to the range 0 - 1.
@@ -329,6 +389,9 @@ class Location
329
389
  def initialize (x = 0, y = 0)
330
390
  self.x, self.y = x, y
331
391
  end
392
+
393
+ #Make a deep copy.
394
+ def copy; self.clone; end
332
395
 
333
396
  end
334
397
 
@@ -346,6 +409,9 @@ class Vector
346
409
  self.pitch = pitch
347
410
  end
348
411
 
412
+ #Make a deep copy.
413
+ def copy; self.clone; end
414
+
349
415
  #The angle along the X/Y axes.
350
416
  def pitch; Utility.to_degrees(@pitch); end
351
417
  def pitch=(degrees)
@@ -390,6 +456,9 @@ class Clock
390
456
  reset_elapsed_time
391
457
  end
392
458
 
459
+ #Make a deep copy.
460
+ def copy; self.clone; end
461
+
393
462
  #Returns the time in (fractional) seconds since this method was last called (or on the first call, time since the Clock was created).
394
463
  def elapsed_time
395
464
  time = Time.new.to_f
data/test/test_zyps.rb CHANGED
@@ -61,6 +61,27 @@ class TestGameObject < Test::Unit::TestCase
61
61
  assert_in_delta(1, object.location.x, 0.001)
62
62
  assert_in_delta(1, object.location.y, 0.001)
63
63
  end
64
+
65
+
66
+ def test_copy
67
+ object = GameObject.new
68
+ object.vector = Vector.new(1, 1)
69
+ object.color = Color.new(0.5, 0.5, 0.5)
70
+ object.location = Location.new(3, 3)
71
+ object.tags = ["1", "2"]
72
+ object.name = "name"
73
+ copy = object.copy
74
+ assert_not_same(object.vector, copy.vector, "Copy's vector should not be same object.")
75
+ assert_equal(object.vector.x, copy.vector.x, "Copy's vector should share attributes.")
76
+ assert_not_same(object.color, copy.color, "Copy's color should not be same object.")
77
+ assert_equal(object.color.red, copy.color.red, "Copy's color should share attributes.")
78
+ assert_not_same(object.location, copy.location, "Copy's location should not be same object.")
79
+ assert_equal(object.location.x, copy.location.x, "Copy's location should share attributes.")
80
+ assert_not_same(object.tags, copy.tags, "Copy's tag list should not be same object.")
81
+ assert_equal(object.tags[0], copy.tags[0], "Copy's tag list should share attributes.")
82
+ assert_not_equal(object.identifier, copy.identifier, "Copy's identifier should not be identical.")
83
+ assert_not_equal(object.name, copy.name, "Copy's name should not be identical.")
84
+ end
64
85
 
65
86
 
66
87
  end
@@ -115,6 +136,17 @@ class TestCreature < Test::Unit::TestCase
115
136
  end
116
137
 
117
138
 
139
+ def test_copy
140
+ creature = Creature.new
141
+ behavior1 = Behavior.new
142
+ behavior2 = Behavior.new
143
+ creature.behaviors << behavior1 << behavior2
144
+ copy = creature.copy
145
+ assert_not_same(creature.behaviors, copy.behaviors, "Copy's behavior list should not be same object.")
146
+ assert_not_same(creature.behaviors[0], copy.behaviors[0], "Behaviors in list should not be same objects.")
147
+ end
148
+
149
+
118
150
  end
119
151
 
120
152
 
@@ -476,7 +508,7 @@ class TestUtility < Test::Unit::TestCase
476
508
 
477
509
  end
478
510
 
479
- #TODO: Rewrite to pass environment.
511
+
480
512
  class TestBehavior < Test::Unit::TestCase
481
513
 
482
514
  #Always true.
@@ -551,5 +583,18 @@ class TestBehavior < Test::Unit::TestCase
551
583
 
552
584
  end
553
585
 
586
+ def test_copy
587
+ original = Behavior.new
588
+ action = TagAction.new("tag")
589
+ original.actions << action
590
+ condition = TagCondition.new("tag")
591
+ original.conditions << condition
592
+ copy = original.copy
593
+ assert_not_same(original.actions, copy.actions, "Copy's action list should not be same object.")
594
+ assert_not_same(original.actions[0], copy.actions[0], "Actions in list should not be same objects.")
595
+ assert_not_same(original.conditions, copy.conditions, "Copy's condition list should not be same object.")
596
+ assert_not_same(original.conditions[0], copy.conditions[0], "Conditions in list should not be same objects.")
597
+ end
598
+
554
599
 
555
600
  end
@@ -40,9 +40,9 @@ class TestActions < Test::Unit::TestCase
40
40
 
41
41
  #Create and populate an environment.
42
42
  def setup
43
- @actor = Creature.new('name', Location.new(0, 0))
44
- @target1 = GameObject.new('name', Location.new(1, 1))
45
- @target2 = GameObject.new('name', Location.new(-2, -2))
43
+ @actor = Creature.new('actor', Location.new(0, 0))
44
+ @target1 = Creature.new('target1', Location.new(1, 1))
45
+ @target2 = Creature.new('target2', Location.new(-2, -2))
46
46
  #Create an environment, and add the objects.
47
47
  @environment = Environment.new
48
48
  #Order is important - we want to act on target 1 first.
@@ -90,7 +90,7 @@ class TestActions < Test::Unit::TestCase
90
90
 
91
91
  #Create an ApproachAction with a 0-degree vector, turn rate of 40 degrees/sec.
92
92
  @actor.vector = Vector.new(0, 0)
93
- action = ApproachAction.new(Vector.new(1, 0), 40)
93
+ action = ApproachAction.new(40, Vector.new(1, 0))
94
94
  add_action(action, @actor)
95
95
  #Act.
96
96
  @environment.interact
@@ -109,7 +109,7 @@ class TestActions < Test::Unit::TestCase
109
109
  #Create an ApproachAction with a 0-degree vector, turn rate high enough to turn more than 45 degrees in 0.1 seconds.
110
110
  #Action should only turn as far as target.
111
111
  @actor.vector = Vector.new(0, 0)
112
- action = ApproachAction.new(Vector.new(1, 0), 500)
112
+ action = ApproachAction.new(500, Vector.new(1, 0))
113
113
  add_action(action, @actor)
114
114
  #Act.
115
115
  @environment.interact
@@ -129,7 +129,7 @@ class TestActions < Test::Unit::TestCase
129
129
 
130
130
  #Create a FleeAction with a 0-degree vector, turn rate of 40 degrees/sec.
131
131
  @actor.vector = Vector.new(0, 0)
132
- action = FleeAction.new(Vector.new(1, 0), 40)
132
+ action = FleeAction.new(40, Vector.new(1, 0))
133
133
  add_action(action, @actor)
134
134
  #Act.
135
135
  @environment.interact
@@ -147,7 +147,7 @@ class TestActions < Test::Unit::TestCase
147
147
  #Create a FleeAction with a 0-degree vector, turn rate high enough to turn more than 135 degrees in 0.1 seconds.
148
148
  #Action should turn directly away from target, but no farther.
149
149
  @actor.vector = Vector.new(0, 0)
150
- action = FleeAction.new(Vector.new(1, 0), 1400)
150
+ action = FleeAction.new(1400, Vector.new(1, 0))
151
151
  add_action(action, @actor)
152
152
  #Act.
153
153
  @environment.interact
@@ -253,4 +253,31 @@ class TestActions < Test::Unit::TestCase
253
253
  assert_in_delta(225.0, @target1.vector.pitch, REQUIRED_ACCURACY, "@target1's angle should be facing toward @actor.")
254
254
  end
255
255
 
256
+
257
+ #A BreedAction creates a new Creature by combining the actor's color and behaviors with another creature.
258
+ def test_breed_action
259
+ #Create two creatures with different colors and behaviors.
260
+ @actor.color = Color.new(1, 1, 1)
261
+ @target1.color = Color.new(0, 0, 0)
262
+ add_action(TagAction.new("1"), @actor)
263
+ add_action(TagAction.new("2"), @target1)
264
+ #Set actor's location to a non-standard place.
265
+ @actor.location = Location.new(33, 33)
266
+ #Create a BreedAction using the Environment, and act.
267
+ add_action(BreedAction.new(@environment, 0.2), @actor) #0.1 delay ensures modified Clock will trigger action on second operation.
268
+ @environment.interact
269
+ @environment.interact #Act twice to trigger action on actor (and only actor).
270
+ #Find child.
271
+ child = @environment.objects.last
272
+ #Ensure child's color is a mix of parents'.
273
+ assert_equal(Color.new(0.5, 0.5, 0.5), child.color)
274
+ #Ensure child's behaviors combine the parents', but exclude those with BreedActions.
275
+ assert_equal("1", child.behaviors[0].actions.first.tag)
276
+ assert_equal("2", child.behaviors[1].actions.first.tag)
277
+ assert_equal(2, child.behaviors.length)
278
+ #Ensure child appears at actor's location.
279
+ assert_equal(@actor.location.x, child.location.x)
280
+ assert_equal(@actor.location.y, child.location.y)
281
+ end
282
+
256
283
  end
metadata CHANGED
@@ -3,8 +3,8 @@ rubygems_version: 0.9.2
3
3
  specification_version: 1
4
4
  name: zyps
5
5
  version: !ruby/object:Gem::Version
6
- version: 0.6.2
7
- date: 2007-11-01 00:00:00 -07:00
6
+ version: 0.6.3
7
+ date: 2007-11-08 00:00:00 -07:00
8
8
  summary: A game library for Ruby
9
9
  require_paths:
10
10
  - lib