Techno-Plaza
Site Navigation [ News | Our Software | Calculators | Programming | Assembly | Downloads | Links | Cool Graphs | Feedback ]
 Main
   Site News

   Our Software

   Legal Information

   Credits

 Calculators
   Information

   C Programming

     Introduction to C

     Keyboard Input

     Graphics Intro

     Slider Puzzle 1

     Functions

     Pointers

     Dynamic Memory

     Slider Puzzle 2

       Part I

       Part II

       Part III

     Structures

     Bit Manipulation

     Advanced Pointers

     File I/O

     Graduate Review

   Assembly

   Downloads

 Miscellaneous
   Links

   Cool Graphs

   Feedback Form

C Programming Lessons

TIGCC Programming Lessons

Lesson Review 2 - Functions, Pointers, and Dynamic Memory

Step 2a - Programmatic Analysis

Since we have not done a review lesson in some time (at least 3 months if you have been checking the site regularly), let me reiterate how I handle review lessons. First, we do not cover every line of code. This is because we are only reviewing concepts in a broader context. By now, you should already understand the concepts that are being presented. If you need more information about a specific concept, please consult the lessons as they go into more detail about function usage, and the background surrounding the use of the functions presented. So, with that in mind, let's continue with the analysis.

#include "slider.h" // include the slider puzzle header file

Remember from lesson 4 that we will often create our own header files for the purpose of containing specific information pertaining to our program within a special file. These header files include our function prototypes, and useful global variable declarations. In this case, we put our sprite declarations inside the header file along with our function prototypes.

Let's begin the function analysis with the _main() function, since it's always a good place to start.

Step 2b - Analysis of the _main() function

void _main(void) {
      int *x = NULL, *y = NULL, *puzzle = NULL;
      const char *title = "DMA Error", *error = "Unable to allocate array space";
      
      // allocate memory for the x position array
      if ((x = (int *)calloc(16,sizeof(int))) == NULL) {
            DlgMessage(title,error,BT_OK,BT_NONE);
            return;
      }
      
      // allocate memory for the y position array
      if ((y = (int *)calloc(16,sizeof(int))) == NULL) {
            DlgMessage(title,error,BT_OK,BT_NONE);
            
            // free the memory used by the x positions
            free(x);
            
            return;
      }
      
      // allocate memory fro the puzzle piece position array
      if ((puzzle = (int *)calloc(16,sizeof(int))) == NULL) {
            DlgMessage(title,error,BT_OK,BT_NONE);
            
            // free the memory used by the position arrays
            free(x);
            free(y);
            
            return;
      }
      
      // seed the random number generator
      randomize();
      
      // initialize the puzzle
      initPuzzle(puzzle);
      
      // loop and play the game
      while ((DlgMessage("Slider Puzzle 2.0","Press ENTER to play, ESC to quit.",
                  BT_OK,BT_NONE)) == KEY_ENTER) {
            newGame(x,y,puzzle);
      }
      
      // free the memory allocated for the arrays
      free(x);
      free(y);
      free(puzzle);
}

The _main() function is where program execution begins. Of course, we already know that, so what does our _main() function do.

The _main() function is the place to take care of global program needs. Data initialization, dynamic memory allocation (where possible), and any other kind of initialization that needs to be done before the program can start doing what it needs to do. Then we call helper functions to handle the main program execution. After the program is finished executing, we perform any necessary closing tasks and then exit the program.

// allocate memory for the x position array
if ((x = (int *)calloc(16,sizeof(int))) == NULL) {
      DlgMessage(title,error,BT_OK,BT_NONE);
      return;
}
      
// allocate memory for the y position array
if ((y = (int *)calloc(16,sizeof(int))) == NULL) {
      DlgMessage(title,error,BT_OK,BT_NONE);
      
      // free the memory used by the x positions
      free(x);
      
      return;
}
      
