Build A Text Based Multiplayer RPG Part I

This is the first article in a multi-part series on how to develop a console based multiplayer MUD style RPG game. By the end of this series you will have a fully working console app that will run a typical client server model desktop application on Windows. This tutorial requires the Microsoft Visual C++ Compiler.

Click here for part II

Review Material

Click here for a review on pointers if you need to brush up.
Click here for a review on C++ iterators, if you can’t follow it then read the pointer review first.

Background

If you happen to be curious as to what exactly a “text based role playing game(rpg)” is in my own words a text based rpg is simply a game in which the player or players input commands into a terminal and are fed back a description of the actions which took place within the game world. For more info see the Wikipedia entry on it.

Ok, now that we’re all familiar with the concept, lets first review why anyone would even want to build a text-based rpg in todays world of fancy 3D graphics, special effects and semi-realistic computer rendered player models. I believe text based rpgs are a great start for programmers who want to build more serious/professional grade rpgs for several reasons. First, text based rpgs allow programmers to focus purely on the game engine itself without the overhead of graphics and or animation.

Second, the core of any role playing game relies on the sophistication of its role playing system; or the game mechanics. Without a unique and interesting core system, gamers will quickly dismiss your rpg as a rehash or just another D&D clone.

Lastly, from a programming perspective it’s a great excuse to practice data structures, socket programming and object oriented design. In this first part of series we’re going to begin by writing out the code base for the header class which will hold all the definitions for the objects which will appear within our game. However before we even write a single line of code lets first think about how we want all the game objects to fit together. Take a look at the picture below, and take note of all the objects that are identified. We have the player, monster, items, dungeon and terminal objects which are all clearly identified within our simple mock up. Although those won’t be our exact classes that we’ll code in a moment, the rough picture just lets us better understand the model.


Game Sketch

Initial Code

