The difficulty of establishing and using polymorphism is on par with other C++ and object-oriented concepts. However, polymorphism is rarely beneficial in small or simple programs, making it challenging to demonstrate its value in an introductory programming text. We can better appreciate its benefits by describing an authentic problem and its solution. To accomplish this, the tiles example excerpts and simplifies a small part of a Mahjong solitaire program.
"Mahjong is a tile-based game that was developed in the 19th century in China and has spread throughout the world." Originally a three- or four-person game, it has become a popular computer solitaire game with numerous variations (see, https://www.free-play-mahjong.com for one example). The following UML class diagram, code fragments, and descriptions are adapted and simplified from a Mahjong solitaire program.
The Mahjong Tile Classes
Mahjong consists of 144 tile objects saved in a custom data structure reflecting each tile's position on a three-dimensional game board. The implementation described here instantiates the tiles from ten related classes representing seven different kinds of tiles - one class represents three kinds of tiles, and three classes support inheritance and polymorphism, but the program never instantiates them. However, each tile's position in the data structure and on the game board is independent of their type. Therefore, it's easiest to upcast and represent them collectively in the data structure and program functions as Tile pointers.
In this simplified example, polymorphism makes two essential operations possible. First, the program must draw each tile separately. The Tile draw function draws a blank tile, and the subclass draw functions add tile-specific detail, making their relative execution order significant. Second, to play the game, the player selects the tiles two at a time, and if they "match," the program removes them from the board. What it means to "match" depends on the tile's type and, sometimes, on saved member data. Polymorphism allows the program to test for a match with a single, simple function call. We can solve both problems without polymorphism, but solutions based on branching statements (like switches or if-else ladders) are larger and more cumbersome and complex than polymorphic solutions.
Mahjong Tile Classes
The draw functions use the host operating system's or implementing language's native drawing functions, making the functions non-portable and placing them beyond the scope of an introductory text. The following class outlines include the draw functions to illustrate their syntax and the sequence of their internal operations but otherwise focus on the more easily demonstrated matches functions.
class CircleTile : public RankTile
{
public:
CircleTile(int rank) : RankTile(rank) {}
virtual void draw()
{
Tile::draw();
// draw "rank" circles on the tile
}
};
class BambooTile : public RankTile
{
public:
BambooTile(int rank) : RankTile(rank) {}
virtual void draw()
{
Tile::draw();
// draw "rank" bamboo sticks on the tile
}
};
The CircleTile and BambooTile classes.
The CircleTile and RankTile classes share several features. Their constructors chain to the RankTile constructor, passing the tile's rank (the number of circles or bamboo sticks on the tile) to it, and both inherit and use the RankTile matches function. Furthermore, both classes define a draw function that chains to the Tile function before drawing rank number of circles or bamboo sticks on the tile's face.
class PictureTile : public Tile
{
private:
string name;
Image picture;
public:
PictureTile(string s) : name(s)
{
picture = load(name);
}
virtual void draw()
{
Tile::draw();
// draw "picture" on the tile
}
};
class SeasonTile : public PictureTile
{
public:
SeasonTile(string s) : PictureTile(s) {}
};
class FlowerTile : public PictureTile
{
public:
FlowerTile(string s) : PictureTile(s) {}
};
class Bamboo1Tile : public PictureTile
{
public:
Bamboo1Tile() : PictureTile("Sparrow") {}
};
The PictureTile classes.
The PictureTile and its subclasses inherit and use the Tile class matches function. The PictureTile constructor's parameter is the file name where the program loads the picture painted on the tile. The name Image is a placeholder for a system-dependent type storing an image format (.jpg or .png). The draw function chains to the File draw function to create a blank tile before painting the image on it. The subclasses pass the file name to the PictureTile constructor but inherit and use its draw function.
Downloadable Tiles Classes
tiles.cpp includes a main function, making the example compilable and runnable. It demonstrates the C++ syntax for creating various tile object, upcasting them to Tile pointers, and calling the virtual functions.