// allocate memory fro the puzzle piece position array
if ((puzzle = (int *)calloc(16,sizeof(int))) == NULL) {
      DlgMessage(title,error,BT_OK,BT_NONE);
      
      // free the memory used by the position arrays
      free(x);
      free(y);
      
      return;
}

In the first segment of the program, we allocate memory for the integer arrays we will use during the course of the program. While we are doing this, we also check to make sure all the arrays got the memory they requested, and if any of the allocation requests fail, we send the user an error message inside a dialog box. Remember that if we get one of the arrays and then fail to get the next one, then we still need to free the memory from the first one before we can exit the program. If you have other questions about how this works, you should consult lesson 6.

// seed the random number generator
randomize();
      
// initialize the puzzle
initPuzzle(puzzle);

In line with our initialization phase, we need to make sure our random number is properly seeded so we can get random numbers. Then we call the initPuzzle() function to initialize our puzzle array. This function simply puts all the initial values into the puzzle array so we know what pieces are where. Since we are on that function anyway, let's go ahead and analyze it. It's very simple so this will be quick.

void initPuzzle(int *puzzle) {
      int loop;

      // initialize the puzzle positions
      for (loop = 0; loop < 15; loop++) {
            puzzle[loop] = loop;
      }

      // make the last piece the blank piece
      puzzle[15] = -1;
}

As we can see in this simple function, all the pieces of the puzzle array just get initialized to the puzzle piece numbers. This is just a handy way of keeping track of where the pieces are in the puzzle relative to one another. Remember that since we only use 15 pieces, the last piece will be made blank. The only other thing of note in this function is that, since puzzle is an array, which means it is also a pointer, the values can be changed inside the function and the changes will still be there when we return from the function call. This is why we use pointers. If you need more help with pointers and arrays, please consult lesson 5.

// loop and play the game
while ((DlgMessage("Slider Puzzle 2.0","Press ENTER to play, ESC to quit.",
            BT_OK,BT_NONE)) == KEY_ENTER) {
      newGame(x,y,puzzle);
}

The next part of the _main() method is the heart of the program. This is the basic loop that lets us play new games over and over, or exit the program, as we choose. So, this should be pretty easy, since we know how dialogs work (basically). Remember that dialog boxes return the key press corresponding to the OK and CANCEL values we selected. So, our while loop will ask the user to press ENTER to play, or press ESC to quit. So long as the user presses ENTER at this dialog box, the newGame() function will be called. Once the user stops pressing ENTER at this dialog box, we stop looping through our new games.

// free the memory allocated for the arrays
free(x);
free(y);
free(puzzle);

If we reached this point and exited our game loop, then we need to perform our closing tasks. These include memory deallocation for the most part, but it is conceivable that we could have other tasks that are necessary to perform before the program exits, although this is by far the most common.

Well, that pretty well covers what we need to go over in the _main() function, so let's take a look now at the newGame() function, where the heart of the program is. This is where new games get created and play out.

Step 2c - Analysis of the newGame() method

