Pascal Worked Example: Bouncing Ball: Iteration 2

From SwinBrain

In this iteration I plan to add the ball and allow it to move across the screen.

Contents

Data Required

To implement this we will need to store the location of the ball and its movement information. This will require four variables representing the x and y location of the ball, and the movement can be stored as the amount to change x, and the ammount to change y: dx and dy.

Structure

We can introduce a DrawBall routine to draw the ball. To move the ball we can introduce two functions: GetNewX and GetNewY. We will also need a routine to clear the ball from the screen, this can be called ClearBall.

This gives us the following structure chart.

Examining this we can see that DrawBorderCharacter, DrawBall, and ClearBall all perform very similar tasks. They goto a certain location and draw a character. We could adjust this so that these were all implemented as a more general routine. The new structure chart is shown below.

GetNewX & GetNewY

These functions will return the new X and Y locations after a single move. These will ensure that the ball does not move beyond the edge of the box. Both have the same basic logic shown below.

newX := x + dx;
if newX out of range then
  result := x;
else
  result := newX;

Cut 1

For the first cut lets just refactor the DrawBorderAt routine and adjust the code to use the new DrawAt routine. The result is shown below.

//This is a game where a ball is placed within a designated
//area of the screen. The ball will have a specified velocity
//and will move around within the area. When the ball collides
//with the edge of the area it should change direction. 
//Striking the bottom/top of the area will reverse the y 
//movement, while striking the left/right will reverse the
//x movement.
program BouncingBall;
uses CRT;
 
const
  BORDER_START_X = 2;
  BORDER_START_Y = 2;
  BORDER_WIDTH = 80 - BORDER_START_X - 1; //-1 for right border
  BORDER_HEIGHT = 25 - BORDER_START_Y - 1;
  BORDER_CHARACTER = #178;
  
//Draws a specified character at a given XY location.
//
// @param X The x location to draw at
// @param Y The y location to draw at
// @param character	The character to be drawn
// 
//Side Effects:
// * Draws to the screen
procedure DrawAt(x, y: Integer; character: Char);
begin
  GotoXY(x, y);
  Write(character);
end;
 
//Draws the border around the game area.
//
// @param title The title to be written above the border
//
//Side Effects:
// * Writes to the console
procedure DrawBorder(title: String);
var
  x, y: Integer;
begin
  GotoXY(BORDER_START_X, 1);
  Write(Title);
  
  //Draw the top + bottom borders
  for x := BORDER_START_X to BORDER_START_X + BORDER_WIDTH do
  begin
    //Top
    DrawAt(x, BORDER_START_Y, BORDER_CHARACTER);
    DrawAt(x, BORDER_START_Y + BORDER_HEIGHT, BORDER_CHARACTER);
  end;
  
  for y := BORDER_START_Y to BORDER_START_Y + BORDER_HEIGHT do
  begin
    DrawAt(BORDER_START_X, y, BORDER_CHARACTER);
    DrawAt(BORDER_START_X + BORDER_WIDTH, y, BORDER_CHARACTER);
  end;
end;
 
 
procedure Main();
begin
  DrawBorder('Bouncing Ball');
  
end;
 
begin
  Main();
end.

Cut 2

Now we can get the location and drawing code to work. At the end of this cut the ball will appear at a fixed location on the screen. This only requires you to update the Main procedure. We need to add in the x and y variables to store the location, initialize these and then use DrawAt to draw the ball. We have also added a BALL_CHARACTER constant that is equal to #2. The for main is shown below.

procedure Main();
var
  x, y: Integer;
begin
  x := BORDER_START_X + 1;
  y := BORDER_START_Y + 1;
	
  DrawBorder('Bouncing Ball');
  
  DrawAt(x, y, BALL_CHARACTER);
end;

Cut 3

Next we can add the code to introduce the movement. This will require a number of changes. We will need to:

  1. Store the dx and dy values
  2. Write the GetNewX and GetNewY functions
  3. Add a loop that allows the movement to be repeated in Main.

We will tackle this in two parts. Firstly getting it to work for a change in x then adding the code to change y. The first changes are shown below.

//Calculates the new X location for the ball given a specified x and
//dx. This ensures that the ball remains in the screen.
//
// @param x	The current x location
// @param dx	The current movement amount
// @returns	The new x location for the ball
function GetNewX(x, dx: Integer): Integer;
var
  newX: Integer;
begin
  newX := x + dx;
  if (newX <= BORDER_START_X) or (newX >= BORDER_START_X + BORDER_WIDTH) then
  begin
    //movement goes off screen... dont move
    result := x;
  end
  else
  begin
    result := newX;
  end;
