#!/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 . begin require 'zyps' require 'zyps/actions' require 'zyps/conditions' require 'zyps/environmental_factors' require 'zyps/views/trails' rescue LoadError require 'rubygems' require 'zyps' require 'zyps/actions' require 'zyps/conditions' require 'zyps/environmental_factors' require 'zyps/views/trails' end class Application #Create app window, game environment, and view. def initialize(width, height) @width, @height = width, height @birth_rate = 1 @fps = 30.0 #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} #Set up controls. window.add(create_controls) #Show all widgets. window.show_all #Create environment. @environment = Environment.new #Point view at environment. @environment.add_observer(@view) end def main #Keep all objects within a boundary. enclosure = Enclosure.new enclosure.left = 0 enclosure.bottom = 0 enclosure.top = @height enclosure.right = @width @environment.environmental_factors << enclosure #Keep all objects under a certain speed. @environment.environmental_factors << SpeedLimit.new(100) #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 #Control population. @environment.objects.shift while @environment.objects.length > 25 #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 #Activate the GUI. Gtk.main end #Create a view and controls. def create_controls(width = @width, height = @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(width, 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?, :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) @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) #Add the control panel to the interface. interface.pack_start(control_panel, expand, fill, padding) interface 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 = 1000 @push_strength = @pull_strength end #Create a creature and add it to the environment. def create_creature(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, :eat => false, }.merge!(options) #Set up the creature's vector. vector = Vector.new(options[:speed], options[:pitch]) #Set up actions and merge colors according to selected behaviors. behavior = Behavior.new behavior.conditions << ProximityCondition.new(@action_proximity) color = Color.new(0.25, 0.25, 0.25) if options[:accelerate] color.red += 0.25 color.green += 0.25 color.blue += 0.25 behavior.actions << AccelerateAction.new(@acceleration_rate) end if options[:turn] color.blue += 1 behavior.actions << TurnAction.new(@turn_rate) end if options[:approach] color.red += 1 behavior.actions << ApproachAction.new(vector, @approach_turn_rate) end if options[:flee] color.red += 0.5; color.green += 0.5 #Yellow. behavior.actions << FleeAction.new(vector, @flee_turn_rate) end if options[:push] color.red += 0.5; color.blue += 0.5 #Purple. behavior.actions << PushAction.new(@push_strength) end if options[:pull] color.blue += 0.75; color.green += 0.75 #Aqua. behavior.actions << PullAction.new(@pull_strength) end #Create a creature. creature = Creature.new( '', Location.new(options[:x], options[:y]), color, vector, 0, 5 ) creature.behaviors << behavior #Special handling for eat action, which needs a CollisionCondition. if options[:eat] creature.color.green += 1 behavior = Behavior.new behavior.actions << EatAction.new(@environment) behavior.conditions << CollisionCondition.new creature.behaviors << behavior end @environment.objects << creature end end begin #The view width. WIDTH = 500 #The view height. HEIGHT = 600 #Run the application. application = Application.new(WIDTH, HEIGHT) application.main rescue => exception #Print error to STDERR and exit with an abnormal status. abort "Error: " + exception.message + exception.backtrace.join("\n") end