void newGame(int *x, int *y, int *puzzle) {
      int key, loop;
      int position = 0, currentPiece = 0, on = 1, done = 0, winner = 0;

      // draw the background screen
      drawScreen();

      // draw a new puzzle
      drawPuzzle(x,y,puzzle);
      
      // set the first piece to be piece 0
      currentPiece = puzzle[position];
      
      // loop until the puzzle is completed, or until the
      // user presses ESC
      while (!done) {
            // set the timer interval to 1/2 second
            OSFreeTimer(USER_TIMER);
            OSRegisterTimer(USER_TIMER, 10);
            
            // wait for the timer to expire, or the user to press a key
            while (!OSTimerExpired(USER_TIMER) && !kbhit());
            
            if (kbhit()) {
                  // if the user pressed a key, grab it
                  key = ngetchx();
            } else {
                  // otherwise, erase old keystrokes
                  key = 0;
            }
            
            // handle keystrokes
            if (key == KEY_ESC) {
                  // ESC means they quit
                  done = 1;
            } else if (key == KEY_ENTER) {
                  if (position+1 <= 16) {
                        // try to move the piece right
                        // if the right position is vacant
                        if (puzzle[position+1] == -1 && x[currentPiece] != (16*4-16)) {
                              // make sure that the old piece is still drawn
                              if (!on) {
                                    Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                                pieces[currentPiece], LCD_MEM, SPRT_XOR);
                                    on = 1;
                              }

                              // erase the piece
                              Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                          pieces[currentPiece], LCD_MEM, SPRT_XOR);
                              
                              // move the piece right 1 position (16 pixels)
                              x[currentPiece]+=16;
                        
                              // reset the puzzle position holders
                              puzzle[position+1] = puzzle[position];
                              puzzle[position] = -1;
                              position++;
                              currentPiece = puzzle[position];

                              // draw the piece at the new position                              
                              Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                          pieces[currentPiece], LCD_MEM, SPRT_XOR);
                              
                              // don't try to move any other direction
                              continue;
                        }
                  } 
                  
                  if (position-1 >= 0) {
                        // try to move the piece left
                        // if the left position is vacant, and we haven't already moved
                        if (puzzle[position-1] == -1 && x[currentPiece] != 0) {
                              // make sure the piece is drawn, so it can be properly erased
                              if (!on) {
                                    Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                                pieces[currentPiece], LCD_MEM, SPRT_XOR);
                                    on = 1;
                              }
                              
                              // erase the piece
                              Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                          pieces[currentPiece], LCD_MEM, SPRT_XOR);
                              
                              // move the piece 1 position left (16 pixels)
                              x[currentPiece]-=16;
                              
                              // reset the puzzle position holders
                              puzzle[position-1] = puzzle[position];
                              puzzle[position] = -1;
                              position--;
                              currentPiece = puzzle[position];
                              
                              // draw the piece at the new location
                              Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                          pieces[currentPiece], LCD_MEM, SPRT_XOR);
                              
                              // don't try to move any other direction
                              continue;
                        }
                  } 
                  
                  if (position-4 >= 0) {
                        // try to move the piece up
                        // if the up position is vacant, and we haven't already moved
                        if (puzzle[position-4] == -1 && y[currentPiece] != 0) {
                              // make sure the piece is drawn, so it can be properly erased
                              if (!on) {
                                    Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                                pieces[currentPiece], LCD_MEM, SPRT_XOR);
                                    on = 1;
                              }
                              
                              // erase the piece                              
                              Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                          pieces[currentPiece], LCD_MEM, SPRT_XOR);
                              
                              // move the piece 1 position up (16 pixels)
                              y[currentPiece]-=16;
                              
                              // reset the puzzle position holders
                              puzzle[position-4] = puzzle[position];
                              puzzle[position] = -1;
                              position-=4;
                              currentPiece = puzzle[position];
                              
                              // redraw the piece at the new location
                              Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                          pieces[currentPiece], LCD_MEM, SPRT_XOR);
                              
                              // don't try to move any other direction
                              continue;
                        }
                  }
                  
                  if (position+4 <= 15) {
                        // try to move the piece down
                        // if the down position is vacant, and we haven't already moved
                        if (puzzle[position+4] == -1 && y[currentPiece] != (16*4-16)) {
                              // make sure the piece is drawn, so it can be properly erased
                              if (!on) {
                                    Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                                pieces[currentPiece], LCD_MEM, SPRT_XOR);
                                    on = 1;
                              }
                              
                              // erase the piece
                              Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                          pieces[currentPiece], LCD_MEM, SPRT_XOR);
                              
                              // move the piece one position down (16 pixels)
                              y[currentPiece]+=16;
                              
                              // reset the puzzle position holders
                              puzzle[position+4] = puzzle[position];
                              puzzle[position] = -1;
                              position+=4;
                              currentPiece = puzzle[position];
                              
                              // redraw the piece at the new location
                              Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                          pieces[currentPiece], LCD_MEM, SPRT_XOR);
                              
                              // no need to continue since there are no other directions...
                        }
                  }
            } else if (key == KEY_LEFT) {
                  // make sure the piece is still drawn, so it's still there when we move
                  if (!on) {
                        Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                    pieces[currentPiece], LCD_MEM, SPRT_XOR);
                        on = 1;
                  }

                  // if there is a left position to move to                  
                  if (position > 0) {
                        // move left
                        position--;
                        
                        // if we are at the empty slot, move left again
                        if (puzzle[position] == -1 && position > 0) {
                              position--;
                        
                        // if we can't move left anymore, start at the end
                        } else if (puzzle[position] == -1 && position == 0) {
                              position = 15;
                        }
                  } else {
                        // set the position to the end
                        position = 15;
                        
                        // if we found the empty slot, move left one
                        if (puzzle[position] == -1) {
                              position--;
                        }
                  }

                  // reset the puzzle position holder
                  currentPiece = puzzle[position];
            } else if (key == KEY_RIGHT) {
                  // make sure the piece is drawn, so we can move away safely
                  if (!on) {
                        Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                    pieces[currentPiece], LCD_MEM, SPRT_XOR);
                        on = 1;
                  }
                  
                  // if there is a position to move right to
                  if (position < 15) {
                        // move one position to the right
                        position++;
                        
                        // if we hit the empty slot, move right again
                        if (puzzle[position] == -1 && position < 15) {
                              position++;
                        
                        // if we hit the empty slot and the edge, start over
                        } else if (puzzle[position] == -1 && position == 15) {
                              position = 0;
                        }
                  // else, we need to start over at the beginning
                  } else {
                        // move to the beginning
                        position = 0;
                        
                        // if we hit the empty slot, move right again
                        if (puzzle[position] == -1) {
                              position++;
                        }
                  }
                  
                  // reset the puzzle position holder
                  currentPiece = puzzle[position];
            } else if (key == KEY_UP) {
                  // make sure the piece is drawn, so we can move safely
                  if (!on) {
                        Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                    pieces[currentPiece], LCD_MEM, SPRT_XOR);
                        on = 1;
                  }
                  
                  // if there is an up position to move to
                  if ((position - 4) >= 0) {
                        // move up one position
                        position-=4;
                        
                        // if we hit the empty slot
                        if (puzzle[position] == -1) {
                              // if there is another up position, go there
                              if ((position - 4) >= 0) {
                                    position-=4;
                              // otherwise, run to the bottom row
                              } else {
                                    position = 16 - (4 - position);
                              }
                        }
                  // else, move to the bottom row
                  } else {
                        // set position at the bottom row
                        position = 16 - (4 - position);
                        
                        // if we hit the empty slot, move up again
                        if (puzzle[position] == -1) {
                              position-=4;
                        }
                  }
                  
                  // reset the puzzle position holder
                  currentPiece = puzzle[position];
            } else if (key == KEY_DOWN) {
                  // make sure the piece is still drawn, so we can move safely
                  if (!on) {
                        Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                    pieces[currentPiece], LCD_MEM, SPRT_XOR);
                        on = 1;
                  }
                  
                  // if there is a down position to move to
                  if ((position + 4) <= 15) {
                        // move down one position
                        position+=4;
                        
                        // if we hit the empty slot
                        if (puzzle[position] == -1) {
                              // if there is another down position
                              if ((position + 4) <= 15) {
                                    // move to the next down position
                                    position+=4;
                              // otherwise, goto the top row
                              } else {
                                    position%=4;
                              }
                        }
                  // else, move to the top row
                  } else {
                        // move to the top row
                        position%=4;
                        
                        // if we hit the empty slot
                        if (puzzle[position] == -1) {
                              // move down one more time
                              position+=4;
                        }
                  }
                  
                  // reset the puzzle position holder
                  currentPiece = puzzle[position];
            }
            
            // blink the current piece so we know which one it is
            on = !on;
            Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                        pieces[currentPiece], LCD_MEM, SPRT_XOR);
            
            // set the winner to one, then try to disqualify it with
            // the for loop. it's easier to reach a false conclusion than
            // a true conclusion, so start true, and work towards false
            winner = 1;

            // test for winner
            // loop through all game pieces, and see if their positions
            // match their piece numbers. If they do, then the player won
            // if we find any piece that is not right, we have no winner
            // and we stop the for loop
            for (loop = 0; loop < 15; loop++) {
                  if (y[loop] != ((loop / 4) * 16) || x[loop] != ((loop % 4) * 16)) {
                        winner = 0;
                        break;
                  }
            }

            // if we have a winner, make sure the piece is drawn and
            // set done to yes
            if (winner) {
                  // if we win, then we are done, obviously
                  done = 1;
                  
                  // make sure the piece is drawn so it looks nice
                  if (!on) {
                        Sprite16(x[currentPiece], y[currentPiece], PIECE_HEIGHT, 
                                    pieces[currentPiece], LCD_MEM, SPRT_XOR);
                        on = 1;
                  }
                  
                  // tell the player they won
                  DlgMessage("Winner", "You have completed the puzzle!", BT_OK, BT_NONE);
            }
      }
}

