0611073adbcf46f93b7d8bf059c109b8

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.

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 !

Your refactoring





Format Copy from initial code

or Cancel