📜 ⬆️ ⬇️

Learn bash scripts, write Quadronix

On Habré there are already a lot of articles about bash games, these are Chess , Xonix , Sokoban , Battleship and even Pseudo-3D Shooter . But in all these games, the control takes place using the keyboard. We will go ahead and write the game on bash, the control in which will be carried out using the mouse. And at the same time we will analyze how to make the game resistant to resizing the terminal. So, let's write the game Quadronix.




')
I saw the Quadronix game for the first time with my brother on the phone several years ago, and I immediately liked playing it. But since my brother almost did not give me my phone, I, without hesitation, implemented a clone of the game in the form of a Java applet, while I was studying Java.

And now, seeing that processor emulators and 3D games are already being done on bash, I realized that my Xonix implementation on bash is already past century, and I need to move on. And I thought that implementing Quadronix on bash would be a good warm-up for the brain.

The rules of the game are simple. It is necessary to find rectangles, all four vertices of which are of the same color. In order to remove such a rectangle, you need to click on the diagonally opposite vertices. At the same time, the number of points added per rectangle is proportional to its area. And in the place of the destroyed rectangle, new squares of random color will appear. The game is played for a while, and to make it more interesting to play, then the more points, the faster the time decreases. Also, if a player makes a mistake with the choice of a rectangle, a penalty is deducted from the rest of the time. Finally, in order to cancel the selection of an incorrectly marked vertex, click on it again.

It is clear that it is not interesting to play such a game without a mouse. Well, we realize the mouse. We read man console_codes and there we find that the escape sequence ESC [ ? 9 h ESC [ ? 9 h includes mouse tracking mode,
and the escape sequence ESC [ ? 9 l ESC [ ? 9 l turns this mode off.
When tracking mode is turned on, when the state of the mouse changes, control sequences describing the state of the mouse will be written to the input stream of the console. They have the format ESC [ M bxy , where in b will be information about the pressed button and modifier keys, and in x and y - information about the coordinates of the mouse. The symbol b is not interesting to us. And to get the coordinates of the mouse, you need to subtract 32 from both x and y .

But not the easiest bash task to do is get the character code. The simplest, in my opinion, the team will be the following, obtained through experiments:
 LC_ALL=C printf -v code '%d' "'$data" 

Here you should pay attention to the single unmatched quotation mark before the dollar sign. It simply means that it will not be the next character displayed, but its code. And LC_ALL=C requires that a character with a code greater than 127 is interpreted by itself, and not as part of a multibyte character.

So, we will write the following script.
 #!/bin/bash declare -i mouseX declare -i mouseY declare -i mouseButton declare -r ESC_CODE=$'\e' declare -r EXIT_CODE='x' printMouseInfo() { echo button=$mouseButton column=$mouseX row=$mouseY } readMouse() { local mouseButtonData local mouseXData local mouseYData read -r -s -n 1 -t 1 mouseButtonData read -r -s -n 1 -t 1 mouseXData read -r -s -n 1 -t 1 mouseYData local -i mouseButtonCode local -i mouseXCode local -i mouseYCode LC_ALL=C printf -v mouseButtonCode '%d' "'$mouseButtonData" LC_ALL=C printf -v mouseXCode '%d' "'$mouseXData" LC_ALL=C printf -v mouseYCode '%d' "'$mouseYData" ((mouseButton = mouseButtonCode)) ((mouseX = mouseXCode - 32)) ((mouseY = mouseYCode - 32)) } declare key echo -ne "\e[?9h" while true; do key="" read -r -s -t 1 -n 1 key case "$key" in $EXIT_CODE) break;; $ESC_CODE) read -r -s -t 1 -n 1 key if [[ "$key" == '[' ]]; then read -r -s -t 1 -n 1 key if [[ "$key" == "M" ]]; then readMouse printMouseInfo fi fi;; esac done echo -ne "\e[?9l" 


Not to say that this is the right way, but it works:
 $ ./mouse.sh button=0 column=46 row=17 button=0 column=61 row=19 button=0 column=64 row=15 button=0 column=59 row=11 button=0 column=43 row=9 button=0 column=36 row=10 button=0 column=42 row=17 button=0 column=63 row=23 button=0 column=75 row=22 button=0 column=91 row=19 $ 

If you use the escape sequence ESC [ ? 1000 h ESC [ ? 1000 h , then you can get information about clicking and releasing the mouse buttons.

Now let's look at how to make the script less frail by user actions using signals.

The first thing you want to do is call the reset command, if you press Ctrl + C while the script is running. This is very convenient when developing, since we use colors in the game and suppress user input, and if the script is interrupted, until you randomly reset , the terminal will be in a state completely unsuitable for work.

In order to execute some code when receiving a signal, the trap command is used:
 trap   

The possible values ​​of the parameter can be found by typing trap -l . If the parameter is not present, the default action will be set. If you specify EXIT as the parameter, the specified command will be executed upon completion of the script, and this is what we need. We write:

 function initApplication() { stty -echo echo -ne $HIDE_CURSOR_CODE trap finishApplication EXIT ... } function finishApplication() { trap EXIT reset } initApplication runApplication finishApplication 


The second case when we need signal handlers is a window resizing. If, for example, open the window, or vice versa, all our beautiful graphics crawl, but do not really want to. To find out that the size of the terminal window has changed, you can use the SIGWINCH signal:
 function repaint() { LINES=`tput lines` COLUMNS=`tput cols` mapXPosition=$(((COLUMNS - CELL_WIDTH * MAP_WIDTH) / 2 + 1)) mapYPosition=$(((LINES - CELL_HEIGHT * MAP_HEIGHT) / 2 + 1)) timerXPosition=$((MAP_WIDTH * CELL_WIDTH + mapXPosition + 6)) timerYPosition=$((mapYPosition)) echo -ne "\e[0m" clear drawMap drawHeader drawFooter ((isInvalidated = 0)) } function initApplication() { ... trap "((isInvalidated = 1))" SIGWINCH } function runGame() { local key ... while true; do if ((isInvalidated)); then repaint fi ... key="" ... case "$key" in $NEW_GAME_CODE) continue 2;; $EXIT_CODE) break 2;; $ESC_CODE) ... esac ... done } 


I note that if you specify DEBUG as a , then the specified command will be executed after each script command, sometimes it is useful when debugging.

Well, now the link to the script code: quadronix.sh .

And finally, I’ll say that man bash , man console_codes and ABS can be read endlessly, and each time discovering newer and newer facets of bash programming.

Source: https://habr.com/ru/post/131921/


All Articles