Win32 Assembly Part 6

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.

[size="5"]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.

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

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

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

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.

[size="5"]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.

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

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.

[size="5"]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.

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

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.

[size="5"]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 ...

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

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 (
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?

[size="5"]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.

;================================================
; This function will test to see if they earned a
; line ... if so it will eliminate that line
; and update our grid of blocks
;================================================

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

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