zyps 0.6.2 → 0.6.3
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.
- 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
|