end;
 
procedure Main();
var
  x, y, dx, dy: Integer;
begin
  x := BORDER_START_X + 1;
  y := BORDER_START_Y + 1;
  dx := 1;
  dy := 1;
	
  DrawBorder('Bouncing Ball');
  
  repeat
    DrawAt(x, y, BALL_CHARACTER);
    Delay(100);
    DrawAt(x, y, ' ');
    x := GetNewX(x, dx);  	
  until KeyPressed();
end;

We can then add similar code to determine the new y location. A snapshot of the execution is shown below.

Refactoring

At the end of this iteration we can examine the code to see if there is any need to refactor it in any way. An examination of the code shows that GetNewX and GetNewY are very similar. We could introduce a GetNewLocation function that also accepts the boundary values and can be used for both the x and y values.

Also at this point I turned the cursor off as it is not needed and distracts from the overall view. The final code and structure chart for this revision is below.

//This is a game where a ball is placed within a designated
//area of the screen. The ball will have a specified velocity
//and will move around within the area. When the ball collides
//with the edge of the area it should change direction. 
//Striking the bottom/top of the area will reverse the y 
//movement, while striking the left/right will reverse the
//x movement.
program BouncingBall;
uses CRT;
 
const
  BORDER_START_X = 2;
  BORDER_START_Y = 2;
  BORDER_WIDTH = 80 - BORDER_START_X - 1; //-1 for right border
  BORDER_HEIGHT = 25 - BORDER_START_Y - 1;
  BORDER_CHARACTER = #178;
  BALL_CHARACTER = #2;
  
//Draws a specified character at a given XY location.
//
// @param X The x location to draw at
// @param Y The y location to draw at
// @param character The character to be drawn
// 
//Side Effects:
// * Draws to the screen
procedure DrawAt(x, y: Integer; character: Char);
begin
  GotoXY(x, y);
  Write(character);
end;
 
//Draws the border around the game area.
//
// @param title The title to be written above the border
//
//Side Effects:
// * Writes to the console
procedure DrawBorder(title: String);
var
  x, y: Integer;
begin
  GotoXY(BORDER_START_X, 1);
  Write(Title);
  
  //Draw the top + bottom borders
  for x := BORDER_START_X to BORDER_START_X + BORDER_WIDTH do
  begin
    //Top
    DrawAt(x, BORDER_START_Y, BORDER_CHARACTER);
    DrawAt(x, BORDER_START_Y + BORDER_HEIGHT, BORDER_CHARACTER);
  end;
  
  for y := BORDER_START_Y to BORDER_START_Y + BORDER_HEIGHT do
  begin
    DrawAt(BORDER_START_X, y, BORDER_CHARACTER);
    DrawAt(BORDER_START_X + BORDER_WIDTH, y, BORDER_CHARACTER);
  end;
end;
 
//Calculates the new location for the ball given the current
//location and amount to move. The low and high boundary values
//are used to ensure that the ball remains in the screen.
//
// @param originalLocation The current location
// @param change  The current movement amount
// @param lowBoundary  The new location cannot be at or below this value
// @param highBoundary  The new location cannot be at or above this value
// @returns The new location for the ball
function GetNewLocation(originalLocation, change, lowBoundary, highBoundary: Integer): Integer;
var
  newLocation: Integer;
begin
  newLocation := originalLocation + change;
  if (newLocation <= lowBoundary) or (newLocation >= highBoundary) then
  begin
    //movement goes off screen... dont move
    result := originalLocation;
  end
  else
  begin
    result := newLocation;
  end;
end;
 
procedure Main();
var
  x, y, dx, dy: Integer;
begin
  CursorOff();
  x := BORDER_START_X + 1;
  y := BORDER_START_Y + 1;
  dx := 1;
  dy := 1;
  
  DrawBorder('Bouncing Ball');
  
  repeat    
    DrawAt(x, y, BALL_CHARACTER);
    Delay(100);
    DrawAt(x, y, ' ');
    x := GetNewLocation(x, dx, BORDER_START_X, BORDER_START_X + BORDER_WIDTH);
    y := GetNewLocation(y, dy, BORDER_START_Y, BORDER_START_Y + BORDER_HEIGHT);
  until KeyPressed();
  CursorOn();
end;
 
begin
  Main();
end.

Summary

This iteration has added the ball and its movement. While the ball is remaining within the boundary it is not bouncing. This will be the focus for the next iteration.

[edit]Bouncing Ball: Overview | Iteration 1 | Iteration 2 | Iteration 3 | Iteration 4
Sample Code » C | C++ | .NET | Pascal | Perl | T-Sql | XHTML | Python | others to come