[size="5"]Where Did We Leave Off?
Up until now we have made some good progress. We have a nice framework for our game to sit in, and, more importantly, we are all set to create the actual game. Which, coincidentally, is what we are going to do in this article. So, instead of just "good" progress ... be prepared for some extraordinary progress.
Here is the current line-up ...
Leading off today is Animation. He is a very important player on our team so we want him up first. Without him we won't be able to play at all.
Next up, we have Mr. Structures. Mr. S, as he is often called, has the job of keeping everything organized. He is the guy in the dugout often stacking things, or keeping track of statistics. He plays a more important role later in the season when we start playing "real games" and keeping score.
Third, we have the New Shape maker. He is responsible for setting things up for the later players. He is often overlooked since he works behind the scenes.
Batting cleanup for us is Update. Update is a big boy. He has a large job but does a great job of delegating things he needs to other people. Often known as our power hitter, he is the most recognized of all members.
Batting fifth and sixth for us are twins Move Shape and Rotate Shape. They are the ones we rely on to keep things going. If they strike out we know something has gone amiss somewhere along the lines.
The seventh in our line up is Line Test. He typically will clear the bases ... but only if certain conditions are met.
The eighth and ninth positions are filled by another set of twins, Draw Grid and Draw Shape. They are the publicity freaks on the team and make sure the fans can see everything that is happening.
Our manager is of course "the loop." He is the leader and holds everything together. It is his job to dictate what needs to be done and make sure nobody fails. He will not hesitate to act upon any error and is very demanding ... likes to make sure everything is done on his clock.
We have a good solid team and are ready to take a look at their statistics, background, and of course ... how they think. Are you ready ... play ball!
[size="5"]Stepping to the plate...
Animation is a very complicated player. He can play in many different ways, and has numerous styles. On our team, he has adopted the style commonly referred to as "Pre-Set." What this means is that everything that he does was determined before the game has even been started. This occurred in the initialization section, of course.
In our game we have single blocks that are selected at random. That random block is used with a random shape. The shapes that exist are the same ones as in the original Tetris. Now the animation sequences that are needed by the shapes are relatively simple ... you merely rotate the shapes. Therefore, I had three choices when deciding how to animate them.
- I could make bitmaps of every shape pre-built and rotate them at runtime as needed.
- I could do the same as above except pre-rotate them, then save many bitmaps and cycle through them at run-time as they are needed.
- I could build every shape from a block and use some sort of table to tell me how the shape was to be built for that frame.
So, now that we know what we want, how do we accomplish it? Well, the first thing I did was sit down with a piece of paper, and determine the patterns that a shape could have. Then, I took those patterns, encapsulated them into mini-grids, and made them represent either ON, or OFF, states depending on if a block was in that position. Here is an example for a square.
[bquote][font="Courier New"]0 0 0 0
0 1 1 0
0 1 1 0[/font]
[font="Courier New"]0 0 0 0[/font][/bquote]Notice the grid is 4x4, the reason is because the largest a shape can be is four squares wide, or tall. The ones are the places were the blocks are and the zeros are empty locations.
With that gigantic list built, I needed a way to organize them into look-up tables. The decision was to pad the left of each line with four zeros and thus get an 8x4 grid. I could then use an array of four bytes for each frame where a single bit represented a block. This caused a 2-byte waste for every block ... yet it made the code about 100000 times easier to understand.
The table access is really simple. We just offset into the table according to the shape we want. Then, we offset into that address by the frame that we want. Every shape has four frames, no matter what, and are all aligned to four bytes. So, we can easily adjust our "frame pointer" with a few simple arithmetic operations.
That is how animation works in our game. You simply tell him when to adjust to a new frame and he does. Need a new shape? No problem, just point him where it is and he will know what to do.
Here is our table.
;===================================================
; Here is our shape table it contains every possible
; combination of values for the different shapes
; in order to give us the correct shape for every
; possible rotation.
;===================================================
ShapeTable \ ; Here is our square
\
DB 00000000b ; Position 1
DB 00000110b
DB 00000110b
DB 00000000b
DB 00000000b ; Position 2
DB 00000110b
DB 00000110b
DB 00000000b
DB 00000000b ; Position 3
DB 00000110b
DB 00000110b
DB 00000000b
DB 00000000b ; Position 4
DB 00000110b
DB 00000110b
DB 00000000b
; Here is our Line
DB 00001000b ; Position 1
DB 00001000b
DB 00001000b
DB 00001000b
DB 00000000b ; Position 2
DB 00000000b
DB 00000000b
DB 00001111b
DB 00000001b ; Position 3
DB 00000001b
DB 00000001b
DB 00000001b
DB 00001111b ; Position 4
DB 00000000b
DB 00000000b
DB 00000000b
; Here is our Pyramid
DB 00001110b ; Position 1
DB 00000100b
DB 00000000b
DB 00000000b
DB 00001000b ; Position 2
DB 00001100b
DB 00001000b
DB 00000000b
DB 00000000b ; Position 3
DB 00000100b
DB 00001110b
DB 00000000b
DB 00000001b ; Position 4
DB 00000011b
DB 00000001b
DB 00000000b
; Here is our L
DB 00001000b ; Position 1
DB 00001000b
DB 00001100b
DB 00000000b
DB 00000000b ; Position 2
DB 00000010b
DB 00001110b
DB 00000000b
DB 00000110b ; Position 3
DB 00000010b
DB 00000010b
DB 00000000b
DB 00001110b ; Position 4
DB 00001000b
DB 00000000b
DB 00000000b
; Here is our Backwards L
DB 00001100b ; Position 1
DB 00001000b
DB 00001000b
DB 00000000b
DB 00000000b ; Position 2
DB 00001000b
DB 00001110b
DB 00000000b
DB 00000001b ; Position 3
DB 00000001b
DB 00000011b
DB 00000000b
DB 00001110b ; Position 4
DB 00000010b
DB 00000000b
DB 00000000b
; Here is our Backwards Z
DB 00000100b ; Position 1
DB 00000110b
DB 00000010b
DB 00000000b
DB 00000110b ; Position 2
DB 00001100b
DB 00000000b
DB 00000000b
DB 00000100b ; Position 3
DB 00000110b
DB 00000010b
DB 00000000b
DB 00000110b ; Position 4
DB 00001100b
DB 00000000b
DB 00000000b
; Here is our Z
DB 00000010b ; Position 1
DB 00000110b
DB 00000100b
DB 00000000b
DB 00001100b ; Position 2
DB 00000110b
DB 00000000b
DB 00000000b
DB 00000010b ; Position 3
DB 00000110b
DB 00000100b
DB 00000000b
DB 00001100b ; Position 4
DB 00000110b
DB 00000000b
DB 00000000b
[size="5"]Mr. Structure
Oh, yes ... Mr. Structure. He often looks like a container, however in our game he is spread out. It is his responsibility to hold the X and Y coordinates, current shape, current shape block to use, and the current frame. He has just a few variables to keep things semi-organized.
As I mentioned earlier, he will have a larger job when it comes to keep score, and manage any other statistics.
He is a really open guy, global to be precise. He doesn't mind helping people and, of course, will let anybody know what he knows.
The declarations for him are in [color="#8B0000"]Shapes.asm [/color]and for the time being are relatively simple.
Because Mr. Structure is so open bad things can POSSIBLY happen. It is your job, as a programmer, to make sure that those bad things can NEVER happen. If you let him be corrupted in some manner your whole game might go down the toilet.
[size="5"]The New Shape Maker
With the dreary setup stuff behind us we have things ready for the New Shape maker. His responsibility is fairly straight forward so let's take a look at the code before I start explaining things.
;########################################################################
; New_Shape Procedure
;########################################################################
New_Shape PROC
;================================================
; This function will select a new shape at random
; for the current shape
;================================================
;======================================
; First make sure they haven't reached
; the top of the grid yet
;
; Begin by calculating the start of
; the very last row where the piece
; is initialized at ... aka (5,19)
;======================================
MOV EAX, 13
MOV ECX, 19
MUL ECX
ADD EAX, 5
MOV EBX, BlockGrid
ADD EAX, EBX
MOV ECX, EAX
ADD ECX, 4
;==========================
; Loop through and test the
; next 4 positions
;==========================
.WHILE EAX <= ECX
;=====================
; Is this one filled?
;=====================
MOV BL, BYTE PTR [EAX]
.IF BL != 0
;===================
; They are dead
;===================
JMP err
.ENDIF
;=================
; Inc the counter
;=================
INC EAX
.ENDW
;=============================
; Use a random number to get
; the current shape to use
;
; For this we will just use
; the time returned by the
; Get_Time() function
;=============================
INVOKE Get_Time
;=============================
; Mod this number with 7
; since there are 7 shapes
;=============================
MOV ECX, 7
XOR EDX, EDX
DIV ECX
MOV EAX, EDX
;=============================
; Multiply by 16 since there
; are 16 bytes per shape
;=============================
SHL EAX, 4
;=============================
; Use that number to select
; the shape from the table
;=============================
MOV EBX, OFFSET ShapeTable
ADD EAX, EBX
MOV CurShape, EAX
;=============================
; Use a random number to get
; the block surface to use
;
; For this we will just use
; the time returned by the
; Get_Time() function
;=============================
INVOKE Get_Time
;=============================
; And this result with 7
; since there are 8 blocks
;=============================
AND EAX, 7
;================================
; Use it as the block surface
;================================
MOV CurShapeColor, EAX
;================================
; Initialize the Starting Coords
;================================
MOV CurShapeX, 5
MOV CurShapeY, 24
;================================
; Set the Current Frame Variable
;================================
MOV CurShapeFrame, 0
done:
;=======================
; They have a new piece
;=======================
return TRUE
err:
;===================
; They died!
;===================
return FALSE
New_Shape ENDP
;########################################################################
; END New_Shape
;########################################################################
To start with we check the area directly under where we want the block to start to see if there are blocks already in there. If so, then they died. It is a really simple concept. No more room on grid = DEATH!
Next we grab some random numbers to use for the block texture and the shape. I chose just to use the [font="Courier New"][color="#000080"]Get_Time()[/color][/font] function that we have. We may write a true random number generator later in this article series. For now, this function call will serve our purposes.
In order to get a number between zero and six we divide by seven and take the remainder ( this is placed in EDX after a DIV ) . This way, the highest number we could have is six, and the lowest is zero, which is perfect since we have seven shapes to choose from.
We do something a bit different for the blocks. Instead of performing a MOD operation, we AND the number with ( N-1 ). Where N is the number you would normally MOD with. This only works for numbers that are powers of 2 however. We are taking advantage of another bit manipulation operation to speed things up.
The next step is to merely initialize the starting X and Y coordinates along with the starting frame to use.
That is all we need to do in order to create a new shape during the game. Once this function is finished everything is setup to start moving and manipulating the current shape, whatever it may be.
[size="5"]Update takes a few practice swings
Update is our power hitter. He has the job of handling all updates. Let's take a look at exactly what it is he does.
;########################################################################
; Update_Shape Procedure
;########################################################################
Update_Shape PROC
;================================================
; This function will update our shape ... or
; drop it down by a grid notch and test for
; a collision with the grid
;================================================
;========================
; Can we move down???
;========================
INVOKE Test_Collision
.IF EAX == TRUE
;=======================
; NO... we hit something
;=======================
;=============================
; Place the piece in the grid
;=============================
INVOKE Place_In_Grid
;=========================
; Jmp & Return with False
;=========================
JMP err
.ELSE
;===========================
; yes we can drop down
;===========================
;=================================
; Drop our piece down by a notch
;=================================
DEC CurShapeY
.ENDIF
done:
;===================
; We hit nothing
;===================
return TRUE
err:
;===================
; We hit something
;===================
return FALSE
Update_Shape ENDP
;########################################################################
; END Update_Shape
;########################################################################
To begin with we make a call to test the collision status of the current shape. If the call returns TRUE then we can not move the shape anymore and need to place it in the grid. So he makes a call to [font="Courier New"][color="#000080"]Place_In_Grid()[/color][/font]. However, if the call returns FALSE, then we can still move the shape. So, we drop it down a notch by decrementing the Y coordinate of the shape.
The last thing we need to do is return to our manager and tell him whether we succeeded or failed. Before we continue though, let's take a closer look at [font="Courier New"][color="#000080"]Test_Collision()[/color][/font] and [font="Courier New"][color="#000080"]Place_In_Grid()[/color][/font] since they are the ones who really do the work.
;########################################################################
; Place_In_Grid Procedure
;########################################################################
Place_In_Grid PROC
;================================================
; This function will place the current shape
; into the grid
;================================================
;===========================
; Local Variables
;===========================
LOCAL DrawY: DWORD
LOCAL DrawX: DWORD
LOCAL CurRow: DWORD
LOCAL CurCol: DWORD
LOCAL CurLine: DWORD
LOCAL CurGrid: DWORD
;===================================
; Get the Current Shape Pos
;===================================
MOV EBX, CurShape
MOV EAX, CurShapeFrame
SHL EAX, 2
ADD EBX, EAX
MOV CurLine, EBX
;===================================
; Set the Starting Row and Column
; for the placement of the block
;===================================
MOV EAX, CurShapeX
MOV EBX, CurShapeY
MOV DrawX, EAX
MOV DrawY, EBX
;===================================
; Loop through all four rows
;===================================
MOV CurRow, 0
.WHILE CurRow < 4
;=====================================
; Loop through all four Columns
;=====================================
MOV CurCol, 4
.WHILE CurCol > 0
;===============================
; Shift the CurLine Byte over
; by our CurCol
;===============================
MOV ECX, 4
SUB ECX, CurCol
MOV EBX, CurLine
XOR EAX, EAX
MOV AL, BYTE PTR [EBX]
SHR EAX, CL
;===============================
; Is it a valid block?
;===============================
.IF ( EAX & 1 )
;============================
; Yes it was a valid block
;============================
;=============================
; Calculate the Block in our
; BlockGrid to place it in
;=============================
MOV EAX, DrawY
MOV ECX, 13
MUL ECX
MOV EBX, DrawX
ADD EBX, CurCol
DEC EBX
ADD EAX, EBX
MOV ECX, BlockGrid
ADD EAX, ECX
;=============================
; Store the Color in the Block
; add one since we let 0 mean
; the block is empty
;=============================
MOV EBX, CurShapeColor
INC EBX
MOV BYTE PTR [EAX], BL
.ENDIF
;=====================
; Dec our col counter
;=====================
DEC CurCol
.ENDW
;=======================
; Inc the CurLine
;=======================
INC CurLine
;====================
; decrement Y coord
;====================
DEC DrawY
;====================
; Inc the row counter
;====================
INC CurRow
.ENDW
done:
;===================
; We completed
;===================
return TRUE
err:
;===================
; We didn't make it
;===================
return FALSE
Place_In_Grid ENDP
;########################################################################
; END Place_In_Grid
;########################################################################
;########################################################################
; Test_Collision Procedure
;########################################################################
Test_Collision PROC
;================================================
; This function will test for a collision between
; the grid and the current shape
;================================================
;==============================
; Local Variables
;==============================
LOCAL Index: DWORD
LOCAL Adjust: DWORD
;========================================
; Loop through and find the first block
; in each of the four columns
;
; NOTE: 0 = RIGHT 3 = LEFT
;========================================
MOV Index, 0
.WHILE Index < 4
;==========================================
; Start at the bottom of the Current Frame
;==========================================
MOV EBX, CurShape
MOV EAX, CurShapeFrame
SHL EAX, 2
ADD EBX, EAX
ADD EBX, 3
;=========================================
; Now loop until we have a one in the
; current colum we are working on or we
; reach the top
;==========================================
MOV Adjust, 4
.WHILE Adjust > 0
;=======================
; Get the Current Line
;=======================
XOR EAX, EAX
MOV AL, BYTE PTR [EBX]
;=======================
; Adjust by the Column
;=======================
MOV ECX, Index
SHR EAX, CL
;=========================
; Was there a block there
;=========================
.IF ( EAX & 1 )
;======================
; Yes there was a block
;======================
;=============================
; Have we hit Bottom
;=============================
MOV EAX, CurShapeY
SUB EAX, Adjust
INC EAX ; Off by 1 syndrome
.IF EAX == 0
;================
; Bottom of grid
;================
JMP done
.ENDIF
;===========================
; Calculate the Block right
; under it on the grid
;===========================
DEC EAX ; Move Under it
MOV ECX, 13
MUL ECX
ADD EAX, CurShapeX
ADD EAX, 3
SUB EAX, Index
MOV ECX, BlockGrid
ADD ECX, EAX
;===========================
; Does the Block have one
; underneath it on the grid?
;===========================
MOV AL, BYTE PTR [ECX]
.IF AL != 0
;===========================
; We had a valid collision
;===========================
JMP done
.ENDIF
.ENDIF
;=================================
; No Block -- Previous Line Please
;=================================
DEC EBX
;===============================
; Decrement the Adjust counter
;===============================
DEC Adjust
.ENDW
;==================================
; Next Column Please!
;==================================
INC Index
.ENDW
err:
;===================
; We didn't collide
;===================
return FALSE
done:
;===================
; We collided
;===================
return TRUE
Test_Collision ENDP
;########################################################################
; END Test_Collision
;########################################################################
[font="Courier New"][color="#000080"]Test_Collision()[/color][/font] is not quite so simple. It loops through all four columns, and, inside that, loops through all four rows of the current frame. It then tests the bit at its' own ( row, col ) location. If there is a bit turned ON there, it checks whether or not the grid has a block in the position directly under it. If it fails this test, on ANY bit, then the block can not be moved so we return TRUE. Otherwise, at the end, if there have been no collisions we return FALSE. At this point we also check to see if it is at the bottom of the grid. This constitutes the same thing as having a block underneath it.
As you an see, although [font="Courier New"][color="#000080"]Update()[/color][/font] is very important to our team, he has so much to do that you ALWAYS want to have him delegate his responsibilities out to others. Then, just let him pretend to do what he is supposed to do.
NOTE: In case you missed my crude attempt at symbolism here is a quick explanation. YOU NEVER want to make functions that do many things. The ideal is to only have them accomplish one, maybe two things. Let a manager type function make calls, test for errors, and things like that.
[size="5"]Let's Get Moving!
Now that we have a piece to play with, we need to do just that, play with it. Get your minds out of the gutter!
Anyway, here is the code:
;########################################################################
; Rotate_Shape Procedure
;########################################################################
Rotate_Shape PROC
;=======================================================
; This function will rotate the current shape it tests
; to make sure there are no blocks already in the place
; where it would rotate.
;
; NOTE; It is missing the check for out of the grid on
; rotation. That is left for the time being as an
; exercise.
;
; My solution will be show in Article #5.
;=======================================================
;================================
; Local Variables
;================================
LOCAL Index: DWORD
LOCAL CurBlock: DWORD
LOCAL Spot: BYTE
;================================
; Are they at the last frame?
;================================
.IF CurShapeFrame == 3
;=====================================
; Yep ... make sure they can rotate
;=====================================
;=========================================
; Adjust to the current Block they are at
;=========================================
MOV EAX, CurShapeY
MOV ECX, 13
MUL ECX
ADD EAX, CurShapeX
ADD EAX, BlockGrid
MOV CurBlock, EAX
;========================================
; Loop through all four rows of our Shape
;========================================
MOV Index, 0
.WHILE Index < 4
;=======================
; Get the current line
;=======================
MOV EBX, CurShape ; Same as Frame 0
ADD EBX, Index
XOR ECX, ECX
MOV CL, BYTE PTR [EBX]
MOV Spot, CL
;==============================
; Test all 4 of the valid bits
;==============================
;=====================
; Position 4
;=====================
.IF ( Spot & 8 ) ; 2^3
;=======================
; Test this on the Grid
;=======================
MOV EAX, CurBlock
.IF ( BYTE PTR [EAX] ) != 0
;======================
; Failed! Can't rotate
;======================
JMP err
.ENDIF
.ENDIF
;=================
; Inc our CurBlock
;=================
INC CurBlock
;=====================
; Position 3
;=====================
.IF ( Spot & 4 ) ; 2^2
;=======================
; Test this on the Grid
;=======================
MOV EAX, CurBlock
.IF ( BYTE PTR [EAX] ) != 0
;======================
; Failed! Can't rotate
;======================
JMP err
.ENDIF
.ENDIF
;=================
; Inc our CurBlock
;=================
INC CurBlock
;=====================
; Position 2
;=====================
.IF ( Spot & 2 ) ; 2^1
;=======================
; Test this on the Grid
;=======================
MOV EAX, CurBlock
.IF ( BYTE PTR [EAX] ) != 0
;======================
; Failed! Can't rotate
;======================
JMP err
.ENDIF
.ENDIF
;=================
; Inc our CurBlock
;=================
INC CurBlock
;=====================
; Position 1
;=====================
.IF ( Spot & 1 ) ; 2^0
;=======================
; Test this on the Grid
;=======================
MOV EAX, CurBlock
.IF ( BYTE PTR [EAX] ) != 0
;======================
; Failed! Can't rotate
;======================
JMP err
.ENDIF
.ENDIF
;========================
; Drop Down by a line
; plus the amount we
; incrmented over by
;========================
SUB CurBlock, 16
;========================
; Incrment our Index
;========================
INC Index
.ENDW
;=======================
; Ok ... start over
;=======================
MOV CurShapeFrame, 0
.ELSE
;=====================================
; NO ... make sure they can rotate
;=====================================
;=========================================
; Adjust to the current Block they are at
;=========================================
MOV EAX, CurShapeY
MOV ECX, 13
MUL ECX
ADD EAX, CurShapeX
ADD EAX, BlockGrid
MOV CurBlock, EAX
;========================================
; Loop through all four rows of our Shape
;========================================
MOV Index, 0
.WHILE Index < 4
;=======================
; Get the current line
;=======================
MOV EBX, CurShape
MOV EAX, CurShapeFrame
INC EAX ; Get to new frame
SHL EAX, 2
ADD EBX, Index
ADD EBX, EAX
MOV CL, BYTE PTR [EBX]
MOV Spot, CL
;==============================
; Test all 4 of the valid bits
;==============================
;=====================
; Position 4
;=====================
.IF ( Spot & 8 ) ; 2^3
;=======================
; Test this on the Grid
;=======================
MOV EAX, CurBlock
.IF ( BYTE PTR [EAX] ) != 0
;======================
; Failed! Can't rotate
;======================
JMP err
.ENDIF
.ENDIF
;=================
; Inc our CurBlock
;=================
INC CurBlock
;=====================
; Position 3
;=====================
.IF ( Spot & 4 ) ; 2^2
;=======================
; Test this on the Grid
;=======================
MOV EAX, CurBlock
.IF ( BYTE PTR [EAX] ) != 0
;======================
; Failed! Can't rotate
;======================
JMP err
.ENDIF
.ENDIF
;=================
; Inc our CurBlock
;=================
INC CurBlock
;=====================
; Position 2
;=====================
.IF ( Spot & 2 ) ; 2^1
;=======================
; Test this on the Grid
;=======================
MOV EAX, CurBlock
.IF ( BYTE PTR [EAX] ) != 0
;======================
; Failed! Can't rotate
;======================
JMP err
.ENDIF
.ENDIF
;=================
; Inc our CurBlock
;=================
INC CurBlock
;=====================
; Position 1
;=====================
.IF ( Spot & 1 ) ; 2^0
;=======================
; Test this on the Grid
;=======================
MOV EAX, CurBlock
.IF ( BYTE PTR [EAX] ) != 0
;======================
; Failed! Can't rotate
;======================
JMP err
.ENDIF
.ENDIF
;========================
; Drop Down by a line
; plus the amount we
; incrmented over by
;========================
SUB CurBlock, 16
;========================
; Incrment our Index
;========================
INC Index
.ENDW
;========================
; OK ... just increment
;========================
INC CurShapeFrame
.ENDIF
done:
;===================
; We completed
;===================
return TRUE
err:
;===================
; We didn't make it
;===================
return FALSE
Rotate_Shape ENDP
;########################################################################
; END Rotate_Shape
;########################################################################
;########################################################################
; Move_Shape Procedure
;########################################################################
Move_Shape PROC Direction:DWORD
;================================================
; This function will move the shape in the
; desired direction
;================================================
;===========================
; Local Variables
;===========================
LOCAL CurCol: DWORD
LOCAL CurRow: DWORD
LOCAL CanMove: DWORD
;====================================
; Set CanMove to true it will
; be fasle later if we can't move
;====================================
MOV CanMove, TRUE
;================================================
; Perform the Tests based on direction they want
;================================================
.IF Direction == MOVE_LEFT
;====================================
; They want to move to the left
;====================================
;====================================
; Find the Left most column with a
; valid block inside of it
;====================================
MOV CurCol, 0
.WHILE CurCol < 4
;==========================
; Calculate Our Mask
;==========================
MOV EAX, 1
MOV ECX, 3 ; Start from the Left
SUB ECX, CurCol
SHL EAX, CL
MOV EDX, EAX
PUSH EDX
;===========================
; Go through all 4 rows
;===========================
MOV CurRow, 0
.WHILE CurRow < 4
;===============================
; Get the Current Line of Blocks
;===============================
MOV EBX, CurShape
MOV EAX, CurShapeFrame
SHL EAX, 2
ADD EBX, EAX
ADD EBX, CurRow
XOR ECX, ECX
MOV CL, BYTE PTR [EBX]
;========================
; Test the Mask and the
; current line of blocks
;========================
POP EDX
PUSH EDX
.IF ( EDX & ECX )
;====================
; There was a Block
;====================
;====================
; Calculate the
; block's X value
;====================
MOV EAX, CurShapeX
ADD EAX, CurCol
;====================
; Can we move?
;====================
.IF EAX == 0
;=============
; Nope
;=============
MOV CanMove, FALSE
.ELSE
;========================
; Calculate the block to
; the left of us
;========================
MOV EAX, CurShapeY
SUB EAX, CurRow
MOV ECX, 13
MUL ECX
ADD EAX, CurShapeX
ADD EAX, CurCol
DEC EAX ; 1 to the Left
MOV ECX, BlockGrid
ADD ECX, EAX
MOV AL, BYTE PTR [ECX]
;======================
; Are we blocked?
;======================
.IF AL != 0
;================
; We are blocked
;================
MOV CanMove, FALSE
.ENDIF
.ENDIF
.ENDIF
;===========================
; Inc our current row
;===========================
INC CurRow
.ENDW
;===========================
; Clean Off the stack
;===========================
POP EDX
;===========================
; Inc our current column
;===========================
INC CurCol
.ENDW
;==================================
; Can we Still Move
;==================================
.IF CanMove == TRUE
;=======================
; yes we can
;=======================
DEC CurShapeX
.ENDIF
.ELSEIF Direction == MOVE_RIGHT
;====================================
; They want to move to the right
;====================================
;====================================
; Find the Right most column with a
; valid block inside of it
;====================================
MOV CurCol, 4
.WHILE CurCol > 0
;==========================
; Calculate Our Mask
;==========================
MOV EAX, 1
MOV ECX, 4 ; Start from the Right
SUB ECX, CurCol
SHL EAX, CL
MOV EDX, EAX
PUSH EDX
;===========================
; Go through all 4 rows
;===========================
MOV CurRow,0
.WHILE CurRow < 4
;===============================
; Get the Current Line of Blocks
;===============================
MOV EBX, CurShape
MOV EAX, CurShapeFrame
SHL EAX, 2
ADD EBX, EAX
ADD EBX, CurRow
XOR ECX, ECX
MOV CL, BYTE PTR [EBX]
;========================
; Test the Mask and the
; current line of blocks
;========================
POP EDX
PUSH EDX
.IF ( EDX & ECX )
;====================
; There was a Block
;====================
;====================
; Calculate the
; block's X value
;====================
MOV EAX, CurShapeX
ADD EAX, CurCol
DEC EAX
;====================
; Can we move?
;====================
.IF EAX == 12
;=============
; Nope
;=============
MOV CanMove, FALSE
.ELSE
;========================
; Calculate the block to
; the right of us
;========================
MOV EAX, CurShapeY
SUB EAX, CurRow
MOV ECX, 13
MUL ECX
ADD EAX, CurShapeX
ADD EAX, CurCol ; Already 1 to the
; Right
MOV ECX, BlockGrid
ADD ECX, EAX
MOV AL, BYTE PTR [ECX]
;======================
; Are we blocked?
;======================
.IF AL != 0
;================
; We are blocked
;================
MOV CanMove, FALSE
.ENDIF
.ENDIF
.ENDIF
;===========================
; Inc our current row
;===========================
INC CurRow
.ENDW
;===========================
; Clean Off the stack
;===========================
POP EDX
;===========================
; dec our current column
;===========================
DEC CurCol
.ENDW
;==================================
; Can we Still Move
;==================================
.IF CanMove == TRUE
;=======================
; yes we can
;=======================
INC CurShapeX
.ENDIF
.ELSEIF Direction == MOVE_DOWN
;====================================
; They want to move the piece down
;====================================
;====================================
; Test for a collision
;====================================
INVOKE Test_Collision
.IF EAX == FALSE
;============================
; It is safe to drop a notch
;============================
DEC CurShapeY
.ENDIF
.ELSE
;====================================
; They passed an invalid direction
;====================================
JMP err
.ENDIF
done:
;===================
; We completed
;===================
return TRUE
err:
;===================
; We didn't make it
;===================
return FALSE
Move_Shape ENDP
;########################################################################
; END Move_Shape
;########################################################################
The rotate function first decided if it is at the last frame. If so, it has code to wrap it around for all of the test, otherwise it just uses the next frame. Then, it loops through all of the bits, finding the valid ones, just like [font="Courier New"][color="#000080"]Test_Collision()[/color][/font]. If there is already a bit set in the place the shape would be at then it is not allowed to move and the call fails. And ... that is that.
NOTE: Code is not in there to check for out of bounds on the grid. So, if you rotate at a corner, you may slide out of the grid and into the background area. This has been left as AN EXERCISE FOR YOU. I wanted to see something interactive come out of this article series, and I decided this would be as good of a place as any to start asking for it. I will present my solution in the next article. Compare yours to mine at that time.
Back to the code at hand: [font="Courier New"][color="#000080"]Move_Shape()[/color][/font]. This function will move the shape to the left, or to the right, depending on the value passed into it. It merely tests the bits once again. Only this time we have to find the leftmost, or rightmost, valid bit in each row. Then, we check the grid block to the left or right and see if it is empty. Accordingly, either we move it, or we don't, and then return to the caller.
There isn't much else to talk about in this section. You have seen how to access everything many times now. The only thing that changes is the things we need to access, or the order in which we test stuff. These are the kinds of things that need to be resolved at design time.
[size="5"]Time to Clear the Bases?
When it comes time to clear the grid we call upon Line_Test. This will function will return TRUE if it clears a line. It will return false if it doesn't have a valid line on the grid.
Here is the code for it:
;########################################################################
; Line_Test Procedure
;########################################################################
Line_Test PROC
;================================================
; This function will test to see if they earned a
; line ... if so it will eliminate that line
; and update our grid of blocks
;================================================
;==========================
; Local Variables
;==========================
LOCAL CurLine: DWORD
LOCAL CurBlock: DWORD
;===============================
; Start at the Base of the Grid
;===============================
MOV CurLine, 0
;=================================
; Loop through all possible Lines
;=================================
.WHILE CurLine < (GRID_HEIGHT - 4)
;===================================
; Goto the base of the current line
;===================================
MOV EAX, CurLine
MOV ECX, 13
MUL ECX
ADD EAX, BlockGrid
;==================================
; Loop through every block
; testing to see if it is valid
;==================================
MOV CurBlock, 0
.WHILE CurBlock < (GRID_WIDTH)
;==========================
; Is this Block IN-Valid?
;==========================
MOV BL, BYTE PTR [EAX]
.IF BL == 0
;===================
; Yes, so break
;===================
.BREAK
.ENDIF
;======================
; Next Block
;======================
INC EAX
;======================
; Inc the counter
;======================
INC CurBlock
.ENDW
;==============================
; Did our inner loop go all
; of the way through??
;==============================
.IF CurBlock == (GRID_WIDTH)
;============================
; Yes. That means that it was
; a valid line we just earned
;============================
;===================================
; Calculate How much memory to move
; TOTAL - Amount_IN = TO_MOVE
;===================================
MOV EBX, (GRID_WIDTH * (GRID_HEIGHT -5))
MOV EAX, CurLine
MOV ECX, 13
MUL ECX
PUSH EAX
SUB EBX, EAX
;============================
; Move the memory one line
; up to our current line
;============================
POP EAX
ADD EAX, BlockGrid
MOV EDX, EAX
ADD EDX, 13
;==============================
; Move the memory down a notch
;==============================
INVOKE RtlMoveMemory, EAX, EDX, EBX
;============================
; Jump down and return TRUE
;============================
JMP done
.ENDIF
;==============================
; Incrment our Line counter
;==============================
INC CurLine
.ENDW
err:
;===================
; We didn't get one
;===================
return FALSE
done:
;===================
; We earned a line
;===================
return TRUE
Line_Test ENDP
;########################################################################
; END Line_Test
;########################################################################
The code loops through every line in our grid memory and test for blocks. If it finds that a grid location is empty then it continues with the next line. If every location has a valid block inside of it, then the function moves all of the memory above it to the row that had the line. It does this by calling the Win32 API function [font="Courier New"][color="#000080"]RTLMoveMemory()[/color][/font].We have it return after every valid line it finds, and eliminates, because when we want to keep score it will be easier to track how many lines they earn. It is always a good thing to keep future expansion in mind while programming.
[size="5"]The Final Batters
Our two final hitters are the publicity hounds [font="Courier New"][color="#000080"]Draw_Shape()[/color][/font] and [font="Courier New"][color="#000080"]Draw_Grid()[/color][/font]. Below is their code.
;########################################################################
; Draw_Shape Procedure
;########################################################################
Draw_Shape PROC
;=======================================================
; This function will draw our current shape at its
; proper location on the screen
;=======================================================
;===========================
; Local Variables
;===========================
LOCAL DrawY: DWORD
LOCAL DrawX: DWORD
LOCAL CurRow: DWORD
LOCAL CurCol: DWORD
LOCAL CurLine: DWORD
LOCAL XPos: DWORD
LOCAL YPos: DWORD
;===================================
; Get the Current Shape Pos
;===================================
MOV EBX, CurShape
MOV EAX, CurShapeFrame
SHL EAX, 2
ADD EBX, EAX
MOV CurLine, EBX
;===================================
; Set the Starting Row and Column
; for the drawing
;===================================
MOV EAX, CurShapeX
MOV EBX, CurShapeY
MOV DrawX, EAX
MOV DrawY, EBX
;===================================
; Loop through all four rows
;===================================
MOV CurRow, 0
.WHILE CurRow < 4
;=====================================
; Loop through all four Columns if
; the Y Coord is in the screen
;=====================================
MOV CurCol, 4
.WHILE CurCol > 0 && DrawY < 20
;===============================
; Shift the CurLine Byte over
; by our CurCol
;===============================
MOV ECX, 4
SUB ECX, CurCol
MOV EBX, CurLine
XOR EAX, EAX
MOV AL, BYTE PTR [EBX]
SHR EAX, CL
;===============================
; Is it a valid block?
;===============================
.IF ( EAX & 1 )
;============================
; Yes it was a valid block
;============================
;=============================
; Calculate the Y coord
;=============================
MOV EAX, (GRID_HEIGHT - 5)
SUB EAX, DrawY
MOV ECX, BLOCK_HEIGHT
MUL ECX
MOV YPos, EAX
;=============================
; Calculate the X coord
;=============================
MOV EAX, DrawX
ADD EAX, CurCol
DEC EAX
MOV ECX, BLOCK_WIDTH
MUL ECX
ADD EAX, 251
MOV XPos, EAX
;=============================
; Calculate the surface to use
;=============================
MOV EAX, CurShapeColor
SHL EAX, 2
MOV EBX, DWORD PTR BlockSurface[EAX]
;=============================
; Blit the block
;=============================
DDS4INVOKE BltFast, lpddsback, XPos, YPos, \
EBX, ADDR SrcRect, \
DDBLTFAST_NOCOLORKEY OR DDBLTFAST_WAIT
.ENDIF
;=====================
; Dec our col counter
;=====================
DEC CurCol
.ENDW
;=======================
; Inc the CurLine
;=======================
INC CurLine
;====================
; decrement Y coord
;====================
DEC DrawY
;====================
; Inc the row counter
;====================
INC CurRow
.ENDW
done:
;===================
; We completed
;===================
return TRUE
err:
;===================
; We didn't make it<br