Feedback Search Top Backward Forward
EDM/2

The Making of WarpTris - Part 1

Written by Paulo Gago da Camara

Linkbar

 
Part1 Part2

Introduction

This article is about the concept and design of WarpTris, a small game I developed last summer in order to learn Presentation Manager programming. I assume here that you have working knowledge of the C++ programming language and basic knowledge of the OS/2 PM.

WarpTris was developed under Visual Age C++ 3.0 for OS/2. I don't know if it will compile on GCC+EMX or any other C++ compiler.

Remember that WarpTris was my first PM program so there might be (and there are) some parts of it that could be done in a more effective way. Nevertheless I hope that this text might be of some help on understanding the WarpTris source code and, who knows, it might even help someone that, just like myself, is learning PM programming.

It is recommended that you download the WarpTris source code in order to best understand each part of it that will be described here.

You can download it and the compiled game at my Web Page. The URL is as follows: http://www.geocities.com/SiliconValley/Heights/1856

About the Game

The original idea for the game belongs to my former C/C++ teacher, Dr. Ghunter Funk, who proposed the game as an assignment in C++. When I first developed WarpTris (the initial name was 4Tris), I did it with a colleague and it was programmed in Borland C++ 3.0 for DOS.

The original program was in text mode (50 lines) and it wasn't very attractive to the eye. Still, we got a nice grade with it. Later, during Summer holidays, I bought the OS/2 Warp Programming for Dummies book and started learning PM. When I finished reading the book I needed to put to practice all the things I thought I had learned. That was when my friend Jorge Martins (an OS/2 user and developer since version 1.0), gave me the idea of developing 4Tris for OS/2, and then WarpTris was born. WarpTris isn't a port from the original version for DOS although some code from the game engine was reused (mainly some methods of the 'Board' class and the 'Figure' class). Many parts were modified and most of them were rewritten from scratch.

WarpTris differs from normal Tetris by the fact that the game figures appear at the middle of the game window and then start falling towards one of the four sides randomly. This way you can make lines on all four sides of the game window. When one figure is falling towards one side, you can only move that figure parallelly to that side. If some part of a figure covers any square of the gray area at the middle, the game ends. There are also three levels of difficulty. The difference between them is the 'impulse' that each figure receives at start, and the score given by each piece.

The WarpTris user interface consists of a main parent window and four child windows: the Game, Score, High Scores and Next windows. Initially WarpTris had only a window where the game was played, but then I had to change the design to what it is now.

The sounds that are played during the game are on a separate thread of execution using an object window to avoid slowing down the figure movements. Since my knowledge of the multimedia API is very restricted I opted to use the MCI command string interface which is quite simple but has its limitations. I plan to include a background MIDI music and more sounds, but first I have to read more about the OS/2 multimedia subsystem.

WarpTris Source Code Files Description

The WarpTris source code contains the following files:

WarpTris.cpp
This is the main file which has all the PM parts of the game and some of the game mechanics.
WarpTris.h
This file includes all the constants and defines needed to identify the menus, windows, bitmaps, icons, dialogs, controls, user defined window messages, etc...
Board.h
Here the Board class and its methods are declared and defined. This class represents the logical grid on the client area of the game window (where the game is actually played).
Figure.h
In this file the Figure class and its methods are defined. This class represents the figure that is currently moving and the next figure that will appear.
HighScore.h
The class declared and defined here handles all the logical operations that have to do with the high scores, including retrieving them from the Scores.ini file, testing if a score is an high score, filling up the list box and saving them to the file.
Digital.h
This class represents the digital panel that displays the score of the current game. This one is quite handy, and it's very easy to use it in another programs.
The discussion here will focus mainly on these files.

The Game Engine

The WarpTris game engine is mainly composed of two classes: Board and Figure. As stated earlier, these classes and their methods are defined on the Board.h and Figure.h header files.

The Board Class

The grid where we play the game is represented by this class. The data structure I used to represent it is the matrix of integers named 'board'. Each element of this matrix can have a value of 0 (if empty) or the resource ID of the square bitmap that is to be drawn there (if occupied).

There is also a private member called 'hwndClient' which is the handle of the game window client area. This is used to pass to the functions that actually draw or clear a square on that window (These functions are declared and defined on the WarpTris.cpp file).

