05.04 - Pecking Away At It...

Started by
7 comments, last by Teej 20 years, 9 months ago
Reviewing BASECODE3B In case you''re wondering, after I propose an exercise, I proceed to do the same work that I''m asking you to do. In other words, I grab the latest BASECODE, turn up the music, and start thinking/typing away. And let me tell you, it doesn''t matter how good you are -- there''s still a mental process that makes the code evolve, and it does take time to identify and iron-out all of the kinks. The result of my efforts is called BASECODE3B, and is available wherever other tutorial resources are (the site has changed so many times I''m afraid to take a guess at the time of this writing -- check the NEWS section of the forum for up-to-date location information). You have the option of continuing with your own code, or picking mine up and using that as your new foundation for the next exercise -- whatever works and makes you happy, but I''ll tell you now that I''ve gone to great lengths to comment the heck out of my version of the exercise, and it would serve you well if some things are still a little unclear. The first thing I did was set up some variables to handle the player''s input. I used toggle flags (on/off switches made from booleans) to throttle the keyboard state so that a keypress only registered once, regardless of how long the key was pressed, on everything except for the DOWN key -- they can hold that one down to let the piece fall to the bottom. In the end, I used four variables for the final inputs that are used by the rest of the game (bLeftPressed, bRightPressed, bDownPressed, bRotPressed). Next, I took a look at these keypress flags in order to determine where the player wanted the piece to move/rotate to (if at all). At this point, I had two sets of variables:

G.currX, G.currY, G.currRot - current piece position/orientation
newX,    newY,    newRot    - proposed piece position/orientation 
If the piece isn''t moving this frame, the new variables will contain the same values as the current variables. Otherwise, one of the new variables has a modified value. I decided to only allow one direction input per frame in order to simplify movement processing, but I could have just as easily allowed a combination of left/right, down and rotate. Now for the fun part. I wrote a function called CanPlacePiece() that took as input the proposed position/orientation, and returned either TRUE or FALSE, depending on whether or not the move/rotation was legal. Now, call it intuition, or call it the fact that I''ve written this before, but I realized that eventually this function would also have to take into account other blocks in the play area, and not just play area borders, so I went ahead and incorporated the collision detection for the blocks as well:

bool CanPlacePiece(int xPos, int yPos, int rot)
{
    int x, y;
    bool bFits = true;
 
    for (y = 0; y < PIECE_HEIGHT; y++)
    {
        for (x = 0; x < PIECE_WIDTH; x++)
        {
            if (G.Pieces[G.currPiece][rot][y][x])
            {
                // If any block falls outside of the play area,
                // we can''t move there
                if ( (xPos + x) < 0 ||
                     (xPos + x) >= PLAYAREA_WIDTH ||
                     (yPos + y) >= PLAYAREA_HEIGHT )
                {
                    bFits = false;
                }
                // If there''s already a block where we''d like to place
                // one, the piece can''t be moved here
                else if (G.PlayArea[yPos + y][xPos + x]) bFits = false;
 
                if (!bFits) break;
            }
        }
        if (!bFits) break;
    }
 
    return bFits;
} 
The first if statement in the double for-loop ensures that we''re only doing hit-testing on sections of the 4x4 game peice array that actually contain blocks. Just as there are transparent pixels in an image, there are transparent sections in our 4x4 game piece array -- if you need to, try removing this if statement and playing with BASECODE3B to see what I mean. The interior logic (first if) states, "If ANY block in the game piece falls outside of the play area bounds, the entire piece can''t be moved here". There''s an if-break statement a little farther down that gets us out of the loops if this happens. The second if states, "Also, if there''s already a block in this position on the play area, the entire piece can''t be moved here". Once again, the cascading break statements get us out of the function as soon as we come to a bad conclusion. As a stand-alone function, it works just fine. Unfortunately, if you try using it in our existing BASECODE3A, it will always register a collision. Why? Quite simply, the current game piece is already in the play area, so it''s always hitting itself! You need to first remove the current game piece from the play area before checking the validity of the new game piece''s position/orientation. The solution here is to
  1. Remove the game piece from the play area
  2. Check the validity of the new position/orientation against the play area
  3. Replace the game piece into the play area, either in its new position/orientation (i.e. it fits), or back at its original position/orientation (the move was illegal)
