• home
  • forum
  • my
  • kt
  • download
  • Writing a Game in Delphi

    Author: 2007-09-05 09:00:14 From:

    Writing a Game in Delphi

    In this Delphi game tutorial we are going to show you how to program the popular falling bricks game. There are many variants of this type of game, here we will explain the basic algorithm and provide full Delphi 7 source code for download

    The Rules of the Game

    In this game, there are a number of bricks on a wall, and each of these bricks has a color out of "n" colors.

    Bricks Game

    When the player clicks on a brick, the whole block of bricks of the same color disappears and the upper bricks fall down and fill the positions of the removed ones.

    Delphi Game

    Whenever a column becomes completely blank, all of the columns to the right of it are shifted one column left.

    The purpose of the player is to remove a block with maximum number of bricks at a time.

    Writing the Game

    Again, we will use a drawgrid with fixed number of rows and columns. The cell sizes will also be fixed. Customization issues are straightforward and they are left to the reader.

    First of all, please follow the steps below:

    1. Start a new application
    2. Name the main form as frmMain
    3. Put a TDrawGrid on the form (you can find it in the Additional tab as default) and name it as DrawGrid
    4. From the object inspector, assign the following values to the indicated properties of the DrawGrid (Of course, you can assign your own values for the number of columns, number of rows and cell size):
      • ColCount: 20
      • RowCount : 20
      • DefaultColWidth : 20
      • DefaultRowHeight : 20
      • FixedCols : 0
      • FixedRows : 0
      • DefaultDrawing : False

    We make the DefaultDrawing property false, because if we don¡¯t, the changes we make to the cells are overriden. So, don¡¯t forget to change this value.

    Finally, arrange the size of the drawgrid and the form in order to get rid of unnecessary blank space on the drawgrid and on the form and thus obtain a proper appearence.

    Blank Game Grid

    If you don¡¯t think to provide the user a customizable game with rearrangable number of rows, number of columns and cell sizes, prevent the user from resizing or maximizing the form in order to prevent an unaesthetic appearance. You can do this by assigning the appropriate values to the BorderIcons and BorderStyle properties of the form by using the Object Inspector, or you can assign these values explicitly in your code.

    Starting to Code

    Assigning the Random Colors of the Cells

    At the beginning of each new game, we have to assign a color among a number of colors to each cell on the grid.

    Actually, we can select a color randomly from the whole color spectrum an assign it to a cell. But since it is possible that any two or more bricks can be assigned two distinct colors but undistinguishable by the human eye (for example two very close tones of red), it is better to select the colors discretely from a predetermined color array.

    For this purpose, we need a constant array declaration similar to the following:

    const
      MAX_COLORS = 3;
    const
      PossibleColors : array [0..MAX_COLORS-1] of TColor = (clRed, clBlue, clGreen);
    

    Here, we defined MAX_COLORS (the maximum number of colors) to be 3. If you want to add new colors to the PossibleColors array, you should also change the MAX_COLORS value accordingly.

    We also need a 2-dimensional array to keep the corresponding colors of the bricks. But what will be the dimensions of this array? It is better to introduce new constants such as MAX_COLUMNS and MAX_ROWS and use these in the array declaration. It is good to do like this for future changes in the code. So, please add the following constant declarations:

    const

    const
      MAX_COLUMNS = 20;
      MAX_ROWS = 20;
    

    Now, we can declare our array that will keep the brick colors:

    var
      ColorOfBrick : array [0..MAX_COLUMN, 0..MAX_ROW] of TColor;
    

    Please note that the first index denotes the column, and the second one denotes the row.

    I think there is no need to mention that all of the declarations made above are global.

    Now, we need a procedure that will assign the colors of the bricks randomly. The following procedure will do that:

    procedure AssignBrickColors;
    var
      i, j : integer;
    begin
      for i := 0 to frmMain.DrawGrid.ColCount-1 do
        for j := 0 to frmMain.DrawGrid.RowCount-1 do
          ColorOfBrick[i][j] := PossibleColors[Random(MAX_COLORS)]
    end;
    

    As you can understand from the code, it assigns one of the colors from the possible colors randomly at each time to a cell.

    Note that we used the built-in Random() function in this procedure. If you don¡¯t want the random seed to be the default one, you should also call the built-in Randomize() procedure once before the first to the Random() function, in order to assign a seed. Otherwise, you will get the same sequence of random numbers at each game, which is not desired.

    We can call it in the creation of the main form:

    procedure TfrmMain.FormCreate(Sender: TObject);
    begin
      Randomize
    end;
    

    New Game

    We have told that we need to assign the colors of the bricks at the start of each new game.

    Let¡¯s first add a main menu on our form and add an item named itemNewGame with the caption ¡°New Game¡±. This will enable the player to start a new game whenever he/she likes to do so.

    In the OnClick event of itemNewGame, we should call our AssignBrickColors procedure:

    procedure TfrmMain.itemNewGameClick(Sender: TObject);
    begin
      AssignBrickColors
    end;
    

    If you want a new game to be started automatically when the application is started, you can call itemNewGame.Click on the creation of the main form:

    procedure TfrmMain.FormCreate(Sender: TObject);
    begin
      Randomize;
      itemNewGame.Click
    end;
    

    Drawing the Cells with Their Corresponding Colors

    We will use the OnDrawCell event of the draw grid. This event is fired whenever a cell needs to be redrawn. But we will also call this event explicitly in our code whenever necessary.

    procedure TfrmMain.DrawGridDrawCell(Sender: TObject; ACol, ARow: Integer;
                                        Rect: TRect; State: TGridDrawState);
    begin
      DrawGrid.Canvas.Brush.Color := ColorOfBrick[ACol][ARow];
      DrawGrid.Canvas.FillRect(Rect)
    end;
    

    Here, we want it to fill the rectangle determined by the Rect parameter with the corresponding color of the brick that is characterized by the column ACol and the row ARow.

    Now, run your application. You¡¯ll see an appearance similar to the below one:

    New Game

    Now, click on the ¡°New Game¡± item on the main menu. Thus the bricks will be assigned new random values. But what is that? The appearance remains the same?

    That is because, the cells do not need to be redrawn at the moment. After clicking on ¡°New Game¡±, if you move the form towards the outside of the screen and pull it back, you will notice that the cells which have just gone to the outside of thescreen have now their new colors, because they needed to be redrawn.

    If you enjoy addictive games you should probably check out Game Addicts, a blog on all the latest gaming news.

    In part 1 of this tutorial we got to the stage of being able to start a new game and the board being drawn, in this part we will continue where we left off.

    Then, the solution is to redraw each brick at start of each new game. The following procedure will help us:

    procedure RedrawCells;
    var
      i, j : integer;
    begin
      for i := 0 to frmMain.DrawGrid.ColCount-1 do
        for j := 0 to frmMain.DrawGrid.RowCount-1 do
          frmMain.DrawGridDrawCell(frmMain, i, j, frmMain.DrawGrid.CellRect(i,j), [])
    end;
    

    And the code for itemNewGameClick procedure should be changed as follows:

    procedure TfrmMain.itemNewGameClick(Sender: TObject);
    begin
      AssignBrickColors;
      RedrawCells;
    end;
    

    Now try it again. For this time, the appearance will change at each click on "New Game".

    Removing The Block of the Cells those have the same color

    The algorithm is simple:

    If the clicked brick has at least one neighbour of the same color (neigbour can be one of the four 
    bricks around it: namely up, down, left and right bricks. Not the diagonals),
         Remove this brick;
         Apply this recursively to the neighbours of this cell those had the same colors with it
    

    This means that a single cell that have no neigbours with the same color cannot be removed.

    Before all, let's define the color of the wall, because when a brick is removed, the emptied cell will take the color of the wall:

    const
      WALL_COLOR : TColor = clWhite;
    

    Here I assumed that the wall will be white. You can select any other color you wish, but it should be different from all of the colors in our PossibleColors array.

    Now, let's write our recursive procedure to remove the bricks properly:

    procedure RemoveBricks (cl : TColor; ACol, ARow : integer);
    begin
      // If the given color does not match with the color of the current brick,
      // Exit without doing anything
    
      if ColorOfBrick[ACol, ARow] <> cl then
        exit;
    
      // Remove the brick, by making its color
      // with the same as the color of the wall
      
    ColorOfBrick[ACol, ARow] := WALL_COLOR;
    
      // Now, apply this to its neighbours recursively
      // But don't forget that this cell might be a border cell,
      // so that it may not have one of the left, right, up or down neighbours
    
      // left neighbour
    
      if ACol > 0 then
        RemoveBricks(cl, ACol-1, ARow);
    
      // right neighbour
    
      if ACol < frmMain.DrawGrid.ColCount-1 then
        RemoveBricks(cl, ACol+1, ARow);
    
      // upper neighbour
    
      if ARow > 0 then
        RemoveBricks(cl, ACol, ARow-1);
    
      // down neighbour
    
      if ARow < frmMain.DrawGrid.RowCount-1 then
        RemoveBricks(cl, ACol, ARow+1);
    end;
    

    When should we call this recursive procedure? We should call it when a clicked brick has at least one neighbour with the same color, that is, when the brick is not alone. That is, it would be good to have a function that checks whether a brick is alone or not:

    function IsBrickAlone(ACol, ARow : integer) : Boolean;
    begin
      // Initially assume that it is alone.
    
      Result := True;
    
      // If there is not a brick on the clicked cell,
      // exit without doing nothing. This means that
      // an empty cell on the wall will be interpreted to be alone.
    
      if ColorOfBrick[ACol, ARow] = WALL_COLOR then
        exit;
    
      // Now, check all of its neighbours whether at least one of them
      // has the same color. Don't forget to check whether the cell
      // is a border cell or not. If it is a border cell, we can get
      // a memory violation error when trying to check one of its non-existent
      // neighbours.
    
      // Check the left neighbour
    
      if ACol > 0 then
        if ColorOfBrick[ACol-1, ARow] = ColorOfBrick[ACol, ARow] then
          Result := False;
    
      // Check the right neighbour
    
      if ACol < frmMain.DrawGrid.ColCount-1 then
        if ColorOfBrick[ACol+1, ARow] = ColorOfBrick[ACol, ARow] then
          Result := False;
    
      // Check the upper neighbour
    
      if ARow > 0 then
        if ColorOfBrick[ACol, ARow-1] = ColorOfBrick[ACol, ARow] then
          Result := False;
    
      // Check the down neighbour
    
      if ARow < frmMain.DrawGrid.RowCount-1 then
        if ColorOfBrick[ACol, ARow+1] = ColorOfBrick[ACol, ARow] then
          Result := False
    end;
    

    Now we can use this function when a brick is selected by the player. If it is not alone, the recursive procedure will be applied:

    procedure TfrmMain.DrawGridSelectCell(Sender: TObject; ACol, ARow: Integer; 
                                          var CanSelect: Boolean);
    begin
       // Check if the cell is alone or not. If it is, we will do nothing
      // according to the game rule, but it is not alone, we should remove
      // that area from the wall. We do this by calling our recursive procedure
      // RemoveBricks.
    
      // If it is not alone, remove the block
    
      if not IsBrickAlone(ACol, ARow) then
      begin
        RemoveBricks(ColorOfBrick[ACol, ARow], ACol, ARow);
    
        // Redraw the cells
        RedrawCells
      end
    end;
    

    Now, run your application and then click on a region. You will see that the region disappears and it takes the color of the wall. Notice that an alone brick cannot be removed when clicked.

    Will turn into

    This concludes this part of the tutorial, in the third installment we will shift all the bricks along to fill the newly created gap.

    If you enjoy addictive games you should probably check out Game Addicts, a blog on all the latest gaming news.

    discuss this topic to forum

    relation tutorial

    No relevant information

    Category

      Basics (4)
      Programming (3)

    New

    Hot