Creating a game application – Stage 2

This prototype extends the one described in the previous section to make a single player game that includes a 'paddle' drawn on left-hand edge of the screen. The position of the paddle is determined by a potentiometer (ADC1) fitted to the evaluation board that provides a voltage input to the Analog-Digital (A-D) Converter.

  1. Begin by creating a new folder named helloPong_c2v0, and within this, a new project. Configure the RTE to include board support software components for the Graphic LCD (API) and A/D Converter (API). Alternatively, clone the folder helloBounce_c2v0, from the previous recipe and modify the RTE. Use Resolve to automatically load any missing libraries.
  2. Copy helloBounce.c and helloBounce.h from the previous recipe, rename them helloPong.c and helloPong.h, and include these in your project. Change the #include in helloPong.c, and replace helloBounce.h with helloPong.h. Build the program and test it as before.
  3. Add #include "Board_ADC.h" and call ADC_Initialize() in main().
  4. Add a function named update_ball(), and move the code concerned with updating the ball's position and collision detection into the body of the function. This tidies up the superloop and makes the main function much easier to read.
  5. Define constants and declare global data structures in helloPong.c to hold the position of the ball, paddle, and information about the Game.
    #define wait_delay HAL_Delay
    #define WIDTH   GLCD_WIDTH
    #define HEIGHT  GLCD_HEIGHT
    #define CHAR_H  GLCD_Font_16x24.height
    /* Character Height (in pixels) */
    #define CHAR_W  GLCD_Font_16x24.width
    /* Character Width (in pixels)  */
    #define BAR_W   6             /* Bar Width (in pixels) */
    #define BAR_H   24            /* Bar Height (in pixels) */
    #define T_LONG   1000                   /* Long delay */
    #define T_SHORT 5                     /* Short delay */
    
    typedef struct {
          int dirn;
          int x; 
          int y; 
    } BallInfo;
    
    typedef struct {
        int x;
        int y;
    } PaddleInfo;
    
    typedef struct {
      unsigned int num_ticks;
      BallInfo ball;
      PaddleInfo p1;
    } GameInfo;
    
    /* Function Prototypes */
    void game_Initialize(void);
    void update_ball (void);
    void update_player (void);
    void check_collision (void);
  6. Declare a global variable in file pong.c:
    GameInfo thisGame;

    Tip

    The ball's position is now accessed as thisGame.ball.x.

  7. Declare the function game_Initialize(). This function initializes the values of the global variables.
    /*------------------------------------------------
    *  game_Init()
    *  Initialize some game parameters.
    *-------------------------------------------------*/
    void game_Initialize(void) 
      init_pstn.dirn = 1;
      init_pstn.x = WIDTH-CHAR_W)/2;
      init_pstn.y = (HEIGHT-CHAR_H)/2;
      thisGame.ball = init_pstn;
      thisGame.p1.x = 0;
      thisGame.p1.y = 0;
      thisGame.num_ticks = T_SHORT;
    }
  8. Create a new function named check_collision(), and copy the code concerned with collision detection into this function. Modify the function check_collision() to check for collisions between the ball and the paddle as well as collisions between the ball and screen edge.
    /*--------------------------------------------------
    *  check_collision(void)
    *  check for contact between ball and screen 
    *  edges/bat and change direction accordingly
    *---------------------------------------------------*/
    void check_collision(void) {
      /* check collision with RH vertical screen 
                                      edge OR P1 paddle */
      if ((thisGame.ball.x == BAR_W) || 
           thisGame.ball.x == (WIDTH-CHAR_W)) {  
          
        switch (thisGame.ball.dirn) {                                                   
           case 0: thisGame.ball.dirn =                                 
                    (thisGame.ball.dirn+4)%8;
                  break;
          case 1: thisGame.ball.dirn = 
                    (thisGame.ball.dirn+2)%8;
                  break;
          case 3: if ( (thisGame.ball.y >=
                          thisGame.p1.y-CHAR_H) &&
                        (thisGame.ball.y <= 
                           (thisGame.p1.y+BAR_H)) )
                       thisGame.ball.dirn = 
                        (thisGame.ball.dirn+6)%8;
                  else 
                   /* empty statement */  
                    break;
           case 4: if ( (thisGame.ball.y >= 
                        thisGame.p1.y-CHAR_H) && 
                          (thisGame.ball.y <= 
                            (thisGame.p1.y+BAR_H)) )
                        thisGame.ball.dirn =
                         (thisGame.ball.dirn+4)%8;
                   else
                    /* empty statement */;
                   break;
            case 5: if ( (thisGame.ball.y >= 
                           thisGame.p1.y-CHAR_H) && 
                         (thisGame.ball.y <= 
                            (thisGame.p1.y+BAR_H)) ) 
                      thisGame.ball.dirn =       
                         (thisGame.ball.dirn+2)%8;
                    else 
                      /* empty statement */;
                    break;
            case 7: thisGame.ball.dirn = 
                      (thisGame.ball.dirn+6)%8;
                    break;
         }
      }
      /* check collision with horizontal screen edge */
      if ((thisGame.ball.y < 0) || 
            thisGame.ball.y > (HEIGHT-CHAR_H)) {
        switch (thisGame.ball.dirn) {
          case 1: thisGame.ball.dirn = 
                    (thisGame.ball.dirn+6)%8;
                  thisGame.ball.y++;
                  break;
          case 2: thisGame.ball.dirn = 
                    (thisGame.ball.dirn+4)%8;
                  thisGame.ball.y++;
                  break;
          case 3: thisGame.ball.dirn = 
                    (thisGame.ball.dirn+2)%8;
                  thisGame.ball.y++;
                  break;
          case 5: thisGame.ball.dirn = 
                    (thisGame.ball.dirn+6)%8;
                  thisGame.ball.y--;
                  break;
          case 6: thisGame.ball.dirn = 
                    (thisGame.ball.dirn+4)%8;
                  thisGame.ball.y--;
                  break;
          case 7: thisGame.ball.dirn = 
                    (thisGame.ball.dirn+2)%8;
                  thisGame.ball.y--;
                  break;
        }
      }
    }
  9. Add the following code fragment to the function update_ball():
    /* reset position */
    if (thisGame.ball.x<BAR_W) {	    
      wait_delay (T_LONG);
      /* Erase Ball */
      GLCD_DrawChar( thisGame.ball.x, thisGame.ball.y, ' '); 
      thisGame.ball = init_pstn;
    }
  10. Define GLCD_customFont_16x24 in the file GLCD_customFont.c, and add this to the project.
    #include "Board_GLCD.h"
     
    static const uint8_t customFont_16x24_h[] = {
    /* PONG PADDLE */
      0x00, 0x3F, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x3F, 
      0x00, 0x3F, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x3F,
      0x00, 0x3F, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x3F, 
      0x00, 0x3F, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x3F,
      0x00, 0x3F, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x3F, 
      0x00, 0x3F, 0x00, 0x3F, 0x00, 0x3F, 0x00, 0x3F,  
    };
    
    GLCD_FONT GLCD_customFont_16x24 = {
      16,                         ///< Character width
      24,                         ///< Character height
      0,                          ///< Character offset
      1,                          ///< Character count
    customFont_16x24_h            ///< Characters bitmaps
    };
  11. Define the function update_player() by adding the following code fragment:
    /*---------------------------------------------
    * update_player(unsigned int *)
    * Read the ADC and draw the player 1's paddle    
    *----------------------------------------------*/
    void update_player(void) {
    
      int adcValue; 
      static int lastValue = 0;
    
      ADC_StartConversion();
      adcValue = ADC_GetValue ();
      adcValue = (adcValue >> 4) * (HEIGHT-BAR_H)/256;
      /* Erase Paddle */
      GLCD_DrawChar (0, lastValue, ' ');    
      /* Draw Paddle */
      GLCD_SetFont (&GLCD_customFont_16x24);
      GLCD_DrawChar (0, adcValue, 0x00 );   
      GLCD_SetFont (&GLCD_Font_16x24);
      lastValue = adcValue;
      thisGame.p1.y = adcValue;
    }
  12. Build the project, download, and run.