If you follow this algorithm, you have to be sure to draw the piece in the end at all times, since you unconditionally removed it in order to do the collision testing. So long as the proposed position/orientation of the game piece is valid, it''s then time to update our current game piece variables. Finally, when the game piece is redrawn to the play area array, it''s drawn in its proper place. You have to remember to initialize all members of your G structure before using them, so I threw a block of code into Game_Initialize():

// Start a piece at the center of the top of the play area,
// falling one level per second
G.currPiece       = 0;
G.currX           = 5;
G.currY           = 0;
G.currRot         = 0;
G.currSpeed       = 1000;
G.currTimeElapsed = 0;
 
// Place some random blocks in the play area to check our
// collision testing
for (int i = 0; i < 10; i++)
{
    G.PlayArea[(rand() % 10) + 5][rand() % 20] = (rand()%7)+1;
} 
The last little loop was an extra so that I could test the game piece collision against some random blocks in the play area -- the fancy rand() calls are there to make sure that there aren''t any blocks in the first 5 rows of the play area (that could make it hard to move in the beginning), and that I always drop in a block (1-7). At this point, I was still a little uncontent, so I went ahead and added some code to make the game piece fall naturally. This wasn''t asked of you in the previous exercise, but I figure, what the heck! The code is nothing new to you; timers and animation rates were covered in previous articles, but here it is for your perusal:

//------------------------------------------------------------------------
// FALLING PIECE LOGIC
//------------------------------------------------------------------------
 
G.currTimeElapsed += G.diffTickCount;
 
