fi.sh - Boids-inspired flocking simulation in Bash


After squa.sh, I thought it was time for another abuse of the Bash shell.
A few weeks ago I wrote fi.sh, a simulation of flocking behaviour, like migrating birds or a school of fish, written in pure Bash and with ascii-art graphics, of course.

Fi.sh is inspired by the classic Boids algorithm by Craig Reynolds. But since I wrote this in a week when I was in the middle of nowhere in Andalucia, without any means of connecting to the Internet it only uses two behaviours, instead of Reynolds' three, still the results look pretty convincing.

Reynolds' original Boids
Craig Reynolds original Boids

Still screenshots don't do fi.sh much justice, I'll try to make a movie of it later, but below are two frames, a few timeslices apart, of fi.sh in action, to get an idea of the behaviour:

                                                6  
                                                  
                                                 
                                               7


              8 
                                        3
                                                                1
                                             5                    
                                                                   4
                                                                     
                                     0                                
                                     
                                    



                                                      2
            9                                           
                                                         
           


		
                                                     
                                                          
                                                           
                                            1                
                                                              
                                                               
                                              2                 
                                                                 
                                          6      4                
                                                                   
                    9 8                                             
                                                                     
                                                                      
                            5                       
                                                   
                        3                           
                                                     
                         7                            
                                                       
                                                        
                       0                                 
                                                      
                                                     

		

Isn't it great how you can make screenshots of these programs by just copy-pasting from your terminal to preformatted text in HTML? :P

Each 'agent' in fi.sh has two behaviours:

Each of these behaviours only considers 'peers' when they are visible for the current 'agent', that is, they are in a 180 degrees field of vision in the agent's current direction.

Both flock() and avoid() return a vector that represents the direction in which this behaviour wants to move.
Fi.sh does not use true vector arithmetic to avoid the use of square roots in Bash' integer-only arithmetic operators. Instead vectors are decomposed in separate x and y dimensions on wich all operations are done seperately.
The vectors of flock() and avoid() are then weighted and added, avoid() is twice as 'heavy' as flock(). After adding the resulting vector is simplified to a unit step in both the x and y direction. In code, this looks like this:

		       # for each fish
		        for ((fish = 0; fish < k_numfish; fish++))
		        do
		                # flocking behaviour
		                flock_vector=$(flock ${fish})
		
		                # avoidance behaviour
		                avoid_vector=$(avoid ${fish})
		
		                # flow vector (this should eventually be a food supply at some
		                # position, implemented as another behaviour, like feed() or so.)
		                flow_vector='0:0'
		
		                # decompose vectors in x/y elements, integrate behaviours
		                dx=$((${flow_vector/:*} + ${flock_vector/:*} + 2*${avoid_vector/:*}))
		                dy=$((${flow_vector/*:} + ${flock_vector/*:} + 2*${avoid_vector/*:}))

		                # redraw fish
                		upd_pos ${fish}
			done
		

This is actually almost the exact core of fi.sh, pretty simple, eh?
Of course most of the complexity is in the flock() and avoid() functions, but in the end the whole algorithm is pretty simple. Itnever stops to amaze me how complexity arises from simple rules, and how you can capture quite fluid and natural behaviour in a few hunderd lines of Bash script!

Currently the fi.sh algorithm uses a torodial space: it wraps around the edges of the terminal. Especially on larger screens it might be more appropiate to make the screen edges repel the Boids instead. This is something that still needs to be implemented.
Also, the different behaviours share a lot of common code, this should of course move to separate functions.

The latest version of fi.sh is available for download at http://isquared.nl/src/fi.sh
And you can expect me to write about it when significant updates are made to fi.sh.