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.
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:
- flock(): move towards the average direction of peers
- avoid(): when peers are within a certain range, move away from them
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.
isquared.nl rss (atom)