// Suspended time elapsed?
if (G.currTimeElapsed >= G.currSpeed)
{
    // Time to lower the next piece.  We accomplish this by forcing
    // a DOWN keypress.  When it comes time to move the piece (see
    // MOVEMENT PROCESSING below), this is the first direction that
    // is checked, ensuring that a falling piece overrides any other
    // keypresses.
    G.currTimeElapsed -= G.currSpeed;
    bDownPressed = true;
} 
As you can probably deduce, I''m pretending that the player touched the DOWN key every time the piece''s falling timer elapses (in BASECODE3 I set currSpeed to 1000 (msec. = 1 sec.). As I''ve stated in the comments, I later make sure that if the piece wants to move down, I disregard whatever the player wanted the piece to do for this frame. Simple as pie. This concludes the review of BASECODE3B -- everything in this article from this point onwards will be work towards BASECODE3C, which we''re about to start on! Cut, Copy, Pase, Slice and Dice So far, we''ve been hacking together proof-of-concept demos in order to demonstrate our ability to code isolated areas of our game project, and our basecode is starting to slowly, but, eventually, resemble the game we''re trying to develope. At some point, however, we have to be able to step back and organize our code a little better, before it ends up being one huge Game_Main() function with a mess of code blocks. I''m going to propose some changes in the next few paragraphs, as they pertain to BASECODE3B. You can either adopt similar changes in your own work (where applicable), or have the default BASECODE3B ready to work with. As the title of this section suggests, we''re going to be moving some code around, making slight changes here and there, and generally shaking things up a bit so that when the dust settles, we have a foundation for the actual complete game and not a simple demo. Game State of Denial Our first order of business is to keep Game_Main() from becoming the recepticle of the entire game''s code, and move the actual gameplay to a single game state, so that we can incorporate some of the standard ''phases'' such as a main menu or game over screen. Alright, so let''s define some game states:

// Game States
enum GameState {GAMESTATE_MENU,
                GAMESTATE_STARTING,
                GAMESTATE_RUNNING,
                GAMESTATE_GAMEOVER}; 
Now, we''ll build a function for each of these states:

// In GAMEMAIN.CPP
void GS_Menu()
{
}
void GS_Starting()
{
}
void GS_Running()
{
}
void GS_GameOver()
{
} 
Hmm...can''t forget the actual variables that hold the game states...

// In GLOBALS.H
struct
{
    // ... Other stuff
    
    GameState currGameState,
              prevGameState;
 
} G; 
I''ll show you how to use the prevGameState variable in a moment. For now, let''s continue by re-shaping our Game_Main():

void Game_Main()
{
    HRESULT hRet;
 
    // Timing
    static DWORD lastTickCount = timeGetTime();
    DWORD        thisTickCount;
 
    //------------------------------------------------------------------------
    // TIMING
    //------------------------------------------------------------------------
 
    thisTickCount   = timeGetTime();
    G.diffTickCount = thisTickCount - lastTickCount;
    lastTickCount   = thisTickCount;
 
    //------------------------------------------------------------------------
    // KEYBOARD INPUT
    //------------------------------------------------------------------------
 
    // Get current keyboard state
    while (hRet = G.lpDIKeyboard->GetDeviceState(256, G.KeyState)
                  == DIERR_INPUTLOST)
    {
        if (FAILED(hRet = G.lpDIKeyboard->Acquire())) break;
    }
 
    //------------------------------------------------------------------------
    // GAME STATE PROCESSING
    //------------------------------------------------------------------------
 
    switch (G.currGameState)
    {
    case GAMESTATE_MENU:
        GS_Menu();
        G.prevGameState = GAMESTATE_MENU;
        break;
 
    case GAMESTATE_STARTING:
        GS_Starting();
        G.prevGameState = GAMESTATE_STARTING;
        break;
    
    case GAMESTATE_RUNNING:
        G.prevGameState = GAMESTATE_RUNNING;
        GS_Running();
        break;
 
    case GAMESTATE_GAMEOVER:
        GS_GameOver();
        G.prevGameState = GAMESTATE_GAMEOVER;
        break;
    }
 
    //------------------------------------------------------------------------
    // RENDERING
    //
    // Flip the surfaces.  If we''ve somehow lost our surface memory,
    // restore all surfaces and reload our images.  If we''ve lost our
    // surfaces, there''s no reason to flip anymore this frame...
    //------------------------------------------------------------------------
 
    hRet = G.lpDDSPrimary->Flip(NULL, 0);
    if (FAILED(hRet)) RestoreAllSurfaces();
} 
Boy, isn''t that nice and neat looking! Now that''s what I call organization. Here, we''ve handled timing and keyboard input first, so that any other game state can take advantage of these values (hey, now you know why diffTickCount and KeyState are in the G structure...). Then, it''s off to the switch statement, which sends the execution to the proper game state function. It is assumed that each game state function is responsible for preparing the backbuffer in any way they see fit, so upon completing the appropriate game state function it''s time to render -- flip the surface to the display. Second verse, same as the first... Just in case I didn''t give the use of prevGameState a good mention earlier, I''ll tackle it again here. First, I''ll illustrate what a typical set of executions looks like for a game state: From within GS_Starting():

First Run:  currGameState = GAMESTATE_STARTING, prevGameState = GAMESTATE_MENU
Second Run: currGameState = GAMESTATE_STARTING, prevGameState = GAMESTATE_STARTING
Third Run:  currGameState = GAMESTATE_STARTING, prevGameState = GAMESTATE_STARTING
Fourth Run: currGameState = GAMESTATE_STARTING, prevGameState = GAMESTATE_STARTING
.
.
. 
For the first time in each game state function, and for the first time only, prevGameState contains the name of the game state we''ve come from. This allows us to write code like this:

void GS_Starting()
{
    if (G.prevGameState != GAMESTATE_STARTING)
    {
        // One-time initialization goes here
    }
    
    // Rest of function...
} 
This one-time initialization block could be use for starting timers or initializing other static variables, as it''s guaranteed to be executed only once, and at the very first frame this game state has possession of the processor. One example would be if you wanted to display "Get Ready Player 1" for a period of one second -- here''s a possible implementation:

void GS_Starting()
{
    static readyTickCount;
    
    if (G.prevGameState != GAMESTATE_STARTING)
    {
        readyTickCount = 0;
    }
    // ...
    // Display "Get Ready Player 1"...
    // ...
    
    readyTickCount += G.diffTickCount;
    if (readyTickCount >= 1000) G.currGameState = GAMESTATE_RUNNING;
} 
This game state function will correctly display "Get Ready Player 1" for one second, regardless of from which game state was active previously, or how many times throughout the execution of the program this game state becomes active. C''est bon, n''est pas? For BASECODE3B, all you need to do is move all of the game-related code to GS_Running(), and move the initialization code from INITTERM.CPP relating to the game data into GS_Starting(). Here''s what I threw into GS_Menu():

void GS_Menu()
{
    HDC hDC;
 
    if (KEYDOWN(DIK_1)) G.currGameState = GAMESTATE_STARTING;
 
    EraseBackground();
 
    G.lpDDSBack->GetDC(&hDC);
 
    SetTextColor(hDC, RGB(255, 255, 255));
    SetBkColor(hDC, RGB(0, 0, 0));
 
    TextOut(hDC, 260, 100, "Blocks ''n Lines!", 16);
    TextOut(hDC, 255, 130, "Press ''1'' to begin.", 19);
 
    TextOut(hDC, 240, 160, "(LEFT, RIGHT) Move the piece", 28);
    TextOut(hDC, 240, 180, "(UP) Rotate the piece", 30);
    TextOut(hDC, 240, 200, "(DOWN) Hold to lower", 20);
    TextOut(hDC, 240, 220, "(P) PAUSE", 9);
    TextOut(hDC, 240, 240, "(ESC) EXIT", 10);
 
    G.lpDDSBack->ReleaseDC(hDC);
} 
See how clean things get when you organize a bit? Hmm... I seem to be promising a PAUSE feature, so I may as well flush that out now:

//------------------------------------------------------------------------
// PAUSE MECHANISM
//------------------------------------------------------------------------
 
if (KEYDOWN(DIK_P))
{
    if (bPauseToggle == false)
    {
        bPauseToggle = true;
 
        // Pressing ''P'' causes G.bPaused to toggle on/off
        G.bPaused = !G.bPaused;
    }
}
else bPauseToggle = false;
 
// If paused, display a message on the screen
if (G.bPaused)
{
    G.lpDDSBack->GetDC(&hDC);
    
    SetTextColor(hDC, RGB(255, 200, 200));
    SetBkColor(hDC, RGB(0, 0, 0));
 
    TextOut(hDC, 260, 200, "-- PAUSED --", 12);
 
    G.lpDDSBack->ReleaseDC(hDC);
 
    // We''re done for this frame
    return;
} 
This code block belongs to the very beginning of GS_Running(), and stops the rest of the function from running if we''re paused. The code is straightforward...actually, a little too straightforward. When I tried pausing my program and task-switching to another application and back again, I found nothing on the display but "--- PAUSED ---" and some other garbage pixels. Even worse, when I unpaused the game, the background image was gone! Sure enough, I had forgotten to reload BACKGROUND.BMP in RestoreAllSurfaces(), which is called whenever there''s a problem (like people task-switching out) and DirectX loses surface memory. So, I added in the proper call to reload the background bitmap, and threw in a couple of calls to the pause code:

if (G.bPaused)
{
    EraseBackground();
    DrawPlayArea();
    
    // Rest is same as above...
} 
Folks, this is what game development is all about; evolving the code. So, now my pause works great, and I move on to GS_Starting():

void GS_Starting()
{
    // Start a piece at the center of the top of the play area,
    // falling one level per second
    G.currPiece       = 0;
    G.currX           = 5;
    G.currY           = 0;
    G.currRot         = 0;
    G.currSpeed       = 1000;
    G.currTimeElapsed = 0;
    G.currScore       = 0;
 
    // And the next piece is...
    G.nextPiece = 3;
 
    // Clear the game array
    memset(G.PlayArea, 0, sizeof(int) * PLAYAREA_WIDTH * PLAYAREA_HEIGHT);
 
    // Place some random blocks in the play area to check our
    // collision testing
    for (int i = 0; i < 10; i++)
    {
        G.PlayArea[(rand() % 10) + 5][rand() % 20] = (rand()%7)+1;
    }
 
    EraseBackground();
 
    G.currGameState = GAMESTATE_RUNNING;
} 
Since we don''t have a proper game yet, I need to create some default values for the game variables... I could have just as easily threw this code into GS_Running() and used the one-time initialization (ala prevGameState), by the way. Well, only one game state left, and that''s GS_GameOver():

void GS_GameOver()
{
    HDC  hDC;
    char szText[80];
 
    // ''SPACE'' restarts the game
    if (KEYDOWN(DIK_SPACE)) G.currGameState = GAMESTATE_MENU;
 
    EraseBackground();
 
    G.lpDDSBack->GetDC(&hDC);
 
    // White text on a black background
    SetTextColor(hDC, RGB(255, 255, 255));
    SetBkColor(hDC, RGB(0, 0, 0));
    
    wsprintf(szText, "Final Score: %d", G.currScore);
    
    TextOut(hDC, 240, 60, szText, strlen(szText));
    TextOut(hDC, 200, 100, "Game Over!  Hit SPACE to restart.", 33);
 
    G.lpDDSBack->ReleaseDC(hDC);
} 
I know, I know, there''s no scoring yet! Well, we''ll remember to create a score variable in our G structure at least. Now, I''m not saying that you could paste all of this code in and run it yet, but we''re getting close... Continuity It''s important to make sure that your game has a way of getting to each game state. GS_Menu() moves along to GS_Starting() as soon as the ''1'' key is pressed, and GS_Starting() hands the torch over to GS_Running() right away, but what about GS_Running()? How does GS_GameOver() ever get called? For the time being, let''s decide that the game (umm, demo) is over when a block can move no farther down. This would coincide with when a game piece comes to rest and a new game piece is introduced in the original Tetris. Obviously, this means we''ll have to go hunting through our existing code and see if we can pinpoint the information we need to make this statement true. To begin with, arm yourself with a flag: bool bStopped = false; In English, we can describe this situation as, "A game piece has stopped when it attempts to move downward but cannot". In BASECODE3B, that scenario is tested with the call to CanPlacePiece(). Here''s the before and after: Before:

// Can we move the piece legally?
if (CanPlacePiece(newX, newY, newRot) == false) bMoving = false; 
After:

// Can we move the piece legally?
if (CanPlacePiece(newX, newY, newRot) == false)
{
    bMoving = false;
 
    if (bDownPressed)
    {
        bStopped = true;
        G.currGameState = GAMESTATE_GAMEOVER;
    }
} 
Well, the bStopped flag is pretty useless to us here, but it will come in handy soon, so we''ll leave it in. Now we have complete game continuity, through all game states. Putting it All Together You''re lucky I''m such a nice guy -- BASECODE3C is available for download, and contains all of the updates in this article, up to this point, including:
  • a routine to display the next game piece to play (preview piece)
  • the play area has been enlarged four rows upwards to make room for new game pieces entering
  • probably other little things I''ve since forgotten
I figure I''ll save you from fiddling with all of the code snippits in this article... But, sometimes I''m not such a nice guy. That freebee is going to cost you -- the payment is: "Finish the basic gameplay." Muuu ha ha ha ha ha ha ha.... It''s actually not as bad as you think! Here''s what you''ll need to do:
  1. Grab BASECODE3C (unless you''re using your own creation)
  2. Use the bStopped flag to trigger new random pieces to start falling once the old one comes to a rest
  3. Also with the bStopped flag, implement a function that counts and removes completed lines from the bottom of the play area
  4. Tally the number of completed lines using G.currScore (make up some scoring system)
  5. Determine the end-game condition. In English, it''s when a game piece is stopped and all (or part) of it is over the top row of the play area.
The way I see it, the only interesting part is the calculation and removal of completed lines. If your brain happens to think a certain way, the routine becomes easy. If your brain leads you in the wrong direction, it will cost you in both time and lines of code. This is creative programming, and there are no rules, so do whatever it takes. Later, we''ll all compare algorithms. Well, don''t just sit there, get going! Questions? Comments? Screaming cries for help? Reply to this topic.
Advertisement
Teej,
I''ve got only one question with your code. In math, x and y is pretty much x and y no matter how you deal with it. x refers to where on the horizon, while y refers to how high or low from that horizon point. x is first, y is second. Seeing your loops is confusing because since I''ve been brainwashed (by the man) to think this way, it looks like all of your loops are backwards (y first then x). Obviously it''s too late to go back and change now, but I''m still wondering why your initial array wasn''t grid[10][20]? In a double for you would then have:
  for (int x=0; x<10; x++)  for (int y=0; y<20; y++)     grid[x][y]=SomeColorInt;  


which would draw the grid starting at the top left and working your way down, and then right. I realize this is a matter of 6 of one a half dozen of another, but if we were to move to 3D suddenly, our world coordinates wouldn''t make any sense in the code. So this just leaves me with a question: is swapping the coordinates a common practice, or did you just do it this way because that''s how the code was. I work for a software co. and find that most of the time, the unfortunate answer is that when code is confusing, it''s confusing because someone wrote it that way to begin with and everyone who inherits doesn''t want to change it because it already works (I call it legacy code syndrome .

thanks,
videns
EZ
Videns,

It''s because of the cache. The cache, if you don''t know, is a little bit of memory on your processor that is used for extremely fast memory access. When your program needs some data that is residing in RAM, it goes to the RAM, grabs it, and sends it to the processor. This takes on the order of 100 cycles, a very long time (if the data isn''t in RAM and the program has to go to the disk, well, you might as well go get a sandwich because it''s going to be a while). Since memory access takes so friggin long, your computer (being the smart devil that it is) grabs more than what you asked for when it goes to memory. It is hoping that the next thing you are going to ask for is the data in the next address in RAM (and usually, that is what you ask for next). Since the processor already has this data (in its cache), it only takes a cycle or two to access it, quite a speed up.
What does all of this have to do with arrays? Well, a long time ago, in a place far, far away, someone decided that programs would use a "row access" scheme (I don''t know if that''s what they really call it, I kinda made it up). So two dimensional arrays are stored in your one dimensional memory like this:

{row0, row1, row2, row3, etc...}

Now, you don''t have to write your arrays like this, but many (read: almost all) arrays already coded are written this way (most importantly, the arrays that your video driver uses to display pixels on the screen). The result of all of this is that when you are traversing a 2D array, your outer loop should be flipping you between rows, and your inner loop should be flipping you between columns, so that the common case is that your next data item is already in the cache.

That was a crash course in caching and array manipulation. I know I didn''t cover everything, and I probably screwed some stuff up, so you should go look it up somewhere if you''re interested. I can''t say for sure that this is why Teej initializes his arrays the way that he does, but it is why I and many other programmers do.

FragLegs
Videns : FragLegs is correct -- it all has to do with successive memory locations. Let's take our Pieces array as an example:

int Pieces[7][4][4][4];

We know that array names are actually pointers to memory (i.e. the beginning of the array memory), so we could create a pointer to traverse all of the memory belonging to the array in the order that it exists in memory (7*4*4*4 = 448 ints). Here's our starting point:

Pieces[0][0][0][0]

I'm going to move my imaginary pointer from the beginning of the array, int by int, until I've reached the end. Every pointer position as it moves through cooincides with a set of array indicies, so let's look at the relationship between the two:
Pointer Position (0-447)  Array Indicies ([n][n][n][n])------------------------  -----------------------------0                         0 0 0 01                         0 0 0 12                         0 0 0 23                         0 0 0 34                         0 0 1 0 5                         0 0 1 1 6                         0 0 1 2 7                         0 0 1 38                         0 0 2 0 9                         0 0 2 1...                       ...445                       6 3 3 1446                       6 3 3 2447                       6 3 3 3 

As you can see, the array indicies accumulate from right to left, and not the other way around. So, let's look at a typical array:

Array[x][y]

Keep in mind that the computer has no concept of x and y per se ; it only knows to create x rows of y elements. YOU are the one that decides that this memory is going to represent rows and columns of a grid or whatever. It all depends on how you're going to be accessing array elements. If you are just seeking out a single element, it doesn't matter at all which order the indicies are. However, if you are traversing this array row-by-row (in a physical sense), the poor computer has to skip 'y' elements for every x element to be read.

Want a simple analogy? How would you like it if your computer took a file you were saving to disk and chopped it up into a thousand pieces, saving each piece to a different area of the hard drive?

So, to (finally) answer your question, the x and y are swapped in my code examples because I'm accomodating the computer's method of laying out array memory -- instead of my left-to-right, top-to-bottom way of thinking, because it's a lot faster to read the memory sequentially than it is to play hop-scotch or leapfrog all over the array's memory. Try thinking of memory rows as solid pieces of memory, and you won't get confused when writing the nested loops backwards etc.

Oh, and BTW, FragLegs and I both know that if you write your code to take advantage of the proper sequential order of array memory, you can drop the array altogether and use pointers instead, which can be a lot faster.

Teej


Edited by - Teej on July 23, 2001 12:35:02 PM
Hey Teej, just wanted to say that i found your forum here 2 days ago and already I have learned more than I ever thought I would know about DirectX. I just about finished my tetris game last night and am starting a mario-style platform game.

I do have a question though:

I wrote the wonderful tetris game and the world was perfect. I go to see how big the release build executable is as opposed to the debug build, which was a fair amount, but when I went to run my tetris game I got completely un-expected behavior.

My pieces would begin to drop (be painted in their original spot) but then would immediatly drop the next piece on top of it, and keep doing this, so at the end of 2 seconds i now have like 25 pieces all on top of each other. This is extremely strange, considering there is no debug build related code or anything.

I don''t know if maybe i am just not allowed to build release builds, or if this is a common occurence, or if I should upload my source code because you''ve never heard of anything like this.

-Terrax
Teej and FragLegs,

Oh that''s a Very Cool Explanation. I knew it already, but forgot to think like a computer again. I catch myself doing that from time to time still...the implant''s new That, and I''ve been spoiled for too long by C++ and MFC doing the back-end work for me. Thanks!

videns
EZ
Grammar Police.

"n''est-ce pas?"
Am I missing it while looking or is it not discussed?

Im looking for the code where you look if something is bloking a piece from going down.

I havent got the files so maybe someone tell me what to do?
Pipo DeClown:

I''m not sure if you''re still reading this stuff or not, but I love to help people so on the off chance that you get this. I hope it helped. You asked about the block collision detection. That''s done in CanPiecePlace. He commented it, but it might have been a bit confusing. Basically he get the coordinates for the new piece location (G.currY++). Then he calls CanPiecePlace. CanPiecePlace first checks boundaries and then checks to see if the block would overlap any blocks that are in the game area. Hence, that is your collision detection.

I''m sure you probably went through this and finished it already, but like I said I like to help. I just found this forum a week ago and I think it''s great! It''s just what I needed to get me started. After this it''s probably a remake of this so that it uses Object Oriented programming and networking possibly. Then Pong and we''ll see where I go from there.

Good luck Pipo.

Your local Game Wizard Apprentice,
theSparko
We are what we repeatedly do. Success in life is not an act, but a habit.

This topic is closed to new replies.

Advertisement