BASH Tetris Part 2

Jarret B

Well-Known Member
Staff member
Joined
May 22, 2017
Messages
344
Reaction score
377
Credits
11,920
In my previous article, I covered the main Tetris script section. In this article, I will cover the functions or at least the important ones. Some are kind of basic and need no explanation.

If you want to see the script, look at the Attachments at the end of the article in 'Tetris for BASH'. It is saved as a '.txt' file and just needs to be renamed to '.sh'. You can leave it as a '.txt' or '.sh' file, but just make it executable and you should be able to run it no matter the extension.

Now, on to the functions.

puts()

This function is called often from other functions. It adds to data being sent to the variable 'screen_buffer'.

The variable 'scree_buffer' contains the screen itself. Every cycle, controlled by the Interrupts, the screen is refreshed from the variable.

xyprint()

Here, we move the cursor to a specific line and column. The line value is stored in '${2}' while the column value is in '${1}'. The 'H' is required and the text to be printed is in the variable '${3}'.

The '\033' is an ANSI escape method to pass the information to be processed. You can play with the command by the example 'printf "\033[10;11HLinux.org". In a terminal, the text 'Linux.org' will be printed in line 10 and column 11.

The function call the function 'puts()' and adds the escape code to the variable 'screen_buffer'.

show_cursor()

To show the cursor in a terminal is a simple ANSI escape sequence.

The command is 'echo -ne "\033[?25h"'. If the cursor is 'invisible', then this command will cause it to reappear. Of course, this won't work if you change the color or shape of the cursor.

The '-ne' parameters are handled as, '-n' is to not output the trailing newline while the '-e' will enable the interpretation of backslash esacapes.

hide_cursor()

For games, it can be beneficial to hide the cursor so it isn't a distraction to the player.

The command to hide the cursor is 'echo -ne "\033[?25l"'.

Even though the cursor is hidden, you can still type other commands.

set_fg()

The first line in the function tests the value of '$no_color'. If the value is 'true', then it continues on the line and 'returns' to the calling line. So, if we have pressed 'c' to set the game with no color, or 'no_color' is 'true', then we don't process the rest of the function.

If we are using color, 'no_color' is 'false', then we execute the line 'puts "\033[3${1}m"'. Here, the variable '${1}' is the variable of the color we want, such as 'CYAN'. The value is placed into the variable 'screen_buffer'.

If you want to type it into the terminal, you need to include text, such as 'printf "\033[33mJarret W. Buse"'.

set_bg()

This is kind of the opposite of the function 'set_fg'. We set the background color of the blocks and other items we are placing on the screen.

The first line is the same as 'set_fg'.

The second line is the same, except instead of '3' to signify the foreground in the ANSI escape, we use '4' for the background.

reset_colors()

In this function, we set the colors back to the defaults, or normal.

By signifying a '0', we reset the colors to normal.

set_bold()

We can use the escape codes to set text to be bold. In this case it is '1m'.

Just for information:

2m Faint
3m Italic
4m Underline


There are others, but these can be useful, even if they aren't used in Tetris.

redraw_playfield()

This function is a good example of a 'for' loops as well as BASH arrays. Keep in mind that BASH arrays are single-dimensional. So, to use an array to manage two dimensions, you need to get a little crafty.

The array used is 'a'. The array is from '11' to 'xy'. The first line consists of '11' to 'x1'. The last number (y) is the line, in this case '1'. The second column in the first line is '21', and the third is '31'.

The first number in the array numbering of the column (x). This may seem a little confusing at first, but once you get used to it, it can work nicely.

So, in the function, it goes through the array and changes the foreground and background color, prints the proper '$filled_cell' value, and resets the color. If the array is a '-1', or empty of a piece, it places an 'empty_cell' value. The values are placed into the 'puts()' function and stored in the 'screen_buffer'.

Depending on if the color is true or false, the cell is a full block or '[]'. The background, or 'empty_cell' is '.'. Depending on your taste, you can edit these.