There's more…

  1. We can tidy the code by moving the function prototype and data structure declarations to a header file called helloPong.h, and include this in pong.c with a #include preprocessor directive.
    /*--------------------------------------------------
     * Recipe:  helloPong_c1v0
     * Name:    helloPong.h
     * Purpose: pong function prototypes and defs
     *--------------------------------------------------
     *
     * Modification History
     * 06.02.14 Created
     * 09.12.15 Updated (uVision5.17 + DFP2.6.0)
     *
     * Dr Mark Fisher, CMP, UEA, Norwich, UK
     *--------------------------------------------------*/
    #ifndef _PONG_H
    #define _PONG_H
    
    #define wait_delay HAL_Delay
    #define WIDTH	GLCD_WIDTH
    #define HEIGHT	GLCD_HEIGHT
    #define CHAR_H  GLCD_Font_16x24.height                 
    /* Character Height (in pixels) */
    #define CHAR_W  GLCD_Font_16x24.width                 
    /* Character Width (in pixels)  */
    #define BAR_W   6          /* Bar Width (in pixels) */
    #define BAR_H    24       /* Bar Height (in pixels) */
    #define T_LONG  1000                /* Long delay */
    #define T_SHORT 5                  /* Short delay */
    
    typedef struct {
        int dirn;
        int x; 
        int y; 
      } BallInfo;
    
    typedef struct {
        int x;
        int y;
    } PaddleInfo;
    
    typedef struct {
      unsigned int num_ticks;
      BallInfo ball;
      PaddleInfo p1;
    } GameInfo;
    
    /* Function Prototypes */
    void game_Initialize(void);
    void update_ball (void);
    void update_player (void);
    void check_collision (void);	
    
    #endif /* _PONG_H */
  2. The function declarations game_Initialize(), update_ball(), update_player(), and check_collision() can be moved to a file called pong_utils.c, which shares the header pong.h.