As you can clearly see, the newGame() function is the largest function in the program. In a complex program, you would probably have more than one function that is this large, or you would break the function down into multiple parts. However, in a program as simple as the slider puzzle, such a breakdown is unnecessary.

// draw the background screen
drawScreen();

The first thing we take care of in the newGame() function is to make a call to the drawScreen() function. The drawScreen() draws the background elements of the game and clears the screen for us. Let's take a look at exactly what the drawScreen() function does.

inline void drawScreen(void) {
      // clear the screen
      ClrScr();
      
      // draw the game control strings
      DrawStr(0, 70, "Use Arrows to Move Pieces", A_NORMAL);
      DrawStr(0, 80, "Press ENTER to Slide Piece", A_NORMAL);
      DrawStr(0, 90, "Press ESC to Quit", A_NORMAL);
      
      // draw the game information strings
      DrawStr(67, 0, "Slider 2.0", A_NORMAL);
      
      FontSetSys(F_4x6);
      DrawStr(80, 10, "Copyright (C) 2001", A_NORMAL);
      DrawStr(80, 18, "John David Ratliff", A_NORMAL);
      
      DrawStr(67, 30, "Created by Techno-Plaza", A_NORMAL);
      DrawStr(75, 38, "for Lesson Review 2", A_NORMAL);
      DrawStr(67, 50, "Learn more at our website", A_NORMAL);
      DrawStr(70, 58, "www.technoplaza.net", A_NORMAL);

      // reset font to normal size
      FontSetSys(F_6x8);
}

