#!/usr/local/bin/ruby # Copyright 2007 Jay McGavren, jay@mcgavren.com. # # This file is part of Zyps. # # Zyps is free software; you can redistribute it and/or modify # it under the terms of the GNU Lesser General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU Lesser General Public License # along with this program. If not, see . gems_loaded = false begin require 'optparse' require 'zyps' require 'zyps/actions' require 'zyps/conditions' require 'zyps/environmental_factors' require 'zyps/remote' require 'zyps/views/trails' rescue LoadError if gems_loaded == false require 'rubygems' gems_loaded = true retry else raise end end include Zyps DEFAULT_VIEW_WIDTH = 800 DEFAULT_VIEW_HEIGHT = 600 DEFAULT_MAX_SPEED = 200 DEFAULT_MAX_POPULATION = 100 DEFAULT_FPS = 60 class Application #Port to open service on. attr_accessor :uri #Maximum allowed number of objects. attr_accessor :max_population #View dimensions. attr_accessor :view_width, :view_height #Create app window, game environment, and view. #Set up default values. def initialize( uri = nil, view_width = DEFAULT_VIEW_WIDTH, view_height = DEFAULT_VIEW_HEIGHT, fps = DEFAULT_FPS, max_population = DEFAULT_MAX_POPULATION, max_speed = DEFAULT_MAX_SPEED, enclosure = true ) @uri, @view_width, @view_height, @fps, @max_population, @max_speed, @enclosure = uri, view_width, view_height, fps, max_population, max_speed, enclosure #Create a window, and set GTK up to quit when it is closed. window = Gtk::Window.new window.resizable = false window.signal_connect("delete_event") {false} window.signal_connect("destroy") {Gtk.main_quit} #Create environment. @environment = Environment.new #Set up controls. #Also initializes @view. window.add(create_controls) #Show all widgets. window.show_all #Point view at environment. @environment.add_observer(@view) end def main #Keep objects on screen. if @enclosure enclosure = Enclosure.new() enclosure.left = 0 enclosure.bottom = 0 enclosure.top = @view_height enclosure.right = @view_width @environment.environmental_factors << enclosure end #Keep all objects under a certain speed. @environment.environmental_factors << SpeedLimit.new(@max_speed) if @max_speed #Limit population. @environment.environmental_factors << PopulationLimit.new(@environment, @max_population) if @max_population #Set up a creature generator. @generator = CreatureGenerator.new(@environment) #Create thread to update environment. thread = Thread.new do begin drawing_clock = Clock.new time_per_frame = 1.0 / @fps loop do @environment.interact #Determine how much time is left in this frame. time_left_in_frame = (time_per_frame) - drawing_clock.elapsed_time #Sleep for the remaining time. if time_left_in_frame > 0 sleep time_left_in_frame #Skip a frame if things are going too slow. else sleep time_per_frame end end rescue Exception => exception puts exception, exception.backtrace end end #Start a network service. if @uri server = EnvironmentServer.new(@environment, @uri) server.start #Disable file system access. $SAFE = 2 end #Activate the GUI. Gtk.main end #Create a view and controls. def create_controls(view_width = @view_width, view_height = @view_height, homogeneous = false, spacing = 0, expand = false, fill = false, padding = 0) #Create a container for the view and controls. interface = Gtk::HBox.new(homogeneous, spacing) #Add view to interface. @view = TrailsView.new(view_width, view_height) interface.pack_start(@view.canvas, expand, fill, padding) #When mouse button pressed, record location for use in release event handler. @view.canvas.add_events(Gdk::Event::BUTTON_PRESS_MASK) @view.canvas.signal_connect("button-press-event") do |canvas, event| @press_location = Location.new(event.x, event.y) end #Create a creature on button release. @view.canvas.add_events(Gdk::Event::BUTTON_RELEASE_MASK) @view.canvas.signal_connect("button-release-event") do |canvas, event| @release_location = Location.new(event.x, event.y) @generator.create_creature( :x => event.x, :y => event.y, :speed => Utility.find_distance(@press_location, @release_location) * 2, #Use distance dragged as speed. :pitch => Utility.find_angle(@press_location, @release_location), #Move in direction of drag. :accelerate => @accelerate_flag.active?, :turn => @turn_flag.active?, :approach => @approach_flag.active?, :flee => @flee_flag.active?, :push => @push_flag.active?, :pull => @pull_flag.active?, :breed => @breed_flag.active?, :eat => @eat_flag.active? ) end #Create a VBox for all controls. control_panel = Gtk::VBox.new(homogeneous, spacing) #Create a group for the actions. action_controls = Gtk::VBox.new(homogeneous, spacing) action_controls.pack_start(Gtk::Label.new("Actions"), expand, fill, padding) @accelerate_flag = Gtk::CheckButton.new("Accelerate") action_controls.pack_start(@accelerate_flag, expand, fill, padding) @turn_flag = Gtk::CheckButton.new("Turn") action_controls.pack_start(@turn_flag, expand, fill, padding) @approach_flag = Gtk::CheckButton.new("Approach") action_controls.pack_start(@approach_flag, expand, fill, padding) @flee_flag = Gtk::CheckButton.new("Flee") action_controls.pack_start(@flee_flag, expand, fill, padding) @push_flag = Gtk::CheckButton.new("Push") action_controls.pack_start(@push_flag, expand, fill, padding) @pull_flag = Gtk::CheckButton.new("Pull") action_controls.pack_start(@pull_flag, expand, fill, padding) @breed_flag = Gtk::CheckButton.new("Breed") action_controls.pack_start(@breed_flag, expand, fill, padding) @eat_flag = Gtk::CheckButton.new("Eat") action_controls.pack_start(@eat_flag, expand, fill, padding) #Add the action controls to the panel. control_panel.pack_start(action_controls, expand, fill, padding) #Create a group for environment controls. environment_controls = Gtk::VBox.new(homogeneous, spacing) environment_controls.pack_start(Gtk::Label.new("Environment"), expand, fill, padding) @clear_button = Gtk::Button.new("Clear") @clear_button.signal_connect("clicked") { @environment.objects = [] } environment_controls.pack_start(@clear_button, expand, fill, padding) #Add the environment controls to the panel. control_panel.pack_start(environment_controls, expand, fill, padding) #Add the control panel to the interface. interface.pack_start(control_panel, expand, fill, padding) interface end #Set attributes according to command-line arguments. def process_options(arguments) #Set up option parser. options = OptionParser.new #Define valid options. options.on("-h", "--help", TrueClass, "Display program help.") { puts options.help exit } options.on( "-m", "--max-population [number]", Integer, "The maximum number of allowed game objects. #{DEFAULT_MAX_POPULATION} by default." ) {|value| @max_population = value} options.on( "-s", "--max-speed [number]", Integer, "The fastest an object can go. #{DEFAULT_MAX_SPEED ? DEFAULT_MAX_SPEED : 'No limit'} by default." ) {|value| @max_speed = value} options.on( "-n", "--no-enclosure", "Disables the barrier that normally keeps objects on the screen." ) {|value| @enclosure = false} options.on( "-u", "--uri [uri]", String, "dRuby URI to run the server on. If not defined, one will be selected and printed to STDOUT." ) {|value| @uri = value} options.on( "-f", "--fps [frames]", Integer, "Number of frames to draw per second. #{DEFAULT_FPS} by default." ) {|value| @fps = value} options.on( "--view-width [pixels]", Integer, "Window width. #{DEFAULT_VIEW_WIDTH} by default." ) {|value| @view_width = value} options.on( "--view-height [pixels]", Integer, "Window height. #{DEFAULT_VIEW_HEIGHT} by default." ) {|value| @view_height = value} #Parse the options, printing usage if parsing fails. options.parse(arguments) rescue puts "#{$!}\nType '#{$0} --help' for valid options." end end class CreatureGenerator #Environment creatures will be added to. attr_accessor :environment #Default required proximity for actions. attr_accessor :action_proximity #Speed of new AccelerateActions. attr_accessor :acceleration_rate #Rate of new TurnActions. attr_accessor :turn_rate #Turn rate of new ApproachActions. attr_accessor :approach_turn_rate #Turn rate of new FleeActions. attr_accessor :flee_turn_rate #Strength of new PullActions. attr_accessor :pull_strength #Strength of new PushActions. attr_accessor :push_strength def initialize(environment) @environment = environment #Set up defaults for attributes. @action_proximity = 200 @acceleration_rate = 1 @turn_rate = 90 @approach_turn_rate = 720 @flee_turn_rate = @approach_turn_rate @pull_strength = 100 @push_strength = @pull_strength @breed_rate = 10 end #Create a creature and add it to the environment. def create_creature(options = {}) options = { :x => 0, :y => 0, :speed => 1, :pitch => 0, :action_proximity => @action_proximity, :accelerate => false, :turn => false, :approach => false, :flee => false, :push => false, :pull => false, :breed => false, :eat => false, }.merge(options) #Create a creature. creature = Creature.new( '', Location.new(options[:x], options[:y]), Color.new, Vector.new(options[:speed], options[:pitch]), 0, 5 ) #Set up actions and merge colors according to selected behaviors. color = Color.new(0.25, 0.25, 0.25) if options[:accelerate] color.red += 0.25 color.green += 0.25 color.blue += 0.25 creature.behaviors << create_behavior(:actions => [AccelerateAction.new(@acceleration_rate)]) end if options[:turn] color.blue += 1 creature.behaviors << create_behavior(:actions => [TurnAction.new(@turn_rate)]) end if options[:approach] color.red += 1 creature.behaviors << create_behavior(:actions => [ApproachAction.new(@approach_turn_rate, creature.vector)]) end if options[:flee] color.red += 0.5; color.green += 0.5 #Yellow. creature.behaviors << create_behavior(:actions => [FleeAction.new(@flee_turn_rate, creature.vector)]) end if options[:push] color.red += 0.5; color.blue += 0.5 #Purple. creature.behaviors << create_behavior(:actions => [PushAction.new(@push_strength)]) end if options[:pull] color.blue += 0.75; color.green += 0.75 #Aqua. creature.behaviors << create_behavior(:actions => [PullAction.new(@pull_strength)]) end if options[:breed] color.green -= 0.1 #Make a bit redder. color.blue -= 0.1 creature.behaviors << create_behavior( :actions => [BreedAction.new(@environment, @breed_rate)], :conditions => [CollisionCondition.new] #The default ProximityCondition won't do. ) end if options[:eat] color.green += 1 creature.behaviors << create_behavior( :actions => [EatAction.new(@environment)], :conditions => [CollisionCondition.new] #The default ProximityCondition won't do. ) end creature.color = color @environment.objects << creature end def create_behavior(options = {}) options = { :actions => [], :conditions => [ProximityCondition.new(@action_proximity)], }.merge(options) behavior = Behavior.new behavior.actions = options[:actions] behavior.conditions = options[:conditions] behavior end end begin #Create a server. application = Application.new #Parse the command line. application.process_options(ARGV) #Start the server. application.main rescue => exception #Print error to STDERR and exit with an abnormal status. abort "Error: " + exception.message + exception.backtrace.join("\n") end