How it works…

The data structures defined within pong.h define three new compound data types which build on the primitive types such as char, integer, and so on, which are part of the language. A global variable thisGame stores all the data used in the application. The main file helloPong.c is shown in step 6. New functions game_Initialize(), update_ball(), update_player(), and check_collision() have been defined within the file pong_utils.c (and delay has also been moved) to declutter main and improve the readability of the code. The function prototypes are shown in step 9.

The function game_Initialize( ) writes the initial values to the global structs, gameInfo and init_pstn(). The function update_player() (step 10) reads the A-D converter, and draws the paddle. Since the paddle may move in large increments, we must explicitly erase the paddle, and redraw it in a new position. The static qualifier is used to ensure that the variable lastValue persists after the function has terminated (that is, it behaves rather like a global variable, although its scope is local to the function). It is important to understand the scoping rules for variables. Variables declared within a function (so-called automatic variables) can only be changed by assignments within the function. But variables declared outside a function have global scope, and can be accessed by any function declared within the same file. The variable gameInfo is a global variable and can be accessed by any function declared in helloPong.c, and because of the extern declaration, by any function declared in pong_utils.c.

The functions named check_collision() and update_ball() are similar to those described in the previous section but with some important additions. When the ball moves in directions 3, 4, or 5, we need to check for a collision with the paddle; modifications necessary to achieve this are shown in step 8. If the ball fails to make contact with the paddle, then a clause in update_ball() holds the ball in its current position for a few seconds, and then restarts the game (see step 9).

The paddle itself can be drawn by declaring our own 'paddle' character bitmap in file GLCD_customFont.c, and by using GLCD_DrawChar() to render it to the screen. The code for checking collisions needs to be extended to include collisions between the ball and the inner vertical edge of the paddle. These can only occur when the ball direction is from right to left (that is, direction codes 3, 4 and 5). We'll need variables to represent the position of the paddle (as we do in case of the ball). As we now have quite a few variables, it's a good opportunity to introduce a data structure that can be used to group them together. The C struct provides us with a mechanism for achieving this. Information about the ball are declared in a struct called ballInfo. The information associated with the paddle is declared in paddleInfo and that about the game in gameInfo, within helloPong.h

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.138.37.20