update_score()

The function increments the 'lines_completed' and increments the 'score'. If you get more 'lines_completed', then you get more points. The number of lines completed is sent in the variable '$1'.

The 'score' is compared to the 'LEVEL_UP*level' and the level is incremented if you reach a new level. At this point, the Interrupt is killed so no update is made to the screen.

Next, we set the font to bold, and the 'SCORE_COLOR'. Once the colors are set, we can print out the lines for the 'Lines Completed', 'Level', and 'Score'. The scores are placed in the proper location using the function 'xyprint'.

Finally, we reset the colors to normal.

toggle_help()

The function works off the user pressing 'h' to toggle the 'help' information on and off.

This is not a difficult function, it either prints the appropriate text or replaces them with spaces. It then uses the function 'xyprint' to place the necessary text in place.

draw_piece()

The function uses five arguments sent from the calling function. For '$1', it holds the 'x' value, and '$2' holds the 'y' value. The next piece displayed on the screen is usually the one being drawn, whether on the player's board or in the area to show the next piece. When the 'Next Piece' is drawn, it already knows the 'X' and 'Y' positions. The variable '$3' is the type of block, in which there are 7 pieces to choose from in the array 'piece'. For the variable '$4', it holds the rotation of the piece. Originally, the rotation was a randomized position. We'll get into that later in the function 'get_random_next'. The variable '$5' is used to hold the content of the character position. The character can be a solid block or bracket.

The function goes through the coordinates in the 'piece' to draw the piece as a whole.

draw_next()

The first line checks that the variable 'next_on' is 'FALSE', or '-1' and exits the function. This means that during gameplay, the user presses 'n' to toggle the view of the next piece.

If 'next_on' is 'TRUE', then the next piece is shown on the screen and the second line is processed. The second line calls the function 'draw_piece' and passes it the appropriate 5 parameters.

clear_next()

This function is a little strange. After moving the 'Next Piece' to the board, the old piece needs to be cleared.

To do this, we simply redraw the piece that was there, but add '//?/ ' to the variable. There is a trailing space in the addition. The added slashes and question marks cause the text to be removed by spaces, which is the trailing value. If you replaced the trailing spaces with dashes, then the text would be replaced with dashes.

show_next()

This function just calls other functions to set the foreground (set_fg) and background (set_bg) colors of the next piece.

It then calls the function to draw the next piece (draw_next) with the appropriate piece. The last line in the function resets the colors (reset_colors).

toggle_next()

The function is a case statement to toggle on or off the next piece.

The toggle key is the 'n' key.

draw_current()

Calls the function 'draw_piece' to draw the current piece on the screen.

All five parameters are here to call the other function. Notice that the 'x' value is multiplied by 2 since each character of the blocks is two characters wider.

show_current()

Similar to 'show_next', this function calls other functions.

The function is exactly as 'show_next', it just calls functions for the active piece and not the 'next_piece'.

clear_current()

Calls the function 'draw_current' to draw the current piece.

The difference is that it draws the current piece with empty cells, so the piece disappears or is cleared.

new_piece_location_ok()

There are two parameters sent to the function. The parameters sent are the 'x' and 'y' coordinates of the current piece.

The values are tested if the piece is out of the playing field or if the spot is already occupied. If so, a value of '1' is returned. The returned value is managed by the calling function.

get_random_next()

With this function, we manage quite a bit of moving things around.

First, we move the piece in 'next_piece' to the 'current_piece' and all its requirements. The requirements are the color, rotation, etc.

Second, we 'clear_next', which empties the area where the 'next_piece' was displayed.

Lastly, we perform getting randomized values for the 'next_piece'. These include the piece, color, rotation, etc.

draw_border()

The function is a simple one, it draws the border of the playing field.

Again, like drawing the pieces, each character is two characters wide. If not, the playing area would be quite narrow.

toggle_color()