We can see that the first unique thing about the drawScreen() function is that it is inline. Remember from lesson 4 that inline functions are not actually separated from the places they are called, but all the code from the function is just added in place of the function call. This means for short functions, or functions that do not get called often, inline functions are faster and more efficient than regular functions. Although there is a slight advantage to declaring this function inline, there is no reason why it couldn't be a normal function other than the fact that I wanted to recap inline functions in this review lesson. If you remember the first slider puzzle program, you will see that these strings are just the strings we had displayed in the first program, with some small differences in version number, copyright date, and which review it was created for. However, this function is very simple, so it doesn't really need much explanation.

// draw a new puzzle
drawPuzzle(x,y,puzzle);

Having drawn the background screen, we now need to draw our puzzle. This is the purpose of the drawPuzzle() function, as I'm sure you already surmised. We pass it three parameters, the x position array, the y position array, and the puzzle array. This lets us fill up the arrays with the correct values for the new puzzle. In addition to drawing the puzzle, the drawPuzzle() function creates a new puzzle before drawing, so every call to drawPuzzle() will draw a different puzzle. Let's take a short look at the drawPuzzle() function.

void drawPuzzle(int *x, int *y, int *puzzle) {
      int xLoop, yLoop, piece = 0;
      
      // randomize the puzzle before drawing
      randomizePuzzle(puzzle);

      // loop through the rows of the puzzle to draw them
      for (yLoop = 0; yLoop < (16 * 4); yLoop += 16) {
            for (xLoop = 0; xLoop < (16 * 4); xLoop += 16) {
                  // only attempt to draw valid pieces
                  if (puzzle[piece] != -1) {
                        Sprite16(xLoop, yLoop, PIECE_HEIGHT, 
                                    pieces[puzzle[piece]], LCD_MEM, SPRT_XOR);
                        
                        // mark the position of the piece
                        x[puzzle[piece]] = xLoop;
                        y[puzzle[piece]] = yLoop;
                  }
                  
                  piece++;
            }
      }
}