Private members:

  • VOID EraseLine(INT line) - Logically erases the line specified in 'line', this is done by resetting to zeros the corresponding line in the matrix.
  • VOID EraseColumn(INT column) - Logically erases the column specified in 'column'. This is done the same way as on EraseLine.
  • INT Limit(DIR side, INT definition[5][5]) - The figures on WarpTris are defined as seven 5x5 constant matrices on the 'WarpTris.h' file. This function returns the offset position of the actual figure from a given side of that matrix. This is very useful to avoid putting the figure in an invalid position (outside the grid for example).

    Note: DIR is a global enumeration declared in the 'WarpTris.h' file as:

    
    enum  DIR{UP=1, DOWN=2, RIGHT=3, LEFT=4, ALL=5};
    

    Example:

    
     Figure defined like -> 00000
                            01100
                            00100
                            00100
                            00000
    
    Here the value returned by Limit for UP, DOWN and LEFT is 1, and for RIGHT is 2.
  • VOID DrawSquare(INT x, INT y) - This method draws or clears a given square on the client area of the game window, depending on the value of board[x][y].
  • INT SingleEvaluate(DIR side) - This function searches for full lines to remove from a given side, and returns the number of lines actually removed.

Constructor:

  • Board(HWND hwnd) - This is the constructor. It has only one argument that is used to initialize the private member 'hwndClient'. The only thing the constructor does is initialize the board matrix with zeros (EMPTY).

Public methods:

  • VOID DrawArea(DIR side) - This function draws a given side of the grid on the client area of the game window.
  • INT Evaluate(DIR side) - This method is used to look for filled lines and remove them. It returns the number of lines removed. The side parameter is used to determine which sides should be evaluated by the SingleEvaluate private method, the value of side is the direction the current figure is falling to. This method is only called when the current figure can't move any further (or has been dropped).
  • BOOL PutFigure(INT x, INT y, INT definition[5][5]) - This method tries to put the figure passed in definition at the (x,y) position on the logical grid. If that figure can't be placed on the given position it returns 'FALSE', otherwise it places the figure there and returns 'TRUE'.
  • VOID RemoveFigure(INT x, INT y, INT definition[5][5]) - This one removes the figure (passed in definition) from the (x,y) position on the logical grid. This method and 'PutFigure' are used in conjunction in order to make the figures move.
  • BOOL Finished() - This method simply tests if any square on the gray area is occupied. If so, the game is over and 'TRUE' is returned, otherwise it returns 'FALSE'.
These are the descriptions of the private and public members of the 'Board' class, below is the full 'Board' class declaration.

class Board
{
 private:
  INT board[COLUMNS][LINES];    // The logical board
  HWND hwndClient;              // Handle of the game client window
  VOID EraseLine(INT line);     // Removes a line
  VOID EraseColumn(INT column); // Removes a Column

  // Returns the limit of a given side of a piece
  INT  Limit(DIR side, INT definition[5][5]);
  // Draws a square on the board
  VOID DrawSquare(INT x, INT y);
  // Searches one side of the board for full lines
  INT  SingleEvaluate(DIR side);

 public:
  // Constructor
  Board(HWND hwnd);
  // Draws a given area of the board, for performance reasons
  VOID DrawArea(DIR side);
  // Searches the board for full lines on a way that depends
  // on the side the piece is falling to, it returns the number
  // of lines removed.
  INT  Evaluate(DIR side);
  // Puts a figure on the board (logical)
  // Returns TRUE if possible on (x,y)
  BOOL PutFigure(INT x, INT y, INT definition[5][5]);
  // Removes a figure from the board
  VOID RemoveFigure(INT x, INT y, INT definition[5][5]);
  // Is the game over ?
  BOOL Finished();
};

4.2 The Figure class

This class is used to logically represent the figure being played. It's also used to represent the next figure that will appear. During the game there are always two objects of this class. One for the currently playing figure and one for the next figure. The overloaded operator '=' is used to assign the figure displayed in the Next window to the new playing figure.

Private members:

  • INT posX, posY - These are the position of the figure in grid coordinates.
  • INT definition[MAX_FIGURE_X][MAX_FIGURE_Y] - This is the matrix that logically represents the figure.
  • INT bitmapID - This is the Resource ID of the square bitmap that is used to draw the figure.
  • BOOL pairFigure - This checks if this figure is a 'pair figure' or not. (See the Rotate method below)
  • HWND hwndClient; Handle of the client window where the figure is to be drawn. This can be the 'Game' or the 'Next' window client area depending on whether this is the current figure or the next figure.

Constructor:

  • Figure(HWND hwnd, INT bitmap, INT def[MAX_FIGURE_X][MAX_FIGURE_Y], INT px=0, INT py=0, BOOL animated=TRUE, BOOL draw=TRUE) - This is the constructor, which accepts various parameters. The first one is the handle of the client window where the figure will be drawn. The second is the resource ID of the square bitmap associated with the figure. Next is the matrix that defines the shape of the figure. 'px' and 'py' are used to initialize the position of the figure. The 'animated' parameter tells is the 'exploding' animation of the gray square is to be drawn, and finally the 'draw' flag indicates if the figure is to be drawn.

Public methods:

  • VOID Rotate(Board &board) - This method rotates (logically) the figure inside the grid represented by the object board. It takes into account the value of the private data member 'pairFigure', which is the way I used to distinguish figures that need to be rotated in different manners. The problem here was that some figures, because of their position inside the logical matrix, rotated in a less than smooth manner. What I did here was to include the '9' digit on the first digit of their constant matrix definition to mark them as pair figures. Then, when an object of this class is constructed from a figure definition matrix, the constructor tests if the first element is the digit '9', and if it is it assigns 'TRUE' to the private member pairFigure, which is later tested by this function to rotate the figure correctly.
  • VOID ClearLastFigure(INT x=0, INT y=0) - This method graphically removes the figure from the game client area. If no parameters are specified the default is to use the current position stored in the private data members 'posX' and 'posY', otherwise the position passed in (x,y) is used.
  • VOID Draw(INT x=0, INT y=0) - This one does the same as the 'ClearLastFigure' method except that it draws the figure.
  • VOID DrawNext(INT x=0, INT y=0) - Does the same as the Draw method, except that it calls the function that draws squares on the next window.
  • INT Move(DIR d, Board &board) - This method tries to move the figure in the direction given by 'dir' inside the grid represented by the 'board' object. If possible it returns 1 otherwise it returns 0.
  • VOID Fall(DIR d, Board &board) {while(Move(d, board));} - This method relies on the return code from the 'Move' method to keep moving the figure towards the side passed in 'd', actually making the figure fall to that side of the game window.
  • VOID operator=(Figure &figure) - I overloaded the '=' operator to make it possible to assign the figure on the Next window to the new figure on the game window.
  • INT GetBitmapID() {return(bitmapID);} - This one just returns the resource ID of the square bitmap associated with this figure.
The full Figure class declaration follows:

class Figure
{
 private:
  // The current position of the figure in squares coordinates
  INT  posX, posY;
  // The logical representation of the figure
  INT  definition[MAX_FIGURE_X][MAX_FIGURE_Y];
  // Which bitmap used to draw the figure
  INT  bitmapID;
  // Is it a pair figure (to do with the rotation of the figure)
  BOOL pairFigure;
  // Handle of the game client window
  HWND hwndClient;

 public:
  // Constructor
  Figure(HWND hwnd,
         INT bitmap,
         INT def[MAX_FIGURE_X][MAX_FIGURE_Y],
         INT px=0,
         INT py=0,
         BOOL animated=TRUE,
         BOOL draw=TRUE);
  // Rotate the figure on the board (if possible)
  VOID Rotate(Board &board);
  // Erase last figure from the game window
  VOID ClearLastFigure(INT x=0, INT y=0);
  // Draw the figure
  VOID Draw(INT x=0, INT y=0);
  // Draw the figure on the next window
  VOID DrawNext(INT x=0, INT y=0);
  // Move it on a given direction (if possible)
  INT  Move(DIR d, Board &board);
  // Let it fall to a given side
  VOID Fall(DIR d, Board &board);
  // To assign a figure to another (to do with the Next figure)
  VOID operator=(Figure &figure);
  // Get method of the private var, bitmapID
  INT  GetBitmapID() {return(bitmapID);}
};
The game engine, as we saw, is mainly composed of these two classes. Next we will take a look inside the WarpTris Presentation Manager code.

Conclusion

Next month I will deal with the PM portion of the game. Until next month!

 

Linkbar