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
         |