• Create Account

Like
0Likes
Dislike

# Win32 Assembly Part 4

By Chris Hobbs | Published Nov 18 1999 03:58 AM in General Programming

00000000b eax position mov shape block 00000110b ecx current
 If you find this article contains errors or problems rendering it unreadable (missing images or files, mangled code, improper text formatting, etc) please contact the editor so corrections can be made. Thank you for helping us improve this resource

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!

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.
Because of speed, and size, I decided to go with the third method. It is a little bit more complex ... but I think the speed gain, and size drop, is worth it. (NOTE: As a programmer, if you have the ability to make a piece of code more robust, more user-friendly, smaller, or faster then do so).

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.
0 0 0 0
0 1 1 0
0 1 1 0

0 0 0 0
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

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 Shapes.asm 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.

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
MOV    EBX, BlockGrid
MOV    ECX, EAX

;==========================
; Loop through and test the
; next 4 positions
;==========================
.WHILE EAX <= ECX
;=====================
; Is this one filled?
;=====================
MOV    BL, BYTE PTR [EAX]
.IF BL != 0
;===================
;===================
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
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
;########################################################################

Do you see what I am doing with the code? You should start having at least a general idea when looking at the code segments. If not, start studying more ... that means writing code, not staring at mine!

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 Get_Time() 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.

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
;########################################################################

Wow! For somebody so important he sure doesn't do very much. Almost like real life, what do you think?

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 Place_In_Grid(). 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 Test_Collision() and Place_In_Grid() 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
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
DEC    EBX
MOV    ECX, BlockGrid

;=============================
; 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

;========================================
; 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

;=========================================
; Now loop until we have a one in the
; current colum we are working on or we
; reach the top
;==========================================
;=======================
; Get the Current Line
;=======================
XOR    EAX, EAX
MOV    AL, BYTE PTR [EBX]

;=======================
;=======================
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
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
SUB    EAX, Index
MOV    ECX, BlockGrid

;===========================
; 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

;===============================
;===============================

.ENDW

;==================================
;==================================
INC    Index

.ENDW

err:
;===================
; We didn't collide
;===================
return FALSE

done:
;===================
; We collided
;===================
return TRUE

Test_Collision    ENDP
;########################################################################
; END Test_Collision
;########################################################################

The Place_In_Grid() function is the simpler of the two so let's cover that one first. It moves to the location in Grid Memory based upon where our current shape is located. Once there, it simply loops through every row in the frame and, if there is a block in that bit position, it sets the block to TRUE by indicating the current block texture + 1. The reason we had to do ( texture + 1 ) is we use zero to indicate that no blocks are there.

Test_Collision() 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 Update() 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.

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
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
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
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
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
;==========================
;==========================
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
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

;====================
; 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
DEC    EAX    ; 1 to the Left
MOV    ECX, BlockGrid
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
;==========================
;==========================
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
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
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
; Right
MOV    ECX, BlockGrid
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
;########################################################################

These two functions Rotate_Shape() and Move_Shape() are pretty big, so it is good they are twins. Yet, what they do, is fairly cut and dried. Let's cover, just in general, what it is they do.

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 Test_Collision(). 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: Move_Shape(). 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.

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

;==================================
; 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
MOV	EDX, EAX

;==============================
; 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 RTLMoveMemory().

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.

The Final Batters

Our two final hitters are the publicity hounds Draw_Shape() and Draw_Grid(). Below is their code.

[code=auto:0];########################################################################
; 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
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
DEC EAX
MOV ECX, BLOCK_WIDTH
MUL ECX
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, \
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