zyps 0.1.1 → 0.2.1
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/README.txt +19 -1
- data/bin/zyps +357 -5
- data/bin/zyps_demo +7 -4
- data/bin/zyps_server +178 -0
- data/lib/zyps.rb +33 -12
- data/lib/zyps/views/trails.rb +6 -6
- data/test/test_zyps.rb +51 -2
- metadata +4 -2
data/README.txt
CHANGED
@@ -26,10 +26,28 @@ Make sure you have administrative privileges, then type the following at a comma
|
|
26
26
|
|
27
27
|
Ensure Ruby-GNOME2 is installed and working.
|
28
28
|
|
29
|
-
|
29
|
+
At a command line, type:
|
30
|
+
|
31
|
+
zyps
|
32
|
+
|
33
|
+
To see a tutorial on the library, at a command line, type:
|
30
34
|
|
31
35
|
zyps_demo
|
32
36
|
|
37
|
+
To run a DRb server others can connect to and add creatures (NOTE: not thoroughly tested and probably insecure!):
|
38
|
+
|
39
|
+
zyps_server [options]
|
40
|
+
-h, --help Display program help.
|
41
|
+
-m, --max-population [number] The maximum number of allowed game
|
42
|
+
objects. 25 by default.
|
43
|
+
-p, --port [number] Port number to run the server on.
|
44
|
+
If not defined, next available one
|
45
|
+
will be selected and the URI printed
|
46
|
+
to STDOUT.
|
47
|
+
-f, --fps [frames] Number of frames to draw per second.
|
48
|
+
--view-width [pixels] Window width. 800 by default.
|
49
|
+
--view-height [pixels] Window height. 600 by default.
|
50
|
+
|
33
51
|
|
34
52
|
== Development
|
35
53
|
|
data/bin/zyps
CHANGED
@@ -28,14 +28,366 @@ rescue LoadError
|
|
28
28
|
end
|
29
29
|
|
30
30
|
|
31
|
-
|
31
|
+
#Generates new creatures and places them in an environment.
|
32
|
+
class Generator
|
32
33
|
|
33
|
-
|
34
|
-
|
34
|
+
#Environment creatures will be spawned into.
|
35
|
+
attr_accessor :environment
|
36
|
+
#Boundary that all new objects must be inside.
|
37
|
+
attr_accessor :min_x, :min_y, :max_x, :max_y
|
38
|
+
#Array of tags to choose from when generating new one.
|
39
|
+
attr_accessor :tag_pool
|
40
|
+
#Array of colors to choose from when generating new one.
|
41
|
+
attr_accessor :color_pool
|
42
|
+
#The maximum speed a new creature should be going.
|
43
|
+
attr_accessor :max_speed
|
44
|
+
#The maximum number of behaviors a creature can have.
|
45
|
+
attr_accessor :max_behaviors
|
46
|
+
#The maximum number of conditions a Behavior can have.
|
47
|
+
attr_accessor :max_conditions
|
48
|
+
#The maximum number of actions a Behavior can have.
|
49
|
+
attr_accessor :max_actions
|
50
|
+
|
51
|
+
def initialize(environment, min_x, min_y, max_x, max_y)
|
52
|
+
@environment, @min_x, @min_y, @max_x, @max_y = environment, min_x, min_y, max_x, max_y
|
53
|
+
@tag_pool = %q{foo bar baz}
|
54
|
+
@color_pool = []
|
55
|
+
@max_behaviors = 2
|
56
|
+
@max_conditions = 2
|
57
|
+
@max_actions = 2
|
58
|
+
@max_speed = 100
|
59
|
+
end
|
60
|
+
|
61
|
+
#Create a creature.
|
62
|
+
def create_creature()
|
63
|
+
|
64
|
+
creature = Creature.new()
|
65
|
+
#Create a Color.
|
66
|
+
creature.color = create_color
|
67
|
+
#Create a Location.
|
68
|
+
creature.location = create_location
|
69
|
+
#Create a Vector.
|
70
|
+
creature.vector = create_vector()
|
71
|
+
#Create a random tag.
|
72
|
+
creature.tags << create_tag()
|
73
|
+
#Create a random number of behaviors.
|
74
|
+
generate_number(0, @max_behaviors).to_i.times {creature.behaviors << create_behavior()}
|
75
|
+
|
76
|
+
creature
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
#Create a random tag.
|
81
|
+
#Chooses from tag_pool.
|
82
|
+
def create_tag()
|
83
|
+
@tag_pool[generate_number(0, @tag_pool.length).to_i]
|
84
|
+
end
|
85
|
+
#Create a random Color.
|
86
|
+
#Chooses from color_pool, or creates a random one from scratch if color_pool is empty.
|
87
|
+
def create_color()
|
88
|
+
if (color_pool.length != 0) then
|
89
|
+
return @color_pool[generate_number(0, @color_pool.length).to_i]
|
90
|
+
else
|
91
|
+
return Color.new(generate_random_number(0, 1), generate_random_number(0, 1), generate_random_number(0, 1))
|
92
|
+
end
|
93
|
+
end
|
94
|
+
#Create a random Location.
|
95
|
+
def create_location()
|
96
|
+
Location.new(
|
97
|
+
generate_random_number(0, @max_x - @min_x) + @min_x,
|
98
|
+
generate_random_number(0, @max_y - @min_y) + @min_y
|
99
|
+
)
|
100
|
+
end
|
101
|
+
#Create a random Vector.
|
102
|
+
def create_vector()
|
103
|
+
Vector.new(generate_number(0, @max_speed), generate_number(0, 360))
|
104
|
+
end
|
105
|
+
#Create a random Behavior.
|
106
|
+
def create_behavior()
|
107
|
+
behavior = Behavior.new
|
108
|
+
action_count = generate_number(0, @max_actions).to_i
|
109
|
+
action_count.times do
|
110
|
+
behavior.actions << create_action()
|
111
|
+
end
|
112
|
+
#Only add conditions if there's an action.
|
113
|
+
if action_count > 0 then
|
114
|
+
#There should always be a proximity condition.
|
115
|
+
behavior.conditions << create_proximity_condition()
|
116
|
+
#Add some additional conditions.
|
117
|
+
generate_random_number(0, @max_conditions).to_i.times do
|
118
|
+
behavior.conditions << create_condition()
|
119
|
+
end
|
120
|
+
end
|
121
|
+
behavior
|
122
|
+
end
|
123
|
+
#Create a random condition.
|
124
|
+
def create_condition()
|
125
|
+
case generate_number(0, 2).to_i
|
126
|
+
when 0
|
127
|
+
return create_tag_condition()
|
128
|
+
when 1
|
129
|
+
return create_age_condition()
|
130
|
+
else
|
131
|
+
raise "Invalid condition"
|
132
|
+
end
|
133
|
+
end
|
134
|
+
#Create a condition that looks for a random tag.
|
135
|
+
def create_tag_condition()
|
136
|
+
tag = create_tag()
|
137
|
+
lambda {|creature, target| target.tags.include?(tag)}
|
138
|
+
end
|
139
|
+
#Create a condition that looks for a random age.
|
140
|
+
def create_age_condition()
|
141
|
+
age = generate_number(0, 60)
|
142
|
+
lambda {|creature, target| target.age > age}
|
143
|
+
end
|
144
|
+
#Create a condition that looks for a random proximity.
|
145
|
+
def create_proximity_condition()
|
146
|
+
proximity = generate_number(0, 100)
|
147
|
+
lambda do |creature, target|
|
148
|
+
Utility.find_distance(creature.location, target.location) < proximity
|
149
|
+
end
|
150
|
+
end
|
151
|
+
#Create a random action.
|
152
|
+
def create_action()
|
153
|
+
case generate_random_number(0, 9).to_i
|
154
|
+
when 0
|
155
|
+
return create_accelerate_action()
|
156
|
+
when 1
|
157
|
+
return create_approach_action()
|
158
|
+
when 2
|
159
|
+
return create_flee_action()
|
160
|
+
when 3
|
161
|
+
return create_blend_action()
|
162
|
+
when 4
|
163
|
+
return create_mate_action()
|
164
|
+
when 5
|
165
|
+
return create_spawn_action()
|
166
|
+
when 6
|
167
|
+
return create_turn_action()
|
168
|
+
when 7
|
169
|
+
return create_eat_action()
|
170
|
+
when 8
|
171
|
+
return create_tag_action()
|
172
|
+
else
|
173
|
+
raise "Invalid action"
|
174
|
+
end
|
175
|
+
end
|
176
|
+
#Create an action that accelerates at a random rate.
|
177
|
+
def create_accelerate_action()
|
178
|
+
clock = Clock.new()
|
179
|
+
rate = generate_number(0, 10)
|
180
|
+
lambda {|creature, target| creature.vector.speed += rate * clock.elapsed_time}
|
181
|
+
end
|
182
|
+
#Create an action that turns in a circle.
|
183
|
+
def create_turn_action()
|
184
|
+
clock = Clock.new()
|
185
|
+
rate = generate_number(0, 360)
|
186
|
+
lambda {|creature, target| creature.vector.pitch += rate * clock.elapsed_time}
|
187
|
+
end
|
188
|
+
#Create an action that approaches the target.
|
189
|
+
def create_approach_action()
|
190
|
+
heading = Vector.new
|
191
|
+
rate = generate_number(0, 20)
|
192
|
+
lambda do |creature, target|
|
193
|
+
#Find the difference between the current heading and the angle to the target.
|
194
|
+
turn_angle = Utility.find_angle(creature.location, target.location) - heading.pitch
|
195
|
+
#If the angle is the long way around from the current heading, change it to the smaller angle.
|
196
|
+
if turn_angle > 180 then
|
197
|
+
turn_angle -= 360.0
|
198
|
+
elsif turn_angle < -180 then
|
199
|
+
turn_angle += 360.0
|
200
|
+
end
|
201
|
+
#If turn angle is greater than allowed turn speed, reduce it.
|
202
|
+
turn_angle = Utility.constrain_value(turn_angle, rate)
|
203
|
+
#Turn the appropriate amount.
|
204
|
+
heading.pitch += turn_angle
|
205
|
+
#Apply the heading to the creature's movement vector.
|
206
|
+
creature.vector += heading
|
207
|
+
end
|
208
|
+
end
|
209
|
+
#Create an action that flees from the target.
|
210
|
+
def create_flee_action()
|
211
|
+
heading = Vector.new
|
212
|
+
rate = generate_number(0, 20)
|
213
|
+
lambda do |creature, target|
|
214
|
+
#Find the difference between the current heading and the angle AWAY from the target.
|
215
|
+
turn_angle = Utility.find_angle(creature.location, target.location) - heading.pitch + 180
|
216
|
+
#If the angle is the long way around from the current heading, change it to the smaller angle.
|
217
|
+
if turn_angle > 180 then
|
218
|
+
turn_angle -= 360.0
|
219
|
+
elsif turn_angle < -180 then
|
220
|
+
turn_angle += 360.0
|
221
|
+
end
|
222
|
+
#If turn angle is greater than allowed turn speed, reduce it.
|
223
|
+
turn_angle = Utility.constrain_value(turn_angle, rate)
|
224
|
+
#Turn the appropriate amount.
|
225
|
+
heading.pitch += turn_angle
|
226
|
+
#Apply the heading to the creature's movement vector.
|
227
|
+
creature.vector += heading
|
228
|
+
end
|
229
|
+
end
|
230
|
+
#Create an action that shifts the subject's color to match the target.
|
231
|
+
def create_blend_action()
|
232
|
+
lambda {|creature, target| creature.color += target.color}
|
233
|
+
end
|
234
|
+
#Create an action that mates with the target.
|
235
|
+
def create_mate_action()
|
236
|
+
#TODO
|
237
|
+
lambda {|creature, target| }
|
238
|
+
end
|
239
|
+
#Create an action that spawns a new creature.
|
240
|
+
def create_spawn_action()
|
241
|
+
#TODO
|
242
|
+
lambda {|creature, target| }
|
243
|
+
end
|
244
|
+
#Create an action that eats the target.
|
245
|
+
def create_eat_action()
|
246
|
+
lambda {|creature, target| @environment.objects.delete(target)}
|
247
|
+
end
|
248
|
+
#Create an action that applies a tag to the target.
|
249
|
+
def create_tag_action()
|
250
|
+
tag = create_tag()
|
251
|
+
lambda {|creature, target| target.tags << tag unless target.tags.include?(tag)}
|
252
|
+
end
|
253
|
+
#Generate a number between the given minimum and maximum, based on the current system time.
|
254
|
+
def generate_number(minimum, maximum)
|
255
|
+
(Time.new.to_f % (maximum - minimum)) + minimum
|
256
|
+
end
|
257
|
+
#Generate a random number between the given minimum and maximum.
|
258
|
+
def generate_random_number(minimum, maximum)
|
259
|
+
value = rand * (maximum - minimum) + minimum
|
260
|
+
end
|
261
|
+
end
|
35
262
|
|
36
|
-
rescue => exception
|
37
263
|
|
264
|
+
#Keeps all objects within a set of walls.
|
265
|
+
class Enclose < Behavior
|
266
|
+
attr_accessor :left, :top, :right, :bottom
|
267
|
+
def initialize
|
268
|
+
super
|
269
|
+
@actions << lambda do |boundary, object|
|
270
|
+
#If object is beyond a boundary, set its position equal to the boundary and reflect it.
|
271
|
+
if (object.location.x < @left) then
|
272
|
+
object.location.x = @left
|
273
|
+
object.vector.pitch = Utility.find_reflection_angle(90, object.vector.pitch)
|
274
|
+
elsif (object.location.x > @right) then
|
275
|
+
object.location.x = @right
|
276
|
+
object.vector.pitch = Utility.find_reflection_angle(270, object.vector.pitch)
|
277
|
+
end
|
278
|
+
if (object.location.y < @top) then
|
279
|
+
object.location.y = @top
|
280
|
+
object.vector.pitch = Utility.find_reflection_angle(0, object.vector.pitch)
|
281
|
+
elsif (object.location.y > @bottom) then
|
282
|
+
object.location.y = @bottom
|
283
|
+
object.vector.pitch = Utility.find_reflection_angle(180, object.vector.pitch)
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
|
290
|
+
class Application
|
291
|
+
|
292
|
+
#Tags to randomly choose from.
|
293
|
+
attr_accessor :tag_pool
|
294
|
+
#Colors to randomly choose from.
|
295
|
+
attr_accessor :color_pool
|
296
|
+
#Seconds between creature creation.
|
297
|
+
attr_accessor :birth_rate
|
298
|
+
|
299
|
+
#Create app window, game environment, and view.
|
300
|
+
def initialize(width, height)
|
301
|
+
|
302
|
+
@width, @height = width, height
|
303
|
+
@tag_pool, @color_pool = [], []
|
304
|
+
@birth_rate = 1
|
305
|
+
|
306
|
+
#Create a window, and set GTK up to quit when it is closed.
|
307
|
+
window = Gtk::Window.new
|
308
|
+
window.signal_connect("delete_event") {false}
|
309
|
+
window.signal_connect("destroy") {Gtk.main_quit}
|
310
|
+
|
311
|
+
#Add view to window.
|
312
|
+
@view = TrailsView.new(@width, @height)
|
313
|
+
window.add(@view.canvas)
|
314
|
+
window.show_all
|
315
|
+
|
316
|
+
#Create environment.
|
317
|
+
@environment = Environment.new
|
318
|
+
|
319
|
+
#Point view at environment.
|
320
|
+
@environment.add_observer(@view)
|
321
|
+
|
322
|
+
end
|
323
|
+
|
324
|
+
|
325
|
+
def main
|
326
|
+
|
327
|
+
#Keep all objects within a boundary.
|
328
|
+
enclose = Enclose.new()
|
329
|
+
enclose.left = 0
|
330
|
+
enclose.right = @width
|
331
|
+
enclose.top = 0
|
332
|
+
enclose.bottom = @height
|
333
|
+
@environment.environmental_factors << EnvironmentalFactor.new([enclose])
|
334
|
+
|
335
|
+
#Create a creature generator.
|
336
|
+
generator = Generator.new(@environment, 0, 0, @width, @height)
|
337
|
+
generator.tag_pool = @tag_pool
|
338
|
+
generator.color_pool = @color_pool
|
339
|
+
|
340
|
+
#Create thread to update environment.
|
341
|
+
thread = Thread.new do
|
342
|
+
begin
|
343
|
+
birth_clock = Clock.new
|
344
|
+
time_since_birth = 0
|
345
|
+
loop do
|
346
|
+
@environment.interact
|
347
|
+
#Control population.
|
348
|
+
@environment.objects.shift while @environment.objects.length > 25
|
349
|
+
#Create new creature every few seconds.
|
350
|
+
if (time_since_birth += birth_clock.elapsed_time) > birth_rate
|
351
|
+
@environment.objects << generator.create_creature()
|
352
|
+
time_since_birth = 0
|
353
|
+
end
|
354
|
+
#Delay 1/60th second to avoid screen flicker.
|
355
|
+
sleep 1.0 / 60.0
|
356
|
+
end
|
357
|
+
rescue Exception => exception
|
358
|
+
puts exception, exception.backtrace
|
359
|
+
end
|
360
|
+
end
|
361
|
+
|
362
|
+
#Activate the GUI.
|
363
|
+
Gtk.main
|
364
|
+
|
365
|
+
end
|
366
|
+
|
367
|
+
|
368
|
+
end
|
369
|
+
|
370
|
+
|
371
|
+
begin
|
372
|
+
TAG_POOL = %w{x y z}
|
373
|
+
COLOR_POOL = [
|
374
|
+
Color.new(1, 0, 0),
|
375
|
+
Color.new(1, 0.75, 0),
|
376
|
+
Color.new(1, 1, 0),
|
377
|
+
Color.new(0, 1, 0),
|
378
|
+
Color.new(0, 0, 1),
|
379
|
+
Color.new(1, 0, 1)
|
380
|
+
]
|
381
|
+
#The view width.
|
382
|
+
WIDTH = 500
|
383
|
+
#The view height.
|
384
|
+
HEIGHT = 400
|
385
|
+
#Run the application.
|
386
|
+
application = Application.new(WIDTH, HEIGHT)
|
387
|
+
application.color_pool = COLOR_POOL
|
388
|
+
application.tag_pool = TAG_POOL
|
389
|
+
application.main
|
390
|
+
rescue => exception
|
38
391
|
#Print error to STDERR and exit with an abnormal status.
|
39
392
|
abort "Error: " + exception.message + exception.backtrace.join("\n")
|
40
|
-
|
41
393
|
end
|
data/bin/zyps_demo
CHANGED
@@ -28,8 +28,6 @@ rescue LoadError
|
|
28
28
|
end
|
29
29
|
|
30
30
|
|
31
|
-
|
32
|
-
|
33
31
|
class Demo
|
34
32
|
|
35
33
|
#The view width.
|
@@ -521,5 +519,10 @@ class Demo
|
|
521
519
|
end
|
522
520
|
|
523
521
|
|
524
|
-
|
525
|
-
|
522
|
+
begin
|
523
|
+
#Run the demos.
|
524
|
+
Demo.new.main
|
525
|
+
rescue => exception
|
526
|
+
#Print error to STDERR and exit with an abnormal status.
|
527
|
+
abort "Error: " + exception.message
|
528
|
+
end
|
data/bin/zyps_server
ADDED
@@ -0,0 +1,178 @@
|
|
1
|
+
#!/usr/local/bin/ruby
|
2
|
+
|
3
|
+
# Copyright 2007 Jay McGavren, jay@mcgavren.com.
|
4
|
+
#
|
5
|
+
# This file is part of Zyps.
|
6
|
+
#
|
7
|
+
# Zyps is free software; you can redistribute it and/or modify
|
8
|
+
# it under the terms of the GNU Lesser General Public License as published by
|
9
|
+
# the Free Software Foundation; either version 3 of the License, or
|
10
|
+
# (at your option) any later version.
|
11
|
+
#
|
12
|
+
# This program is distributed in the hope that it will be useful,
|
13
|
+
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
14
|
+
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
15
|
+
# GNU General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU Lesser General Public License
|
18
|
+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
19
|
+
|
20
|
+
|
21
|
+
begin
|
22
|
+
require 'optparse'
|
23
|
+
require 'zyps'
|
24
|
+
require 'zyps/views/trails'
|
25
|
+
require 'drb'
|
26
|
+
rescue LoadError
|
27
|
+
require 'optparse'
|
28
|
+
require 'rubygems'
|
29
|
+
require 'zyps'
|
30
|
+
require 'zyps/views/trails'
|
31
|
+
require 'drb'
|
32
|
+
end
|
33
|
+
|
34
|
+
|
35
|
+
#An Environment intended for serving over DRb.
|
36
|
+
class RemoteEnvironment < Environment
|
37
|
+
def initialize
|
38
|
+
super
|
39
|
+
@objects.extend DRbUndumped
|
40
|
+
@environmental_factors.extend DRbUndumped
|
41
|
+
end
|
42
|
+
def objects=(value)
|
43
|
+
@objects = value
|
44
|
+
@objects.extend DRbUndumped
|
45
|
+
end
|
46
|
+
def environmental_factors=(value)
|
47
|
+
@environmental_factors = value
|
48
|
+
@environmental_factors.extend DRbUndumped
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
#A server for a Zyps Environment.
|
54
|
+
class EnvironmentServer
|
55
|
+
|
56
|
+
#Port to open service on.
|
57
|
+
attr_accessor :port
|
58
|
+
#Maximum allowed number of objects.
|
59
|
+
attr_accessor :max_population
|
60
|
+
#View dimensions.
|
61
|
+
attr_accessor :view_width, :view_height
|
62
|
+
|
63
|
+
#Set up default values.
|
64
|
+
def initialize(port = nil, max_population = 25, fps = 60, view_width = 800, view_height = 600)
|
65
|
+
@port, @max_population, @fps, @view_width, @view_height = port, max_population, fps, view_width, view_height
|
66
|
+
end
|
67
|
+
|
68
|
+
#Create app window, game environment, view, and network service.
|
69
|
+
def main
|
70
|
+
|
71
|
+
#A clock to track frames to draw this second.
|
72
|
+
clock = Clock.new
|
73
|
+
|
74
|
+
#Create a window, and set GTK up to quit when it is closed.
|
75
|
+
window = Gtk::Window.new
|
76
|
+
window.signal_connect("delete_event") {false}
|
77
|
+
window.signal_connect("destroy") {Gtk.main_quit}
|
78
|
+
|
79
|
+
#Add view to window.
|
80
|
+
view = TrailsView.new(@view_width, @view_height)
|
81
|
+
window.add(view.canvas)
|
82
|
+
window.show_all
|
83
|
+
|
84
|
+
#Create environment.
|
85
|
+
environment = RemoteEnvironment.new
|
86
|
+
#Point view at environment.
|
87
|
+
environment.add_observer(view)
|
88
|
+
|
89
|
+
#Create thread to update environment.
|
90
|
+
update_thread = Thread.new do
|
91
|
+
begin
|
92
|
+
loop do
|
93
|
+
environment.interact
|
94
|
+
#Control population.
|
95
|
+
environment.objects.shift while environment.objects.length > @max_population
|
96
|
+
#Sleep for the rest of this frame.
|
97
|
+
delay = (1.0 / @fps) - clock.elapsed_time
|
98
|
+
sleep delay if delay > 0
|
99
|
+
end
|
100
|
+
rescue Exception => exception
|
101
|
+
puts exception, exception.backtrace
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
#Start a network service.
|
106
|
+
DRb.start_service(
|
107
|
+
@port ? "druby://localhost:#{@port}" : nil,
|
108
|
+
environment
|
109
|
+
)
|
110
|
+
#If port was automatically chosen, display our URI.
|
111
|
+
puts DRb.uri unless @port
|
112
|
+
|
113
|
+
#Activate the GUI.
|
114
|
+
Gtk.main
|
115
|
+
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
#Set attributes according to command-line arguments.
|
120
|
+
def process_options(arguments)
|
121
|
+
|
122
|
+
#Set up option parser.
|
123
|
+
options = OptionParser.new
|
124
|
+
|
125
|
+
#Define valid options.
|
126
|
+
options.on("-h", "--help", TrueClass, "Display program help.") {
|
127
|
+
puts options.help
|
128
|
+
exit
|
129
|
+
}
|
130
|
+
options.on(
|
131
|
+
"-m",
|
132
|
+
"--max-population [number]",
|
133
|
+
Integer,
|
134
|
+
"The maximum number of allowed game objects. 25 by default."
|
135
|
+
) {|value| @max_population = value}
|
136
|
+
options.on(
|
137
|
+
"-p",
|
138
|
+
"--port [number]",
|
139
|
+
Integer,
|
140
|
+
"Port number to run the server on. If not defined, next available one will be selected and the URI printed to STDOUT."
|
141
|
+
) {|value| @port = value}
|
142
|
+
options.on(
|
143
|
+
"-f",
|
144
|
+
"--fps [frames]",
|
145
|
+
Integer,
|
146
|
+
"Number of frames to draw per second."
|
147
|
+
) {|value| @fps = value}
|
148
|
+
options.on(
|
149
|
+
"--view-width [pixels]",
|
150
|
+
Integer,
|
151
|
+
"Window width. 800 by default."
|
152
|
+
) {|value| @view_width = value}
|
153
|
+
options.on(
|
154
|
+
"--view-height [pixels]",
|
155
|
+
Integer,
|
156
|
+
"Window height. 600 by default."
|
157
|
+
) {|value| @view_height = value}
|
158
|
+
|
159
|
+
#Parse the options, printing usage if parsing fails.
|
160
|
+
options.parse(arguments) rescue puts "#{$!}\nType '#{$0} --help' for valid options."
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
begin
|
169
|
+
#Create a server.
|
170
|
+
server = EnvironmentServer.new
|
171
|
+
#Parse the command line.
|
172
|
+
server.process_options(ARGV)
|
173
|
+
#Start the server.
|
174
|
+
server.main
|
175
|
+
rescue => exception
|
176
|
+
#Print error to STDERR and exit with an abnormal status.
|
177
|
+
abort "Error: " + exception.message + exception.backtrace.join("\n")
|
178
|
+
end
|
data/lib/zyps.rb
CHANGED
@@ -19,7 +19,6 @@
|
|
19
19
|
require 'observer'
|
20
20
|
|
21
21
|
|
22
|
-
|
23
22
|
#A virtual environment.
|
24
23
|
class Environment
|
25
24
|
|
@@ -47,17 +46,16 @@ class Environment
|
|
47
46
|
objects.each do |object|
|
48
47
|
|
49
48
|
#Move each object according to its vector.
|
50
|
-
object.
|
51
|
-
object.location.y += object.vector.y * elapsed_time
|
49
|
+
object.move(elapsed_time)
|
52
50
|
|
53
51
|
#Have all environmental factors interact with each object.
|
54
|
-
environmental_factors.each {|factor| factor.act(
|
52
|
+
environmental_factors.each {|factor| factor.act(object)}
|
55
53
|
|
56
54
|
#Have all creatures interact with each other.
|
57
55
|
if object.respond_to?(:act)
|
58
56
|
objects.each do |target|
|
59
57
|
next if target.equal?(object) #Ensure object does not act on itself.
|
60
|
-
object.act(
|
58
|
+
object.act(target)
|
61
59
|
end
|
62
60
|
end
|
63
61
|
|
@@ -78,6 +76,9 @@ end
|
|
78
76
|
#An object in the virtual environment.
|
79
77
|
class GameObject
|
80
78
|
|
79
|
+
#A universal identifier for the object.
|
80
|
+
#Needed for DRb transmission, etc.
|
81
|
+
attr_reader :identifier
|
81
82
|
#The object's Location in space.
|
82
83
|
attr_accessor :location
|
83
84
|
#A Color that will be used to draw the object.
|
@@ -91,9 +92,16 @@ class GameObject
|
|
91
92
|
|
92
93
|
def initialize (name = nil, location = Location.new, color = Color.new, vector = Vector.new, age = 0, tags = [])
|
93
94
|
@name, @location, @color, @vector, @tags = name, location, color, vector, tags
|
95
|
+
@identifier = rand(99999999) #TODO: Current setup won't necessarily be unique.
|
94
96
|
self.age = age
|
95
97
|
end
|
96
98
|
|
99
|
+
#Move according to vector over the given number of seconds.
|
100
|
+
def move (elapsed_time)
|
101
|
+
@location.x += @vector.x * elapsed_time
|
102
|
+
@location.y += @vector.y * elapsed_time
|
103
|
+
end
|
104
|
+
|
97
105
|
#Time since the object was created, in seconds.
|
98
106
|
def age; Time.new.to_f - @birth_time; end
|
99
107
|
def age=(age); @birth_time = Time.new.to_f - age; end
|
@@ -106,8 +114,8 @@ end
|
|
106
114
|
module Responsive
|
107
115
|
|
108
116
|
#Call Behavior.perform on each of the object's assigned Behaviors, with the object and a target as arguments.
|
109
|
-
def act(
|
110
|
-
behaviors.each {|behavior| behavior.perform(
|
117
|
+
def act(target)
|
118
|
+
behaviors.each {|behavior| behavior.perform(self, target)}
|
111
119
|
end
|
112
120
|
|
113
121
|
end
|
@@ -188,7 +196,7 @@ class Color
|
|
188
196
|
attr_accessor :red, :green, :blue
|
189
197
|
|
190
198
|
def initialize (red = 1, green = 1, blue = 1)
|
191
|
-
|
199
|
+
self.red, self.green, self.blue = red, green, blue
|
192
200
|
end
|
193
201
|
|
194
202
|
#Automatically constrains value to the range 0 - 1.
|
@@ -248,7 +256,6 @@ class Vector
|
|
248
256
|
def pitch=(degrees)
|
249
257
|
#Constrain degrees to 0 to 360.
|
250
258
|
value = degrees % 360
|
251
|
-
value += 360 if value < 0
|
252
259
|
#Store as radians internally.
|
253
260
|
@pitch = Utility.to_radians(value)
|
254
261
|
end
|
@@ -352,7 +359,21 @@ module Utility
|
|
352
359
|
value
|
353
360
|
end
|
354
361
|
|
355
|
-
|
356
|
-
|
357
|
-
|
362
|
+
#Given a normal and an angle, find the reflection angle.
|
363
|
+
def Utility.find_reflection_angle(normal, angle)
|
364
|
+
incidence_angle = normal - angle
|
365
|
+
reflection_angle = normal + incidence_angle
|
366
|
+
reflection_angle %= 360
|
367
|
+
reflection_angle
|
368
|
+
end
|
358
369
|
|
370
|
+
#Given a location and the upper left and lower right corners of a box, determine if the point is within the box.
|
371
|
+
def Utility.inside_box?(point, upper_left, lower_right)
|
372
|
+
return false if point.x < upper_left.x
|
373
|
+
return false if point.y < upper_left.y
|
374
|
+
return false if point.x > lower_right.x
|
375
|
+
return false if point.y > lower_right.y
|
376
|
+
true
|
377
|
+
end
|
378
|
+
|
379
|
+
end
|
data/lib/zyps/views/trails.rb
CHANGED
@@ -84,20 +84,20 @@ class TrailsView
|
|
84
84
|
|
85
85
|
#For each GameObject in the environment:
|
86
86
|
environment.objects.each do |object|
|
87
|
-
|
87
|
+
|
88
88
|
#Add the object's current location to the list.
|
89
|
-
@locations[object] << [object.location.x, object.location.y]
|
89
|
+
@locations[object.identifier] << [object.location.x, object.location.y]
|
90
90
|
#If the list is larger than the number of tail segments, delete the first position.
|
91
|
-
@locations[object].shift if @locations[object].length > @trail_length
|
91
|
+
@locations[object.identifier].shift if @locations[object.identifier].length > @trail_length
|
92
92
|
|
93
93
|
#For each location in this object's list:
|
94
|
-
@locations[object].each_with_index do |location, index|
|
94
|
+
@locations[object.identifier].each_with_index do |location, index|
|
95
95
|
|
96
96
|
#Skip first location.
|
97
97
|
next if index == 0
|
98
98
|
|
99
99
|
#Divide the current segment number by trail segment count to get the multiplier to use for brightness and width.
|
100
|
-
multiplier = index.to_f / @locations[object].length.to_f
|
100
|
+
multiplier = index.to_f / @locations[object.identifier].length.to_f
|
101
101
|
|
102
102
|
#Set the drawing color to use the object's colors, adjusted by the multiplier.
|
103
103
|
graphics_context.rgb_fg_color = Gdk::Color.new( #Don't use Gdk::GC.foreground= here, as that requires a color to be in the color map already.
|
@@ -115,7 +115,7 @@ class TrailsView
|
|
115
115
|
)
|
116
116
|
|
117
117
|
#Get previous location so we can draw a line from it.
|
118
|
-
previous_location = @locations[object][index - 1]
|
118
|
+
previous_location = @locations[object.identifier][index - 1]
|
119
119
|
|
120
120
|
#Draw a line with the current width from the prior location to the current location.
|
121
121
|
buffer.draw_line(
|
data/test/test_zyps.rb
CHANGED
@@ -20,11 +20,31 @@ require 'zyps'
|
|
20
20
|
require 'test/unit'
|
21
21
|
|
22
22
|
|
23
|
+
class TestGameObject < Test::Unit::TestCase
|
24
|
+
|
25
|
+
|
26
|
+
def test_move
|
27
|
+
#Set up moving object.
|
28
|
+
object = GameObject.new
|
29
|
+
object.location = Location.new(0, 0)
|
30
|
+
object.vector = Vector.new(1.4142, 45)
|
31
|
+
#Move for 1 second.
|
32
|
+
object.move(1)
|
33
|
+
#Check object moved to expected coordinates.
|
34
|
+
assert_in_delta(1, object.location.x, 0.001)
|
35
|
+
assert_in_delta(1, object.location.y, 0.001)
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
end
|
40
|
+
|
41
|
+
|
23
42
|
class TestCreature < Test::Unit::TestCase
|
24
43
|
|
25
44
|
|
26
45
|
def test_default_initialization
|
27
46
|
creature = Creature.new
|
47
|
+
assert_not_nil(creature.identifier)
|
28
48
|
assert_equal(0, creature.location.x)
|
29
49
|
assert_equal(0, creature.location.y)
|
30
50
|
assert_equal(1, creature.color.red)
|
@@ -36,6 +56,8 @@ class TestCreature < Test::Unit::TestCase
|
|
36
56
|
assert_in_delta(0, creature.age, 0.1)
|
37
57
|
assert_equal([], creature.tags)
|
38
58
|
assert_equal([], creature.behaviors)
|
59
|
+
#Identifiers should be unique.
|
60
|
+
assert_not_equal(creature.identifier, Creature.new.identifier)
|
39
61
|
end
|
40
62
|
|
41
63
|
|
@@ -98,7 +120,8 @@ class TestEnvironment < Test::Unit::TestCase
|
|
98
120
|
#Look for expected interactions (each should only occur once).
|
99
121
|
assert(@interactions.find_all{|i| i == "2 targeting 1"}.length == 1)
|
100
122
|
assert(@interactions.find_all{|i| i == "1 targeting 2"}.length == 1)
|
101
|
-
|
123
|
+
assert(@interactions.find_all{|i| i == "1 targeting 1"}.length == 0)
|
124
|
+
assert(@interactions.find_all{|i| i == "2 targeting 2"}.length == 0)
|
102
125
|
|
103
126
|
end
|
104
127
|
|
@@ -305,6 +328,32 @@ class TestUtility < Test::Unit::TestCase
|
|
305
328
|
assert_in_delta(1.4142, Utility.find_distance(origin, Location.new(-1,-1)), 0.001)
|
306
329
|
assert_in_delta(1.4142, Utility.find_distance(origin, Location.new(1,-1)), 0.001)
|
307
330
|
end
|
331
|
+
|
332
|
+
|
333
|
+
def test_find_reflection_angle
|
334
|
+
assert_equal(210, Utility.find_reflection_angle(0, 150))
|
335
|
+
assert_equal(330, Utility.find_reflection_angle(0, 30))
|
336
|
+
assert_equal(150, Utility.find_reflection_angle(90, 30))
|
337
|
+
assert_equal(210, Utility.find_reflection_angle(90, 330))
|
338
|
+
assert_equal(30, Utility.find_reflection_angle(180, 330))
|
339
|
+
assert_equal(150, Utility.find_reflection_angle(180, 210))
|
340
|
+
assert_equal(330, Utility.find_reflection_angle(270, 210))
|
341
|
+
assert_equal(30, Utility.find_reflection_angle(270, 150))
|
342
|
+
end
|
308
343
|
|
309
344
|
|
310
|
-
|
345
|
+
def test_inside_box?
|
346
|
+
#Too far left.
|
347
|
+
assert(! Utility.inside_box?(Location.new(1, 3), Location.new(2, 2), Location.new(4, 4)))
|
348
|
+
#Too far right.
|
349
|
+
assert(! Utility.inside_box?(Location.new(5, 3), Location.new(2, 2), Location.new(4, 4)))
|
350
|
+
#Too far up.
|
351
|
+
assert(! Utility.inside_box?(Location.new(3, 1), Location.new(2, 2), Location.new(4, 4)))
|
352
|
+
#Too far down.
|
353
|
+
assert(! Utility.inside_box?(Location.new(3, 5), Location.new(2, 2), Location.new(4, 4)))
|
354
|
+
#Inside.
|
355
|
+
assert(Utility.inside_box?(Location.new(3, 3), Location.new(2, 2), Location.new(4, 4)))
|
356
|
+
end
|
357
|
+
|
358
|
+
|
359
|
+
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.
|
7
|
-
date: 2007-
|
6
|
+
version: 0.2.1
|
7
|
+
date: 2007-08-06 00:00:00 -07:00
|
8
8
|
summary: A simulation/game with autonomous creatures
|
9
9
|
require_paths:
|
10
10
|
- lib
|
@@ -34,6 +34,7 @@ files:
|
|
34
34
|
- README.txt
|
35
35
|
- bin/zyps
|
36
36
|
- bin/zyps_demo
|
37
|
+
- bin/zyps_server
|
37
38
|
- lib/zyps
|
38
39
|
- lib/zyps/views
|
39
40
|
- lib/zyps/views/trails.rb
|
@@ -53,6 +54,7 @@ extra_rdoc_files:
|
|
53
54
|
executables:
|
54
55
|
- zyps
|
55
56
|
- zyps_demo
|
57
|
+
- zyps_server
|
56
58
|
extensions: []
|
57
59
|
|
58
60
|
requirements:
|