Jump to content

  • Log In with Google      Sign In   
  • Create Account


Like
0Likes
Dislike

Win32 Assembly Part 6

By Chris Hobbs | Published May 31 2000 06:07 AM in General Programming

eax mov ecx font add text_rect
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?

Okay, time to get back to work. During our last session we added in the totally awesome sound effects (Thanks EvilX -- http://www.evilx.com), and we made some simple screen transitions. I also showed the solution to the rotation problem we had. It may not sound like much, but trust me those things had a significant impact on the game.

Before I get into this article’s topics, allow me to sidetrack a little bit. I have received numerous eMails lately asking me to add feature X, Y, or Z to SPACE-TRIS. As much as I’d like to ... I won’t, and here is why. I simply don’t have the time, or the space to add in everybody’s wishes. Many of the suggestions are very good ones, and, I too have numerous ideas that could be integrated into the game. The trouble is that this article series isn’t about making a commercial-level game. My sole goal is to introduce you, the reader, to game concepts implemented in assembly language.

Often times, a feature asked for is something I have already covered the basis for in this series. At those times, I suggest that you add those features yourself. Other times, I may not have covered that type of concept at all. Those are the things you need to let me know about. Send me an eMail saying "Hey Chris, try and cover blah-blah-blah...". Then, I will attempt to fit it in, or at least add it to my list of needed articles for later. This way, a little bit of everything is covered instead of one main concept and 300 variations of it.

Enough of that, what are we going to cover today? You don’t know! In that case I had better let you know exactly what I am up to. Or should I just leave it as a surprise? Ah, okay ... I’ll let you, and just you, in on the secrets.

We’ll start off by adding in the ability to see the preview piece, which means we’ll have to add a preview piece to our list of needed data (duh!). Once that is taken care of we’ll add the ability to draw text of different font sizes -- this will take a few new routines and alteration of an old one. Then, we’ll write the code to draw text for our level, score, and the current lines we have earned. Finally, we can add the scoring system along with a primitive level system. I had originally hoped to write the code that would save and load our games, but I just didn’t have the time to get it in. So, it looks like that little feature gets pushed off to the last article.

Okay, that is the plan. So, I suppose I should stop chattering and get to the good stuff.


Next Piece Please

Integrating a new piece into the ‘pipeline’ was very easy. Basically, what I wanted was a piece that would stand in line. Then, when the current piece finished dropping, the next piece in the line would become current and the new piece, that was just created, would take it’s place waiting.

So, I started out by copying all the variables the current piece had. Then I just gave them new names to show they were for the next piece and not the current one. Then, I needed to alter the New_Shape() procedure. Take a look at the code I added.

;########################################################################
; New_Shape Procedure
;########################################################################
New_Shape    proc    

    ;================================================
    ; This function will select a new shape at random
    ; for the Next shape and assign the old next
    ; shape values to the current shape
    ;================================================

    ;=================================
    ; Do the swaps if this isn't our
    ; very first piece of the game
    ;=================================
again:
    .if NextShape != -1
   	 m2m    CurShape, NextShape
   	 m2m    CurShapeColor, NextShapeColor
   	 m2m    CurShapeX, NextShapeX
   	 m2m    CurShapeY, NextShapeY
   	 m2m    CurShapeFrame, NextShapeFrame
    .endif

    ;======================================
    ; 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    NextShape, 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    NextShapeColor, eax

    ;================================
    ; Initialize the Starting Coords
    ;================================
    mov    NextShapeX, 5
    mov    NextShapeY, 24

    ;================================
    ; Set the Current Frame Variable
    ;================================
    mov    NextShapeFrame, 0

    ;====================================
    ; Go back to the top and load again
    ; if this was our very first piece
    ;====================================
    .if CurShape == -1
   	 jmp    again
    .endif

done:
    ;=======================
    ; They have a new piece
    ;=======================
    return TRUE

err:
    ;===================
    ; They died!
    ;===================
    return FALSE

New_Shape    ENDP
;########################################################################
; END New_Shape
;########################################################################

Notice that the first thing I do is test to see if NextShape is currently -1. I assign NextShape this value during initialization to show that I need to create two new shapes, one for the current and one for the next. After that special very first iteration though everything runs as normal. I place the values in the next shape into the current shape’s variables. Then, I create everything just as before except I store the values in my next shape instead. At the bottom I test the current shape to see if it is -1. If so, then I know I need to create another shape, so I jump back to the top and do it all over again.

The only other modification I had to make was, as I mentioned, during initialization. At that point, both the current shape and the next shape were set equal to -1 to indicate they needed to be created.


I Can’t See It!

After getting it to create and store the piece, I just needed a way to draw it on the screen. I decided to simply modify the existing Draw_Shape() procedure. The idea was, to have it draw either the current shape, or the next shape, based upon a variable that was passed in. Have a look at the new version.

;########################################################################
; Draw_Shape Procedure
;########################################################################
Draw_Shape    proc    UseNext:BYTE

    ;=======================================================
    ; This function will draw our current shape at its
    ; proper location on the screen or it will draw the next
    ; shape on the screen in the next window
    ;=======================================================

    ;===========================
    ; 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
    ;===================================
    .if UseNext == FALSE
   	 mov    ebx, CurShape
   	 mov    eax, CurShapeFrame
    .else
   	 mov    ebx, NextShape
   	 mov    eax, NextShapeFrame
    .endif
    shl    eax, 2
    add    ebx, eax
    mov    CurLine, ebx

    ;===================================
    ; Set the Starting Row and Column
    ; for the drawing
    ;===================================
    .if UseNext == FALSE
   	 mov    eax, CurShapeX
   	 mov    ebx, CurShapeY
    .else
   	 mov    eax, 2   	 ; X Coord
   	 mov    ebx, 4   	 ; Y Coord
    .endif
    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

   			 ;===========================
   			 ; Adjust the Y coord for
   			 ; certain shapes in the next
   			 ; window since they are off
   			 ; of the center
   			 ;===========================
   			 .if UseNext == TRUE
   				 mov    ecx, NextShape
   				 .if ecx == Offset Square || ecx == Offset Line
   					 sub    YPos, 7
   				 .elseif ecx == offset Pyramid
   					 add    YPos, 15
   				 .else
   					 add    YPos, 5
   				 .endif
   			 .endif

   			 ;=============================
   			 ; Calculate the X coord
   			 ;=============================
   			 mov    eax, DrawX
   			 add    eax, CurCol
   			 dec    eax
   			 mov    ecx, BLOCK_WIDTH
   			 mul    ecx
   			 .if UseNext == FALSE
   				 add    eax, 251
   			 .else
   				 add    eax, 40
   				 ;=============================
   				 ; Now adjust the X coord on a
   				 ; shape by shape basis
   				 ;=============================
   				 mov    ecx, NextShape
   				 .if ecx == offset Square
   					 sub    eax, 12
   				 .elseif ecx == offset Line
   					 add    eax, 25
   				 .elseif ecx == offset L
   					 add    eax, 15
   				 .elseif ecx == offset Back_L
   					 add    eax, 15
   				 .elseif ecx == offset Z
   					 sub    eax, 15
   				 .elseif ecx == offset Back_Z
   					 sub    eax, 15
   				 .endif
   			 .endif
   			 mov    XPos, eax

   			 ;=============================
   			 ; Calculate the surface to use
   			 ;=============================
   			 .if UseNext == FALSE
   				 mov    eax, CurShapeColor
   			 .else
   				 mov    eax, NextShapeColor
   			 .endif
   			 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
    ;===================
    return FALSE

Draw_Shape    ENDP
;########################################################################
; END Draw_Shape
;########################################################################

The start of that code is pretty self-explanatory. It simply decides which variables to use based upon the piece we are drawing. Take note, that the coordinates 2, and 4, are not pixel coordinates. They are the number of 32x32 blocks on the X-axis, and the number of blocks from the bottom on the Y-axis.

There is one major change in the code and that is where I adjust the position of the blocks that are drawn. Because our window we are trying to draw them in is square, but our shapes typically aren’t, we needed a way to center them. So, I decided to hard-code in the coordinate adjustments.

I used a special technique in order to do this though. You’ll notice that I labeled the start of each shape’s declaration in the shape table. Remember when we were declaring the shapes by using bits? Well, all I did was place a label before the start of every new shape. This is very, very powerful. I am now able to address the middle of a huge table by name. Needless to say, this adds to the clarity of what would have been a very difficult thing to understand.

The only exception to this rule was the square. Because the square was the first shape, I couldn’t have two names both at the same place, the first name being, of course, our variable name ShapeTable. So, at the end of ShapeTable I put an equate that said treat ‘Square’ the same as ShapeTable. In code, I could have easily just used ShapeTable directly ... but then it wouldn’t have been as clear as to what I was doing.

Finally, in the main code we call this routine both with TRUE, and with FALSE, so we can have both pieces drawn. The next step is to modify the drawing routine to let us change fonts to draw our text.


The New Text

The text support didn’t require too much alteration. Basically, I wanted to be able to support drawing the text with GDI in different fonts instead of the system default. This is something that I should have planned in from the beginning, but I didn’t. I would like to be able to say I was just saving it for later ... but, the truth is, I plum forgot about it. Oh well, I guess you’ll get to see it now.

The very first thing we have to do is add in support for selecting and deselecting certain fonts. In Windows you specify what font you want to use by selecting it into your object after you create it. This sounds pretty crazy but the code is fairly straightforward. Here are the routines to select and deselect the font.

;########################################################################
; DD_Select_Font Procedure
;########################################################################
DD_Select_Font PROC    handle:DWORD, lfheight:DWORD, lfweight:DWORD,\
   		 ptr_szName:DWORD, ptr_old_obj:DWORD

    ;=======================================================
    ; This function will create & select the font after 
    ; altering the font structure based on the params
    ;=======================================================

    ;=================================
    ; Create the FONT object
    ;=================================
    INVOKE CreateFont, lfheight, 0, 0, 0, lfweight, 0, 0, \
   	 0, ANSI_CHARSET, OUT_DEFAULT_PRECIS, CLIP_STROKE_PRECIS,\
   	 DEFAULT_QUALITY, DEFAULT_PITCH OR FF_DONTCARE, ptr_szName
    MOV    temp, EAX

    ;===================================
    ; Select the font and preserve old
    ;===================================
    INVOKE SelectObject, handle, EAX
    MOV    EBX, ptr_old_obj
    MOV    [EBX], EAX

done:
    ;===================
    ; We completed
    ;===================
    return temp

err:
    ;===================
    ; We didn't make it
    ;===================
    return FALSE

DD_Select_Font    ENDP
;########################################################################
; END DD_Select_Font
;########################################################################

;########################################################################
; DD_UnSelect_Font Procedure
;########################################################################
DD_UnSelect_Font PROC    handle:DWORD, font_object:DWORD, old_object:DWORD

    ;=======================================================
    ; This function will delete the font object and restore
    ; the old object
    ;=======================================================

    ;==================================
    ; Restore old obj and delete font
    ;==================================
    INVOKE SelectObject, handle, old_object
    INVOKE DeleteObject, font_object

done:
    ;===================
    ; We completed
    ;===================
    return TRUE

err:
    ;===================
    ; We didn't make it
    ;===================
    return FALSE

DD_UnSelect_Font    ENDP
;########################################################################
; END DD_UnSelect_Font
;########################################################################

This probably doesn’t mean too much to you right now. But here is how the routines work. In order to select two steps are required. First, we must create a font object. My functions lets you control three different things: size, weight, and font name. The size is how large you want it, the weight controls BOLD and normal, while the name controls the actual font you use. There are many other parameters that can be played with ... I suggest reviewing the Win32 API calls for those parameters. The second step is to ‘select’ that font object into the current device context. The only trick here is we preserve the old object with the pointer that was passed in for that old object. This is all that needs to be done to select a new font.

Our routine top deselect the font is pretty much the same process but in reverse. First we select our old object back into the device context. This step is important because we may have had something else in there that we want to restore. When programming it is best to abide by the adage most of our mothers taught us ... "put it back the way you found it." Anyway, after we select the old object we can delete our current font object and we are finished.

That is all that there is to selecting a new font to use for drawing. But, it doesn’t do much good without some code to put it on the screen.


Wanna See This Too?

I suppose now would be the time to show you the code for drawing our captions on the screen. I simply added a new function to our shapes module. Here it is ...

;########################################################################
; Draw_Captions Procedure
;########################################################################
Draw_Captions    proc    

    ;=======================================================
    ; This function will draw our captions, such as the 
    ; score and the current level they are on
    ;=======================================================

    ;====================
    ; Local Variables
    ;====================
    LOCAL    hFont   	 :DWORD

    ;=====================================
    ; Get the DC for the back buffer
    ;=====================================
    invoke DD_GetDC, lpddsback
    mov    hDC, eax

    ;====================================
    ; Set the font to "IMPACT" at the 
    ; size that we need it
    ;====================================
    invoke DD_Select_Font, hDC, -32, FW_BOLD, ADDR szImpact, ADDR Old_Obj
    mov    hFont, eax

    ;=============================
    ; Setup rect for score text
    ;=============================
    mov    text_rect.top, 161
    mov    text_rect.left, 54
    mov    text_rect.right, 197
    mov    text_rect.bottom, 193

    ;=============================
    ; Draw the Score Text
    ;=============================
    RGB 255, 255, 255
    push    eax
    mov    eax, Score
    mov    dwArgs, eax
    invoke wvsprintfA, ADDR szBuffer, ADDR szScore, Offset dwArgs
    pop    ebx
    invoke DD_Draw_Text, hDC, ADDR szBuffer, eax, ADDR text_rect,\
   	 DT_CENTER or DT_VCENTER or DT_SINGLELINE, ebx

    ;=============================
    ; Setup rect for Level text
    ;=============================
    mov    text_rect.top, 67
    mov    text_rect.left, 102
    mov    text_rect.right, 151
    mov    text_rect.bottom, 99

    ;=============================
    ; Draw the Level Text
    ;=============================
    RGB 255, 255, 0
    push    eax
    mov    eax, CurLevel
    mov    dwArgs, eax
    invoke wvsprintfA, ADDR szBuffer, ADDR szLevel, Offset dwArgs
    pop    ebx
    invoke DD_Draw_Text, hDC, ADDR szBuffer, eax, ADDR text_rect,\
   	 DT_CENTER or DT_VCENTER or DT_SINGLELINE, ebx

    ;=============================
    ; Setup rect for Lines text
    ;=============================
    mov    text_rect.top, 256
    mov    text_rect.left, 90
    mov    text_rect.right, 162
    mov    text_rect.bottom, 288

    ;=============================
    ; Draw the Lines Text
    ;=============================
    RGB 255, 255, 0
    push    eax
    mov    eax, NumLines
    mov    dwArgs, eax
    invoke wvsprintfA, ADDR szBuffer, ADDR szLines, Offset dwArgs
    pop    ebx
    invoke DD_Draw_Text, hDC, ADDR szBuffer, eax, ADDR text_rect,\
   	 DT_CENTER or DT_VCENTER or DT_SINGLELINE, ebx

    ;=============================
    ; Unselect the font
    ;=============================
    invoke DD_UnSelect_Font, hDC, hFont, Old_Obj

    ;============================
    ; Release the DC
    ;============================
    invoke DD_ReleaseDC, lpddsback, hDC

done:
    ;===================
    ; We completed
    ;===================
    return TRUE

err:
    ;===================
    ; We didn't make it
    ;===================
    return FALSE

Draw_Captions    ENDP
;########################################################################
; END Draw_Captions
;########################################################################

I have tried to keep it in the same form as the rest of what I’ve shown you. The code reads from a few module variables to get the current numbers to draw. It then makes a call to set the font how we want. This isn’t anything new I hope. We then set our rectangle for the drawing and make the call. If you don’t remember wvsprintfA() is a function that is used for formatting a string buffer ... almost exactly like sprintf().

The other thing I am doing is setting the color we will use. I don’t know about you but I prefer to make things a little bit varied and stand-out-ish ( <-- Is that even a word???).

In short, this routine just calls upon a few library routines and pieces things together as needed. I can’t remember if I have told you guys, or not ... but programming is like one big jigsaw puzzle. It is just a matter of finding the right pieces and putting them together correctly. There is no one right way to do it and that is why everybody creates different pictures. Make sense?


Scoring and Levels

It is truly amazing how primitive I made this scoring and level system. The thing does about as much as the old Atari games, but hey, it is a start.

Inside the Line_Test() function the code increments a variable which tests itself for a MAX condition. This is where the number of lines is counted. Once that MAX condition is exceeded the number of lines gets reset and the level increased. Then, in our main code, another function we call is the Is_Game_Won() function. It is called to find out if they have gone over the maximum number of levels in the game. In our case, the MAX levels is ten, but you can make it whatever you would like it to be.

The other function we added was one to keep track of the score. As expected it is called Adjust_Score() and performs the same type of adjustment we did for the levels. The only difference is that if the user exceeds the maximum score we simply reset their score to the maximum amount. Nothing fancy, but it works as it is supposed to, which is always a nice side effect. This function is called from the main module based upon how many lines they achieved in one swoop. So, the more lines they eliminate at once the more points they would achieve.

When they have reached the end of the game our main code sets the state to GS_WON and simply restarts them. It is in that section that we would perform credits and special winning sequences. But, I was lacking in both art and creativity when I coded it, so they just restart the game.

Here are the Line_Test(), Adjust_Score(), and Is_Game_Won() functions. I’ll let you sort through the main code yourself and see what alterations I made.

;########################################################################
; 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:
    ;===================
    ; Play the sound
    ;===================
    invoke Play_Sound, Thud_ID, 0

    ;==========================
    ; Adjust their line count
    ;==========================
    inc    NumLines
    .if NumLines >= MAX_LINES
   	 mov    NumLines, 0
   	 inc    CurLevel
    .endif

    ;===================
    ; We earned a line
    ;===================
    return TRUE


Line_Test    ENDP
;########################################################################
; END Line_Test
;########################################################################

;########################################################################
; Adjust_Score Procedure
;########################################################################
Adjust_Score    proc    amount:DWORD

    ;================================================
    ; This function will adjust the score by the 
    ; passed in value if possible, adjusting the
    ; level if necessary
    ;================================================
    mov    eax, amount
    add    Score, eax
    .if Score > MAX_SCORE
   	 mov    Score, MAX_SCORE
    .endif

done:
    ;===================
    ; We earned a line
    ;===================
    return TRUE


Adjust_Score    ENDP
;########################################################################
; END Adjust_Score
;########################################################################

;########################################################################
; Is_Game_Won Procedure
;########################################################################
Is_Game_Won    proc

    ;================================================
    ; This function will return TRUE if we have won
    ; the game and false otherwise
    ;================================================

    .if CurLevel > MAX_LEVEL
   	 return TRUE
    .else
   	 return FALSE
    .endif

Is_Game_Won    ENDP
;########################################################################
; END Is_Game_Won
;########################################################################


Until Next Time...

Whoopie! We are finished with yet another installment. So, have you guys been working on your different versions like I keep hounding you about? I really hope so ... especially since I have a nice little challenge to offer you in our final installment.

Gosh, I can hardly think of anything else to say right now. I am excited to be bringing this series under wraps here soon. I have some things I would like to talk/write about but aren’t really applicable to this series. Ergo, it will be totally cool to see this series end and get into some more advanced stuff. I can tell from some of the letters that I get, that many of you are waiting for that to happen also.

The one thing I do want to mention is it could be a couple of months before my final installment is complete. I have been really pressed for time here lately. Those of you who visit my web-site may have noticed the lack of updates. I wish I had more time right now, but my job is keeping me really, really busy. Anyway, I just wanted to let you know that it was coming, just not as soon as we all would have liked. So ...

As always ... young grasshoppers, until next time ... happy coding.







Comments

Note: Please offer only positive, constructive comments - we are looking to promote a positive atmosphere where collaboration is valued above all else.




PARTNERS