Original Post
Preface
At this moment, you should have a modified version of BASECODE3 that displays blocks inside of the play area. If you don''t, now is not the time to start any bad habits, like say, laziness or confusion, so it''s really in your best interest to create a working demo from the exercise in 05.02 before proceeding.
If you insist that you don''t need to do the work because it''s pathetically beneath your skill level, or you just plain can''t figure it out, I''ve supplied BASECODE3a on my webpage which will bring you right up to speed.
In my version of the project, I''ve simply filled a 2-dimensional array with random numbers between 0-7, with 0 being ''blank''. Here''s a quick list of what I did:
- Since I don''t plan on blitting off-screen (i.e. everything I blit is guaranteed to be completely on the display), I''ve removed the clipper. The reason is simple -- BltFast() doesn''t work with a clipper, and it''s an easier to use (and theoretically faster) method than Blt().
- Code for loading BACKGROUND.BMP was added. Just look for RESOURCE.BMP code and imitate what you see.
- I used a typical double-for-loop (x,y) for rendering the play area.
X X XX XX X X X XX XX X XX X XXX XX X XX X XX (I) (L) (J) (O) (T) (Z) (S)I''ve labeled them with letters that ''kind of'' resemble them (the ''Z'' and ''S'' are a bit of a stretch), and I''m sure that you all have seen these pieces in action before. Our next job is to feed the shape of these pieces into our game, and it should seem natural to suggest another array -- an array of pieces. At its highest level, we want to be able to access the correct piece by using the piece number as an array index, like so: PIECE Pieces[7]; What about the PIECE data type? How about this: int PIECE[4][4]; Stick them together and you have: int Pieces[7][4][4]; Make sure that you understand how this array was formed - it''s an array of 4x4 arrays. Let''s assume that I said, "And there you go". While you''re busy drawing in the pieces, it will likely occur to you that you could draw some of these pieces four different ways because they rotate in the game! Hmm, we''d better work out a plan for that...here are two options that come to mind:
- Store each piece in all four rotations
- Rotate the piece on-the-fly (i.e. as the game is playing)
0000 0100 0000 0100 1111 0100 1111 0100 0000 0100 0000 0100 0000 0100 0000 0100 0000 0000 0000 0000 0220 0220 0220 0220 0220 0220 0220 0220 0000 0000 0000 0000 0000 0000 0000 0000 0300 0300 3330 0300 3330 0330 0300 3300 0000 0300 0000 0300 0400 0000 4400 0040 0400 4440 0400 4440 0440 4000 0400 0000 0000 0000 0000 0000 0050 0000 0550 0000 0050 0500 0500 5550 0550 0555 0500 0050 0000 0000 0000 0000 0000 0600 0000 0600 0660 0660 0660 0660 6600 0060 6600 0060 0000 0000 0000 0000 0000 0070 0000 0070 7700 0770 7700 0770 0770 0700 0770 0700 0000 0000 0000 0000There are seven sections, each with four 4x4 grids for the piece data. Since the blocks are numbered 1-7 by color, these are the numbers I use in the grids. The trick when drawing each of the rotations for a piece is to try and imagine the piece rotating in-place, and then seeing if it looks natural. At this stage, I''m not sure if they''ll look right, but who cares -- I can always edit this file later, once I see the pieces on the display and try rotating them. For the loading of this data file, we''ll need to read this file a row at a time and make sure that the values end up in the correct section of the Pieces array -- here''s what I mean: Pieces[2][1][y][x] ..would refer to the third piece (0,1,2), second rotation (0,1) (remember, all array indicies start at zero!). I was considering leaving this to you as an exercise, but I''ve got other things in store for you, so here you go:
bool LoadPieces()
{
FILE *fp;
char szBuffer[100];
int iCurrPiece = 0,
iCurrRotation = 0,
iCurrRow = 0,
iCurrColumn = 0,
iCurrPos = 0;
// Open the file for reading
fp = fopen("pieces.dat", "r");
if (!fp) return false;
// Process the file line by line
while (fgets(szBuffer, sizeof(szBuffer), fp) != NULL)
{
// Skip empty lines, or any line that doesn''t start with 0-9
if (szBuffer[0] < ''0'' || szBuffer[0] > ''9'') continue;
// Process all four sets of digits (piece rotation rows)
iCurrPos = 0;
for (iCurrRotation = 0; iCurrRotation < NUM_ROTATIONS; iCurrRotation++)
{
// Process each digit in the set
for (iCurrRow = 0; iCurrRow < PIECE_WIDTH; iCurrRow++)
{
// Convert this digit from a character to a number and store
G.Pieces[iCurrPiece][iCurrRotation][iCurrColumn][iCurrRow]
= szBuffer[iCurrPos++] - ''0'';
}
// Skip the space between rotation rows
iCurrPos++;
}
// Move to the next row for the piece
iCurrColumn++;
// Do we have all four rows for the current piece?
if (iCurrColumn == PIECE_HEIGHT)
{
// We''re done the current piece
iCurrColumn = 0;
iCurrPiece++;
}
}
// Close the file
fclose(fp);
return true;
}
May as well do a quick run-through on the code: The data file was designed to be easy to draw in, which means that the code reading it needs to do a little fancy footwork. The file is being read line-by-line, which means that we''re getting a single row of a game piece for all four rotations. With a little forethought, the proper set of for loops comes into focus -- four rows (outer loop), and four blocks per row (inner loop). You could have just as easily wrapped this up into another couple of for loops for the columns and pieces, but here I decided to read data through a single while loop, and that means manually keeping track, column-by-column, of what column we''re on, and what piece each particular column of data is for. Finally, since there are spaces between rows, I skip these spaces by advancing the current position in the line buffer, and ignore entire columns altogether if they don''t start with a digit. Consequently, you could place comment lines in this file without any problems.
Ready to drop this function into your BASECODE3(a)? Here''s the steps:
- Drop the function into INITTERM.CPP
- Add a prototype for it at the top of INITTERM.CPP so it can be used anywhere in the file
- Add a call to this function in Game_Initialize(), after the standard init calls
- Add the following defines to GLOBALS.H:
// Play Area Dimensions (blocks) #define PLAYAREA_WIDTH 10 #define PLAYAREA_HEIGHT 20 // Block Dimensions (pixels) #define BLOCK_WIDTH 16 #define BLOCK_HEIGHT 16 // Play Area Position #define PLAYAREA_OFFSET_X 241 #define PLAYAREA_OFFSET_Y 97 // Piece Information #define NUM_PIECES 7 #define PIECE_WIDTH 4 #define PIECE_HEIGHT 4 #define NUM_ROTATIONS 4
- Add the following to your G structure:
// Game data int Pieces[NUM_PIECES][NUM_ROTATIONS][PIECE_HEIGHT][PIECE_WIDTH]; int PlayArea[PLAYAREA_HEIGHT][PLAYAREA_WIDTH];
static bool bRotateToggle = false;
bool bRotatePressed = false;
if (KEYDOWN(DIK_UP))
{
if (bRotateToggle == false) bRotateToggle = bRotatePressed = true;
}
else bRotateToggle = false;
Here''s a crude graph showing how it works:
KeyDown: 0 0 1 1 1 1 1 0 0 0 0 1 1 1 1 1 1 0 0 0 RotateToggle: f f T T T T T f f f f T T T T T T f f f RotatePressed: f f T f f f f f f f f T f f f f f f f fTake a moment and go through the code, and keep in mind that bRotatePressed starts off being false every frame; in other words, it only makes it to true if the key wasn''t down on the last frame (bRotateToggle == false), and then reverts to false for the rest of the sequence (until the key is released and pressed again). This code snippit should be repeated for any key that is meant to be read once per press. For the down arrow key, which will allow the player to drop the currently active piece, you don''t want to use this code -- we''ll just set the appropriate flag every frame: if (KEYDOWN(DIK_DOWN)) bDownPressed = true; It looks like we''re in good shape to move our active game piece, so let''s get to the actual piece placement... Piece Movement, Round I We may as well dump in the variables we''re going to be using...add these to your G structure:
int currPiece; // Current piece int currX; // x-Pos of current piece int currY; // Y-Pos of current piece int currRotation; // Rotation angle of current piece (0-3) int currSpeed; // Falling speed int currTimeElapsed; // How long current piece has been suspendedLet''s go over what each variable is to be used for:
- currPiece: the currently active game piece, numbers 1-7
- currX, currY: the current position of the game piece in the play area, from (0,0) to PLAYAREA_WIDTH, PLAYAREA_HEIGHT (20, 20)
- currRotation: the number of the current rotation, 0-3
- currSpeed: the rate of speed that the block falls, in milliseconds
- currTimeElapsed: your standard ticker for determining when it''s time for the piece to fall one block downward