Here comes the classic of all classics. In this post we recreate the game of Tetris in Excel using VBA macros. The blocks are shown by filling the interior color of the corresponding cells in the range that represents the block. That range is built from an array that defines each block type. The SetTimer function of the Windows API is used to create the movement of blocks in Excel Tetris. The OnKey method of the Application object is used to capture arrows keystroke to move the blocks left and right, rotate them, or move them all the way down.
Tetris game
The game of Tetris was created in the mid-80s by Alexey Pajitnov. The aim of the game is to move falling blocks so that they fit as tight as possible without leaving any gaps across the playfield. A whole line without gaps disappears from the playfield and lets other blocks fall down the gap. This prevents the pile of blocks from reaching the top and grants points.
The falling blocks are called "tetrominoes" and consist of four connected square units of the same color. There are seven different types of tetrominoes in Tetris, often referred to as for the letter they represent: I, O, T, J, L, S, Z.
Game playfield and blocks
The Tetris playfield spans a range of 20 rows and 10 columns (range B2:K21 in this version of Excel Tetris). The range is formatted with black interior color and black cell borders. The blocks are shown by changing the interior color of the corresponding four cells that represent the block.
The four cells that represent each block are defined by reading an array (BlockArr) that stores the configuration for each block type. The array is redimensioned and filled each time a block is added, depending on the blueprint for that block. This version of Excel Tetris includes the blueprints for each block type and rotation in the second sheet as a visual aid (we could also have a collection of arrays for each block type initialized in the code).
Blocks are selected by picking a random range in that sheet (BlockStartRng). That range is used to create the corresponding 2D array (BlockArr) that defines the block. The size of the array is determined by the number of rows and columns in the blueprint range. For example, for the square block (O shape), the range (B2:C3 above) consists of 2 rows and 2 columns.
blockRows = BlockStartRng.Rows.Count '2 for square block
blockColumns = BlockStartRng.Columns.Count '2 for square block
In a very simple manner, we could show the square block in a given location of the sheet using the simplified version of the PrintBlock macro below. The variables r and c correspond to the row and column of the upper-left corner of the block range (BlockRng). The variable blockColor is a random number between 3 and 8 in the ColorIndex scale (see more about that property here).
Set BlockRng = Range(Cells(r, c), Cells(r + blockRows - 1, c + blockColumns - 1))
BlockRng.Interior.ColorIndex = blockColor
End Sub
But that only works for the square block (where the whole 2x2 range is filled); all the other blocks have gaps within the blueprint block range. Hence, it is necessary to create an array of 1s and 0s (or True and False) to indicate filled cells vs gaps. For example, if we take the first form of the L shape (range B10:D12 above), the array BlockArr would determine which cells are filled vs those that aren´t. The macro below converts the blueprint range into the block array (this could also very well be a function returning the BlockArr array).
Dim arrRows As Integer, arrCols As Integer, arrR As Integer, arrC As Integer
arrRows = rng.Rows.Count
arrCols = rng.Columns.Count
ReDim BlockArr(1 To arrRows, 1 To arrCols)
For arrR = 1 To arrRows
For arrC = 1 To arrCols
Set cell = rng.Cells(arrR, arrC)
If cell.Interior.Color <> vbWhite Then
BlockArr(arrR, arrC) = 1
Else
BlockArr(arrR, arrC) = 0
End If
Next arrC
Next arrR
End Sub
The square block (O shape) returns a 2x2 array, the bar (I shape) is 4x4, while all the other shapes are 3x3. The array BlockArr can now be used to fill the corresponding cells for each block type (see the updated PrintBlock macro below). A similar macro is run just before printing the block in order to check whether the block can take that position or not - all cells in that given position must be empty (black interior color) and within the boundaries of the playfield.
Dim cell As Range, arrR As Integer, arrC As Integer
Set BlockRng = Range(Cells(r, c), Cells(r + blockRows - 1, c + blockCols - 1))
For Each cell In BlockRng
arrR = cell.Row - BlockRng.Row + 1
arrC = cell.Column - BlockRng.Column + 1
If BlockArr(arrR, arrC) = 1 Then
cell.Interior.ColorIndex = blockColor
End If
Next cell
End Sub
Blocks movement and rotation
The initial position of the block is defined by the top row of the playfield (row 2 for range B2:K21) and a random column. As shown above, the variables r and c correspond to the upper-left cell of the range that represents the block (BlockRng).
In order to create the block movement, the procedure MoveBlock is called repeatedly every certain time interval in milliseconds. That´s achieved using the SetTimer function of the Windows API library (user32.dll). We have already seen how to create movement in Excel with SetTimer in previous posts (Excel Circles Movement, Excel Tron Game, Excel Falling Letters, and other). The routine is added to a separate module in the version of Excel Tetris available for download at the bottom of this article. The variable "speed" determines the time in milliseconds that elapses to call the macro MoveBlock.
If TimerID <> 0 Then
KillTimer 0, TimerID
TimerID = 0
End If
TimerID = SetTimer(0, 0, speed, AddressOf MoveBlock)
End Sub
The "moving" procedure (MoveBlock) updates the position of the block by filling the corresponding cells with a given color (blockColor) in the new position (one row below, or right or left). The macro MoveBlock checks first whether the block can take that position or not - all cells in that given position must be empty (black interior color) and within the boundaries of the playfield. It then calls the macro MoveIt, which clears the block, updates the value of row and column variables, and prints the block in the new position.
Application.ScreenUpdating = False
r = r + targetR
c = c + targetC
Call PrintBlock
End Sub
When moving down, targetR is 1 and targetC is 0. When moving right or left, targetR is 0 and targetC is 1 or -1, respectively. Horizontal block movement is triggered by keyboard arrow keys. Hence, another macro binds keyboard keys to the corresponding macro. This is all stored in another module in the version of Excel Tetris available for download below.
Application.OnKey "{LEFT}", "MoveLeft"
Application.OnKey "{RIGHT}", "MoveRight"
Application.OnKey "{DOWN}", "MoveDown"
Application.OnKey "{UP}", "Rotate"
End Sub
The macros "MoveLeft" and "MoveRight" check whether that position does not overlap with other blocks and stands within the boundaries of the playfield, and then call MoveIt with the respective values for targetC. The macro "MoveDown" sets targetR to 1 as long as there are no blocks underneath.
The macro "Rotate" selects the next blueprint range (90-degree rotation) and, if the filled cells for that block range do not have any conflict with other blocks in the playfield, it prints the rotated block. A conditional statement rules out the rotation for the square block. All the other blocks follow the super rotation system (Tetris standard) and rotate 90 degrees clockwise. Except for the I shape, the rotation point is always the center cell (for all 3x3 blocks).
Clear lines and points
Blocks also move down whenever a full line is complete and cleared. Another macro is called whenever a block hits another block (or reaches the bottom row) to check whether a full line is complete. The macro loops first by row from the bottom row upwards, and then by column cell by cell to count the number of gaps along each row - if the number of gaps is 0, the line is cleared (interior color black) and the score is updated. When that happens, all blocks and block parts above that line are moved one line below.
No comments:
Post a Comment