1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197
# Boids - A Shoes Application # # Author : Wally Glutton - http://stungeye.com # Summary : A hungry swarm indeed! # # Notes : My home-rolled Vector class appears to be quicker than the matrix library Vectors. # Boid Algo : http://www.vergenet.net/~conrad/boids/pseudocode.html # # 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 : http://creativecommons.org/licenses/by-sa/2.5/ca/ srand NUM_BOIDS = 30 NUM_FOODSTUFF = 6 boids = [] foodstuff = [] Shoes.app do stroke rgb(0x30, 0x30, 0x05, 0.5) app = self NUM_BOIDS.times { |i| boids[i] = Boid.new rand * self.width, rand * self.height, random(-5, 5), random(-5, 5) } NUM_FOODSTUFF.times { |i| foodstuff[i] = Food.new app} animate(24) do clear do 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 self # Don't fly too far from home boid.apply_deltas boid.limit_speed boid.move boid.draw self, app foodstuff.each do |food| food.eaten? boid end end foodstuff.each do |food| food.draw self end end end end class Food RADIUS = 30 attr_reader :position def initialize app @app = app spawn end def spawn @size = RADIUS @position = VectorK.new rand * @app.width, rand * @app.height end def eaten? boid if @position.nearby? RADIUS, boid.position @size -= 1 end if @size < 0 spawn end end def draw slot @app.fill rgb(0xFF, 0x30, 0xFF, 0.4) slot.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 = 100 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 = 30 HUNTING_RADIUS = RADIUS*5 # Locate food within this radius HUNTUNG_DAMPER = 10 STAY_VISIBLE_DAMPER = 300 attr_reader :velocity, :position def initialize x, y, vx, vy @velocity = VectorK.new vx, vy @position = VectorK.new x, y @velocity_delta = VectorK.new 0, 0 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 slot mid_x = slot.width / 2 mid_y = slot.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 slot, app app.fill rgb(0x30, 0xFF, 0xFF, 0.5) slot.oval :left => @position.x, :top => @position.y, :radius => RADIUS, :center => true slot.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 def random min, max choice = (max - min > 0) ? (rand max - min) + min : 0; end
Refactorings
No refactoring yet !
You will need Shoes installed to run this sketch: http://code.whytheluckystiff.net/shoes/
I'm looking for speed optimizations and a reduction in code-size (but not at the expense of legibility).
Also, coming from a C/Javascript background, I'd appreciate suggestions that change my code to better follow the 'Ruby way'.
Tested with Shoes 0.r396, Windows XP.
Thank you kindly.