If the user should toggle the color on or off, the function changes the variable to 'true' or 'false'.

Once the color variable has changed, the function calls every other function that will draw or show something on the screen. By doing this, the whole screen will appear to toggle between color or black and white.

init()

The function sets up initial variables for starting the game.

The 'playfield' is set up with empty cell (-1) values.

We clear the screen, hide the cursor, and get a 'random_next' piece, which will not create a 'current_piece' since the 'next_piece' was empty. So, the function is called a second time to move the 'next_piece' to the 'current_piece' and pick a new 'next_piece'.

Finally, the script will toggle the color, which in essence, draws the screen.

ticker()

Here is the timing of the games. This is an important part.

The first thing to check is if 'SIGUSR2' has been trapped. 'SIGUSR2' is interrupted when the user presses 'q' to quit, or the game is over.

If the issue is not 'SIGUSR2', then we can check the 'SIGUSR1' interrupt. This interrupt is a time delay that forces the piece to move down one block. If the player has caused a level-up, then the delay is shortened to make gameplay a little more difficult.

When 'SIGUSR1' is trapped, the key for 'down' is processed to make the piece go down one. There is then a delay to allow the piece to be redrawn and the function ends.

reader()

The first thing, since we are trapping the interrupts, is to check for 'SIGUSR2' or a game over.

'SIGUSR1' is ignored since we are reading from the 'controller', which is the keyboard. We do not need this function to process the delay and make the piece move down, that is handled in 'ticker'.

The script then declares local variables, ones that only exist in the function.

Lastly, we declare an associative array (-A) where a key is associated with a command. For example, the right arrow is associated with the command '$RIGHT'. Later, we will see that the variable '$RIGHT' is set to 'cmd_right', this is in the 'controller' function.

The script then goes on to input a key from the keyboard. Remember that the interrupts are still active, especially 'SIGUSR1'.

flatten_playfield()

The function is called when a piece is dropped. After a piece is dropped, it goes as low on the playing field as it can without requiring an interrupt to drop one layer at a time.

The piece is dropped and the playing field is updated.

process_complete_lines()

This function is self-explanatory. The playing field is checked if any lines are filled with blocks of pieces.

If an empty cell is found, then the process is stopped for that layer.

Should a completed line be filled, the lines are moved down one, the variable 'complete_lines' is incremented by one, and the cell values are replaced with a '-1'.

Finally, the function is ended by returning the number of 'complete_lines' to the calling function.

process_fallen_piece()

The function is called when a piece is dropped. It first calls the function 'flatten_playfield', then 'process_complete_lines' and 'update_score'.

Once all of this has been accomplished, it then calls the function 'redraw_playfield' to update the screen and remove the completed lines, and show the updated score.

move_piece()

The function checks that the piece can be moved and moves it if it can move into the new place.

If the piece cannot move, then it finalizes the placement and gets the next piece.

cmd_right(), cmd_left(), cmd_rotate(), cmd_down(), cmd_drop(), cmd_quit()

These are all self-explanatory. These are the functions that are called when a specific key is pressed on the keyboard.

Each one is managed in a specific way, which corresponds to the key pressed by the player.

controller()

Here we have the heart of the game.

The commands are set and the game is initialized (init). We then run through a loop until the game is over. Keys are input and the specific commands are executed.

Keep in mind that the interrupts 'SIGUSR1' and 'SIGUSR2' are running in the background. The system is 'multi-tasking' by running the function 'controller' and watching the interrupts at the same time.

screensize()

The function was added by me to make sure the viewable size of the terminal would fit the playing field.

It can be a nuisance to open a game, and then have to resize the window manually to play the game properly.

Conclusion

There are a lot of functions in the game which all work together. Some functions call other functions, which calls other functions. It can get quite convoluted.

Learning these basics can help you make games similar to those in the 80's. Tetris is a good start to learn some of the basics.
 


It's funny though: the last change made to this game was made 11 years ago.
 

Members online


Top