Okay, this function is not too complex, but there are some pieces worth exploring. You'll notice that the first thing the function does is call the randomizePuzzle() function and pass it the puzzle array. This is how we ensure each puzzle drawn will be new, and this seemed the logical place to put the randomizePuzzle() function since we would never draw a puzzle before randomizing it. We can examine the randomizePuzzle() function here:

void randomizePuzzle(int *puzzle) {
      int loop, randNum, temp;

      // randomize the puzzle pieces
      for (loop = 0; loop < 16; loop++) {
            // select a random puzzle piece
            randNum = random(16);

            // replace the old piece with the new piece
            temp = puzzle[loop];
            puzzle[loop] = puzzle[randNum];
            puzzle[randNum] = temp;
      }
}

As you can see from the randomizePuzzle() function, this is the same thing we did in the first slider puzzle version. Remember that we already seeded our random number generator in the _main() method, and we only need to do that once per program. The method simply loops through all the pieces in the array, selects a random number for each piece, and swaps the piece at the random position with the piece we are currently at in the loop. All in all, we haven't done anything complicated.

The rest of the drawPuzzle() function is just the same as it was from the first slider puzzle version, only now it has been encapsulated within a function, which is the proper way to handle it.

The last part I think we need to examine in the newGame() function is embedded in the big while loop.

// don't try to move any other direction
continue;

You can see this on all of the direction movements when the user presses enter and we attempt to slide the piece. Remember that in the first slider puzzle example, we used a variable named 'moved' to see if we had already moved the piece in some direction, implying there is no more need to move it again. While there is nothing wrong with that method, it uses extra variable space, and it's really not a good idea to waste memory.

You remember from lesson 4 when we used the switch statement? Remember that every time we had a case block, we had to end it with a break keyword. This told the computer where to stop executing code for that block. Well, we can use break keywords within loops, too. It works the same way, when we encounter a break keyword, we exit the body of the loop. Well, that is nice, but we do not want to exit out of the body of the loop, we want to skip to the next loop iteration (return to the beginning of the loop). So, instead of the break keyword, there is a special keyword called the continue keyword which will allow us to do just that. The continue keyword skips to the closing brace } of the while loop (or for loop, or whatever kind of loop we have), and then we start the loop again at the condition statement. Since we don't use a variable here, it's more efficient, because we only have to do a jump, and a single jump is better than wading through numerous conditional statements checking conditions that we know will never be true. Since we already moved the piece in one direction, there is no need to do anything else in this iteration of the loop. The logical thing is to skip to the end, and the continue keyword is our means of achieving this goal.

Continue with the Analysis in Part III

 

Copyright © 1998-2007 Techno-Plaza
All Rights Reserved Unless Otherwise Noted

Get Firefox!    Valid HTML 4.01!    Made with jEdit