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 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
- To see the demo, at a command line, type:
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
- begin
31
+ #Generates new creatures and places them in an environment.
32
+ class Generator
32
33
 
33
- puts "This is just a placeholder for the final application."
34
- puts "To see a demonstration of the library, please run 'zyps_demo'."
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
- #Run the demos.
525
- Demo.new.main
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.location.x += object.vector.x * elapsed_time
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(factor, object)}
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(object, target)
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(subject, target)
110
- behaviors.each {|behavior| behavior.perform(subject, target)}
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
- @red, @green, @blue = red, green, blue
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
- end
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
@@ -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
- #TODO: Ensure creatures don't target selves.
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
- end
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.1.1
7
- date: 2007-07-25 00:00:00 -07:00
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: