There aren't many games written in a shell scripting language. This is one of them. I was inspired by the book Steve_Parker, Shell Scripting: Expert Recipes for Linux, Bash and more, where a source code for the Space Invaders game clone can be found. I've borrowed the idea on how to create a basic game cycle and written a clone of an old DOS game housenka.exe (I played this as a kid). Also an article from Pavel Tišnovský about text-mode games was another push that made me create my own version of the game using the capabilities of Bourne Again Shell and ANSI terminal.
Housenka means caterpillar in Czech and I believe it is not necessary to describe how to play it any further :)
I created the title screen in my other project – SHPaint and finalized it in ViM.
Prerequisities:
How to install (in CLI):
cd wget http://bruxy.regnet.cz/linux/housenka/housenka.sh chmod +x housenka.sh ./housenka.sh
The code of Housenka itself is not very well written for easy understanding. There are no function parameters, only global variable are used for storing actual state of program.
function move() { ( sleep 0.1; kill -ALRM $$ ) & ... do something ... } trap move ALRM move while : do # read 3 byte long cursor codes: read -rsn3 -d '' PRESS KEY=${PRESS:2} done
The first of all the signal alarm (ALRM) is trapped. Every time when script receives ALRM, the function "move" is called. There is a fork inside the move: ( sleep 0.1; kill -ALRM $$ ) &. It starts new process in the background which just waits 0.1 sec and then sends the ALRM to script itself ($$ means PID).
The rest of code continues in infinite while-loop. Where just a key press is read. The code of one cursor key-press has three bytes, so read automatically continues after reading of three bytes. Only third byte of them is useful for next program tasks, so KEY=${PRESS:2}. There is a constraint, if you press a key with just one byte code and then you press cursor, it is together 4 bytes, but only 3 bytes will be stores in $PRESS, and last byte will appear as first byte of next cycle. Then wrong values are passed to program and the keyboard handler will not work correct.
In fact, there are two infinite loops running concurrently. One is a key press reader and the second is the function move. So even if there is no key pressed the move still calls itself and perform other tasks like changing Housenka's position, checking collisions, score updating and calling next_level and game_over functions.
A function gen_food() is used for randomized position of edible (♠) and poisonous (♣) leaves. The leaves positions are stored in hash array $FOOD. Bash defines hashes with command: declare -A FOOD. As a key position of leave is used, in format $y;$x, actually same as the position ANSI code is used – \e[$y;${x}f.
The position of Housenka is stored in array $HOUSENKA. It is ordinary array, where each game cycle, the last item stored new position and first item is deleted or remains if the head eats leave
The detection of collisions provides function check_food(). It checks hash $FOOD – coordinates of last position in $HOUSENKA is used as a key. The wall collision is simple, it is just check of coordinates and edge coordinates detects wall break.
Housenka cannot bite herself, so there is also detection of self-collision:
last=${#HOUSENKA[@]} if [ $(echo ${HOUSENKA[@]} | tr ' ' '\n' | \ head -n $[last-2] | grep -c "^$HY;$HX$") -gt 0 ] ; then DEATH=3 fi
A variable $last contains number of array items, later it is used for getting all Housenka's coordinates instead of last one. Then content of all array ${HOUSENKA[@]} is send to grep, which check if the actual position of head is same as another coordinate stored in array. If the number is greater than zero, than Housenka bites her body.