Now lets start coding; call this file database.h, why we chose to name it database will be important in the future once we get to the multiplayer aspects of the game but enough talk.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>
#include<string>
#include<list>
#include<vector>
#include<map>
#include<algorithm>
#include<iterator>
#include<locale>
#include<sstream>
using namespace std ;
namespace MUD
{
class Player ;
class Item ;
class Room ;
class Dungeon ;
class Monster ;
class Dispatch ;
class Filter ;

Ok so now we have our libraries all included and the forward declarations for our classes planned out. Don’t worry about the inclusion of the not so common libraries such as locale, and sstream yet as we’ll introduce them in the future.

Item and Monster Classes

To start off we begin with the Item class. The item class as you can guess will represent items within our rpg game. Due to this being a simple rpg game we will restrict ourselves to just three types; weapons, armor and health. At this moment you might be wondering why don’t we just make three different classes, one for each item. However, as you can see below, we’ll just stick with this one class and generalize it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class Item
{
public:
enum TYPE { WEAPON, ARMOR, HEALTH } ;
Item( string name, Item::TYPE type, int power, int vitality, int health ) ;
Item operator=( Item& newItem ) ;
Item( void ) ;
//~Item()
void setPower( int value ) ;
int getPower( void ) ;
void setVitality( int value ) ;
int getVitality( void ) ;
void setHealth( int value ) ;
int getHealth( void ) ;
void setName( string name ) ;
string getName( void ) ;
void setType( TYPE newType ) ;
TYPE getType( void ) ;
string mName ;
int mPower ;
int mVitality ;
int mHealth ;
TYPE mType ;
bool mAvailiable ;
} ;

The item class as you can look at above uses six different attributes, name, power, vitality, health, type and availability. We’ll discuss each of those further when we get to writing the actual methods for the class but for now try and think about how each of those attributes relate to the three different enum types or weapon, armor and health. Next, we are going to look at the monster class. This class being as its represents the monsters in our game should meet the following requirements. Monsters should have some measurement of life or hit points, a strength or attack power, a defense rating, a unique name and of course an indicator of whether or not they are currently in combat.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Monster
{
public:
Monster( string name, int maxHp, int strength, int defence ) ;
//~Monster()
void setHp( int newHp ) ;
int getHp( void ) ;
void setStrength( int newStrength ) ;
int getStrength( void ) ;
void setDefence( int newDefence ) ;
int getDefence( void ) ;
void setName( string newName ) ;
string getName( void ) ;
void setMaxHp( int newMaxHp ) ;
int mMaxHp ;
int mHp ;
int mStrength ;
int mDefence ;
string mName ;
bool inCombat ;
} ;

The Room Class and Player Classes

For more sophisticated games our monster class would probably have attributes for magic, speed, steal-able items and experience points rewarded if your into the experience points style of rpgs. But for this simple game; strength, hp and defense will do. Moving along we are ready to begin sketching out the code for the room class. The room class is a class which will represent a single room within our dungeon and will also contain items, monsters and allow players to traverse them. In order to best represent the interconnected nature of the rooms as they would appear in our game setting the data structure of choice will be a simple 4-way(technically 8-way since each rooms allows backtracking) linked list with each direction being the familiar north, south, east and west. When I first designed this application several months ago the room class was by far the most difficult class conceptually since most of the game revolves around traversing the rooms, finding items(within rooms), slaying monsters(again within rooms), updating the state of the player(moving about rooms) and keeping the state of the dungeon intact(managing the rooms). As you can tell the room class is key to this simple game we’re making.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class Room
{
public:
Room( string name, string description, Item* item = NULL ) ;
//Room( void ) ;
//~Room()
typedef bool success ;
void addMonster( Monster* newMonster ) ;
Monster& getMonsterRef( string monsterName ) ;
success killMonster( string monsterName ) ;
void itemTaken( void ) ;
string searchResponse( void ) ;
success link( char direction, Room& room ) ;
Room& north( void ) ;
Room& south( void ) ;
Room& east( void ) ;
Room& west( void ) ;
void setName( string newName ) ;
void setDescription( string newDescription ) ;
string getDescription( void ) ;
void setItem( Item* newItem ) ;
Item* mItem ;
Room* mNorth ;
Room* mSouth ;
Room* mEast ;
Room* mWest ;
list< Monster* > mMonsters ;
string mName ;
string mDescription ;
} ;

Next up is the player class, although for this simple game the player class resembles the monster class to an exceedingly high degree and you might be thinking “why are we not using inheritance from a general base class that has hp, str, def and such”. The answer to that question was due to me having originally planned out the player class to have multiple sub classes representing jobs such wizard, knight, archer, monk, ninja and such and to have different weapons and movement bonuses and so on until I realized that it was getting too large. So I cut all that stuff out and decided to keep things simple.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class Player
{
public:
Player( string name, Room* start = NULL ) ;
//~Player()
typedef bool dead ;
void setHp( int newHp ) ;
int getHp( void ) ;
void setStrength( int newStrength ) ;
int getStrength( void ) ;
void setDefence( int newDefence ) ;
int getDefence( void ) ;
string getLocation( void ) ;
void setName( string newName ) ;
string getName( void ) const ;
string equip( string itemName ) ;
string attack( string monsterName ) ;
string move( char direction ) ;
string search( void ) ;
dead death( void ) ;
int mMaxHp ;
int mHp ;
int mStrength ;
int mDefence ;
Room* mLocation ;
string mName ;
Item* mWeapon ;
Item* mArmor ;
dead mLife ;
} ;

The Dungeon Class

Now here is where the class design gets a little tricky. The dungeon class, the class we’re coding next will make use of two std::vector<> objects to hold both the current players and the references to the various rooms within the game. Also an interesting method within the dungeon class which we’ll describe in detail later on in this tutorial series is the combatLoop() method. If you recall from earlier, the player class already has an attack() method so why does the entire dungeon need to be aware of which players are in combat? Remember that the goal of this tutorial is a multiplayer game so important aspects of the dungeon such as which players are alive and which monsters have been slain should be propagated to each player. Ok, so here is the code.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Dungeon
{
public:
Dungeon( int maxPlayers, int maxRooms ) ;
//~Dungeon()
bool isComplete( void ) ;
string addPlayer( Player* newPlayer ) ;
string addRoom( Room* newRoom ) ;
string removePlayer( string playerName ) ;
void generatePlayerStats( Player* newPlayer ) ;
void combatLoop( Player& hero, Monster& villain ) ;
string* playerStatus( Player& player ) ;
void displayHelp( void ) ;
int mMaxRooms ;
int mMaxPlayers ;
vector< Room* > mRooms ;
vector< Player* > mPlayers ;
} ;

Utility helpers, “Filter” and “Dispatch”

Lastly there are two utility classes I wrote to help facilitate the ever important parsing of the user inputs into the game terminal. Being that this game is text based, players will undoubtedly type in all sorts of crazy commands and awkward inputs. So the design of the two utility classes dispatch and filter were put in place to basically filter all incoming messages to ensure validity and to dispatch valid messages based on a simple protocol I crafted to handle commands. In other words, in order to avoid players attempting to cheat the game say by equipping from rooms which they currently don’t occupy or grabbing all the health items from other players the filter class serves to clean up dirty commands before they even reach the dungeon class.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Filter
{
public:
typedef bool valid ;
valid validateMessage( string message ) ;
string chat( string message ) ;
} ;
class Dispatch
{
public:
char extractCommand( string fromFilter ) ;
string extractValue( string fromFilter ) ;
} ;

Ok folks that’s about all for now; just remember to stay tuned for the next parts of this tutorial which I’ll roll out when I get the time.

-Thanks-

Oh and here is the whole code if you’re in a hurry

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
// database.h
#include<iostream>
#include<string>
#include<list>
#include<vector>
#include<map>
#include<algorithm>
#include<iterator>
#include<locale>
#include<sstream>
using namespace std ;
namespace MUD
{
class Player ;
class Item ;
class Room ;
class Dungeon ;
class Monster ;
class Dispatch ;
class Filter ;
class Item
{
public:
enum TYPE { WEAPON, ARMOR, HEALTH } ;
Item( string name, Item::TYPE type, int power, int vitality, int health ) ;
Item operator=( Item& newItem ) ;
Item( void ) ;
//~Item()
void setPower( int value ) ;
int getPower( void ) ;
void setVitality( int value ) ;
int getVitality( void ) ;
void setHealth( int value ) ;
int getHealth( void ) ;
void setName( string name ) ;
string getName( void ) ;
void setType( TYPE newType ) ;
TYPE getType( void ) ;
string mName ;
int mPower ;
int mVitality ;
int mHealth ;
TYPE mType ;
bool mAvailiable ;
} ;
class Monster
{
public:
Monster( string name, int maxHp, int strength, int defence ) ;
//~Monster()
void setHp( int newHp ) ;
int getHp( void ) ;
void setStrength( int newStrength ) ;
int getStrength( void ) ;
void setDefence( int newDefence ) ;
int getDefence( void ) ;
void setName( string newName ) ;
string getName( void ) ;
void setMaxHp( int newMaxHp ) ;
int mMaxHp ;
int mHp ;
int mStrength ;
int mDefence ;
string mName ;
bool inCombat ;
} ;
class Room
{
public:
Room( string name, string description, Item* item = NULL ) ;
//Room( void ) ;
//~Room()
typedef bool success ;
void addMonster( Monster* newMonster ) ;
Monster& getMonsterRef( string monsterName ) ;
success killMonster( string monsterName ) ;
void itemTaken( void ) ;
string searchResponse( void ) ;
success link( char direction, Room& room ) ;
Room& north( void ) ;
Room& south( void ) ;
Room& east( void ) ;
Room& west( void ) ;
void setName( string newName ) ;
void setDescription( string newDescription ) ;
string getDescription( void ) ;
void setItem( Item* newItem ) ;
Item* mItem ;
Room* mNorth ;
Room* mSouth ;
Room* mEast ;
Room* mWest ;
list< Monster* > mMonsters ;
string mName ;
string mDescription ;
} ;
class Player
{
public:
Player( string name, Room* start = NULL ) ;
//~Player()
typedef bool dead ;
void setHp( int newHp ) ;
int getHp( void ) ;
void setStrength( int newStrength ) ;
int getStrength( void ) ;
void setDefence( int newDefence ) ;
int getDefence( void ) ;
string getLocation( void ) ;
void setName( string newName ) ;
string getName( void ) const ;
string equip( string itemName ) ;
string attack( string monsterName ) ;
string move( char direction ) ;
string search( void ) ;
dead death( void ) ;
int mMaxHp ;
int mHp ;
int mStrength ;
int mDefence ;
Room* mLocation ;
string mName ;
Item* mWeapon ;
Item* mArmor ;
dead mLife ;
} ;
class Dungeon
{
public:
Dungeon( int maxPlayers, int maxRooms ) ;
//~Dungeon()
bool isComplete( void ) ;
string addPlayer( Player* newPlayer ) ;
string addRoom( Room* newRoom ) ;
string removePlayer( string playerName ) ;
void generatePlayerStats( Player* newPlayer ) ;
void combatLoop( Player& hero, Monster& villain ) ;
string* playerStatus( Player& player ) ;
void displayHelp( void ) ;
int mMaxRooms ;
int mMaxPlayers ;
vector< Room* > mRooms ;
vector< Player* > mPlayers ;
} ;
class Filter
{
public:
typedef bool valid ;
valid validateMessage( string message ) ;
string chat( string message ) ;
} ;
class Dispatch
{
public:
char extractCommand( string fromFilter ) ;
string extractValue( string fromFilter ) ;
} ;
}

Click here for part II

Share Comments