|
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!
|
|