Pastie now auto-senses if line-wrap is a bad or good idea. Feedback?
## mark a section (Learn more)
# Hungry Boids v0.4 - A Shoes Application # # Author : Wally Glutton - http://stungeye.com # Summary : A hungry swarm indeed! # # Boid Algo : http://www.vergenet.net/~conrad/boids/pseudocode.html # Notes : My home-rolled Vector class appears to be quicker than the matrix library Vectors. # # Required : You must have Shoes installed to view the boids. # Get Shoes : http://code.whytheluckystiff.net/shoes/ # Learn Shoes : http://hackety.org/press/nks.html # # Code License : Creative Commons Attribution-Share Alike 2.5 # License URL : http://creativecommons.org/licenses/by-sa/2.5/ca/ # # Change Log : v0.1 - The initial swarm. # v0.2 - Replaced custom random method with (min..max).rand # - Mouse clicks add food. Careful not to add too much. # Magnus Adamsson v0.3 - Seems Shoes 0.r646 can no longer draw a zero radius circle # Wally Glutton v0.4 - No longer using a $app global to access Shoes methods within the Food and Boid classes. # - Moved class definitions above Shoes.app block as this was causing a problem for OSX builds. srand NUM_BOIDS = 25 NUM_FOODSTUFF = 5 boids = [] foodstuff = [] class Food RADIUS = 30 attr_reader :position def initialize (app, x=(rand*app.width), y=(rand*app.height)) spawn x, y @app = app end def spawn (x=(rand*@app.width), y=(rand*@app.height)) @size = RADIUS @position = VectorK.new x, y end def eaten? boid if @position.nearby? RADIUS, boid.position @size -= 1 end if @size <= 0 spawn end end def draw @app.fill rgb(0xFF, 0x30, 0xFF, 0.4) @app.oval :left => @position.x, :top => @position.y, :radius => @size, :center => true end end class Boid RADIUS = 20 MAX_SPEED = 25 AVOID_RADIUS = RADIUS*3 # Avoid other boids within this radius AVOID_DAMPER = 50 ATTRACTION_RADIUS = RADIUS*8 # Gravitate to the centre of mass of boids within this radius ATTRACTION_DAMPER = 30 ALLIGNMENT_RADIUS = RADIUS*3 # Allign velocity with boids within this radius ALLIGNMENT_DAMPER = 50 HUNTING_RADIUS = RADIUS*5 # Locate food within this radius HUNTUNG_DAMPER = 10 STAY_VISIBLE_DAMPER = 450 attr_reader :velocity, :position def initialize x, y, vx, vy, app @velocity = VectorK.new vx, vy @position = VectorK.new x, y @velocity_delta = VectorK.new 0, 0 @app = app end def calculate_avoidance_delta boids boids.each do |other| if @position.nearby? AVOID_RADIUS, other.position @velocity_delta += (@position - other.position) / AVOID_DAMPER end end end def calculate_attraction_delta boids average_position = VectorK.new 0, 0 visible_boids = 0 boids.each do |other| if @position.nearby? ATTRACTION_RADIUS, other.position average_position += other.position visible_boids += 1 end end average_position /= visible_boids @velocity_delta += (average_position - @position) / ATTRACTION_DAMPER end def calculate_allignment_delta boids allignment_delta = VectorK.new 0, 0 visible_boids = 0 boids.each do |other| if @position.nearby? ALLIGNMENT_RADIUS, other.position allignment_delta += other.velocity visible_boids += 1 end end allignment_delta /= visible_boids @velocity_delta += allignment_delta / ALLIGNMENT_DAMPER end def calculate_hunting_delta foodstuff foodstuff.each do |food| if @position.nearby? HUNTING_RADIUS, food.position @velocity_delta += (food.position - @position) / HUNTUNG_DAMPER end end end def calculate_stay_visible_delta mid_x = @app.width / 2 mid_y = @app.height / 2 @velocity_delta -= (@position - VectorK.new(mid_x, mid_y)) / STAY_VISIBLE_DAMPER end def apply_deltas @velocity += @velocity_delta @velocity_delta = VectorK.new 0, 0 end def limit_speed if @velocity.r > MAX_SPEED @velocity /= @velocity.r # Create a unit vector @velocity *= MAX_SPEED # Scale to max speed end end def move @position += @velocity end def draw @app.fill rgb(0x30, 0xFF, 0xFF, 0.5) @app.oval :left => @position.x, :top => @position.y, :radius => RADIUS, :center => true @app.line @position.x, @position.y, (@position.x + @velocity.x), (@position.y + @velocity.y) end end class VectorK attr_reader :x, :y def initialize x, y @x = x @y = y end def nearby? threshold, a return false if a === self (distance a) < threshold end def distance a Math.sqrt((@x - a.x)**2 + (@y - a.y)**2) end def / a if (a != 0) VectorK.new(@x / a, @y / a) else self end end def + a VectorK.new(@x + a.x, @y + a.y) end def - a VectorK.new(@x - a.x, @y - a.y) end def * a VectorK.new(@x * a, @y * a) end def r Math.sqrt(@x * @x + @y * @y) end end Shoes.app do stroke rgb(0x30, 0x30, 0x05, 0.5) NUM_BOIDS.times { |i| boids[i] = Boid.new(rand * self.width, rand * self.height, (-15..15).rand, (-15..15).rand, self) } NUM_FOODSTUFF.times { |i| foodstuff[i] = Food.new(self) } click do |_,x,y| foodstuff.push Food.new(self, x, y) end animate(24) do clear do background rgb(0xFF, 0xFF, 0xFF) boids.each do |boid| boid.calculate_avoidance_delta boids # Avoid other boids boid.calculate_attraction_delta boids # Gravitate towards the centre-of-mass of nearby boids boid.calculate_allignment_delta boids # Allign velocity with nearby boids boid.calculate_hunting_delta foodstuff # Be on the lookout for food boid.calculate_stay_visible_delta # Don't fly too far from home boid.apply_deltas boid.limit_speed boid.move boid.draw foodstuff.each do |food| food.eaten? boid end end foodstuff.each do |food| food.draw end end end end
This paste will be private.
From the Design Piracy series on my blog: