Tetris in HTML5 for Noobs: Part 6 – The Game State Grid

In this chapter, we will perform a major restructuring of the code to allow drawing multiple tetriminos at the same time. We’re going to create a variable called the state grid that will maintain the state of each grid location in the game canvas. A zero will represent an empty location and the numbers 1-7 will represent one of the tetrimino brick types. We’ll start by creating another global variable.

We need to initialize the grid to be a 2D array containing all zeros. In the initialize() function, add the following lines.

The first line creates a new Array object containing 20 elements and assigns it to the variable grid. The next line begins a for loop block. Just like functions and if-else statements, curly braces are used to define the scope of the for loop. Loops are one of the basic control structures in every programming language, repeating a block of code until a certain condition is met. A for loop executes the code inside the block a certain number of times. In JavaScript, a for loop is defined by the three statements separated by semicolons inside of the parentheses. The first statement defines a loop variable i that we initialize to zero. The second statement defines the stop condition for the loop, which we set as i < 20, meaning that the program should continue to loop as long as the loop variable i is less than 20. The third statement increments the loop variable each time through the loop. Altogether, these three statements create a loop that will execute 20 times, once for each row of the game state grid.

The first line inside the loop block assigns a 10-element Array object to the grid variable at one of the 20 row locations defined by the loop variable i. Notice that we access an individual Array element by using an integer in square brackets. Arrays in JavaScript are 0-indexed, meaning that the first element is accessed with an index of zero. We define another for loop, inside the first, with a different loop variable j to iterate over the 10 grid columns. This loop has no curly braces since it only repeats a single line. The indexing scheme on this line uses both loop variables, accessing a specific row and column of the grid and setting the value to zero.

Now that we have a game state grid defined, we need a function that will draw whatever it contains to the canvas. We define a new function drawGrid() that loops over each grid cell as before, and calls the drawBlock function.

You may notice that I said that grid contains an integer between 0 and 7 representing the block type, but our current drawBlock function takes a color parameter directly. We could change our representation so that grid holds a color value, but it will ultimately make things easier in the long run, if we modify our existing code to conform to this new representation. To do that, we need to add a block of code to the drawBlock function that will pick the appropriate color based on the block type. That should look like this:

We’ll also put an additional if statement at the start of the function to only perform the drawing commands if the block type t > 0. This also changes our tetrimino indexing scheme, so we’ll need to change the drawTetrimino function as well. In addition to modifying the tetrimino index values, we need to change this function to modify the game state grid, rather than calling the drawBlock function directly. We also need the function to be able to erase a specific tetrimino, so an additional function parameter will be needed to indicate whether we want to draw or erase. This represents a rather significant change to the longest function we currently have. To make the change easier, we define a helper function that will mimic the form of the current drawBlock function, but will instead write to the game state grid. This will also allow us to make sure that the x and y values are valid.

The if statement in this function checks four different conditions. The && operators mean logical AND, so the whole condition will only be true if each of the four inequalities is true. Now in the drawTetrimino function, we can set the variable c = t*d, where t is the tetrimino type using the new indexing scheme, and d is 1 for drawing and 0 for erasing. We can then erase all of the existing lines that set the variable c. Then, using the find and replace command, we can change all of the drawBlock calls in this function with setGrid. Do NOT use replace all, as it is easy to accidentally replace something that shouldn’t be changed, causing errors.

The last thing we need to change is the keyDown function. Now that drawing is handled by the drawGrid function, we don’t need to clear the canvas each time a key is pressed. Instead, we will erase the current tetrimino using the current variable values, then update the values and redraw the tetrimino. If we don’t erase the old tetrimino when a new one is added, we can retain the position of the old blocks in the game state grid. This is how we can have multiple tetrimino blocks on the screen at the same time. The last two changes we need to make to this function are to add 1 to the new tetrimino type to account for the new indexing scheme, and to call the drawGrid function at the end to refresh the display. The complete code should look like this.

If you load the page now, you should be able to add new tetriminos with any key except the arrow keys, and move them with the arrow keys. You can stack them as you would when actually playing, however you’ll notice that if you move a piece over some existing blocks, they will be overwritten and will disappear when you move again. We’ll look at how to prevent this from happening next time when we introduce basic collision detection.

Updated: February 11, 2015 — 9:47 am

Leave a Reply

Your email address will not be published. Required fields are marked *

DrewBuck.com © 2015 Frontier Theme