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 +185 -58
- data/bin/zyps_demo +2 -2
- data/lib/zyps/actions.rb +107 -68
- data/lib/zyps.rb +73 -4
- data/test/test_zyps.rb +46 -1
- data/test/zyps/test_actions.rb +34 -7
- metadata +2 -2
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
|
-
|
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
|
-
@
|
48
|
-
|
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
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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(
|
110
|
+
@environment.environmental_factors << SpeedLimit.new(@max_speed) if @max_speed
|
85
111
|
|
86
112
|
#Limit population.
|
87
|
-
@environment.environmental_factors << PopulationLimit.new(@environment,
|
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(
|
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(
|
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 =
|
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
|
358
|
+
}.merge(options)
|
250
359
|
|
251
|
-
#
|
252
|
-
|
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
|
-
|
376
|
+
creature.behaviors << create_behavior(:actions => [AccelerateAction.new(@acceleration_rate)])
|
263
377
|
end
|
264
378
|
if options[:turn]
|
265
379
|
color.blue += 1
|
266
|
-
|
380
|
+
creature.behaviors << create_behavior(:actions => [TurnAction.new(@turn_rate)])
|
267
381
|
end
|
268
382
|
if options[:approach]
|
269
383
|
color.red += 1
|
270
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
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
|
-
#
|
314
|
-
|
315
|
-
#
|
316
|
-
|
317
|
-
#
|
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 <
|
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 +=
|
75
|
+
actor.vector.speed += delta
|
50
76
|
end
|
51
77
|
end
|
52
78
|
|
53
79
|
|
54
80
|
#Turn over time.
|
55
|
-
class TurnAction <
|
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 +=
|
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 <
|
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 :
|
81
|
-
def initialize(
|
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
|
-
|
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 =
|
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 <
|
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 :
|
123
|
-
def initialize(
|
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
|
-
|
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 =
|
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 <
|
233
|
+
class PushAction < TimedAction
|
217
234
|
#Units/second to accelerate target by.
|
218
|
-
attr_accessor :
|
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 =
|
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 <
|
249
|
+
class PullAction < TimedAction
|
242
250
|
#Units/second to accelerate target by.
|
243
|
-
attr_accessor :
|
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 =
|
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 =
|
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
|
-
#
|
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
|
-
|
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
|
data/test/zyps/test_actions.rb
CHANGED
@@ -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('
|
44
|
-
@target1 =
|
45
|
-
@target2 =
|
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)
|
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)
|
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)
|
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)
|
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.
|
7
|
-
date: 2007-11-
|
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
|