Text Based RPG help

Started by
34 comments, last by sharpe 12 years, 5 months ago
Ive been working through the Game Institute's C++ Module I and have completed almost all of the exercises except for adding multiple enemies to combat. Most of the features I did get operational I implemented in unorthodox ways(i.e. Store, Save Game, Load Game, and Random Encounter during rest)

What I'm looking for is some constructive criticism on how to improve the code and any tips on how to implement multiple enemies into the combat scenario. It advises to return a pointer to an array of enemies from Map::checkRandomCounter() then to update the player's attack function to accept an array of monsters. I can't seem to figure it out.

Also, attached is the code for the project.

EDIT: Went through the code again and fixed some bugs. I updated the .zip archive.
Advertisement
Can someone please give me some tips on how to clean this up and make it more object-oriented?

Initially I tried to create an enter function within the store(i.e. store.enter(mainPlayer)) and pass a pointer to player into the function but that didn't work out as I am still struggling to grasp the concept of pointers.

Also random encounter during rest is pretty messy. I would have liked to create the the random encounter within the rest function but failed as I'm sure using pointers is the solution.

I'm not asking for someone to just hand me over a solution I would just like some tips on improvement and hopefully help grasping pointers and their mechanics. Thank you in advance.
I'm a newbie or I would try to help you.

However, I've found using pastebin.com will make it easier for others to answer your question. Also, paste code here using the code tag. That might get you some answers! :)


EDIT: I'm playing your game now, by the way! :)
A 30-year-old interested in learning to program by making an old-school, simple, text-only or 2D dungeon fantasy video game.

  • As of 9/28/2011, currently attempting to learn the basics of Python 2.7.
  • As of 10/14/11, Dabbling in C#.
  • As of 10/24/11, decisively surpassed my small knowledge of Python in C#.

Can someone please give me some tips on how to clean this up and make it more object-oriented?

Initially I tried to create an enter function within the store(i.e. store.enter(mainPlayer)) and pass a pointer to player into the function but that didn't work out as I am still struggling to grasp the concept of pointers.

Also random encounter during rest is pretty messy. I would have liked to create the the random encounter within the rest function but failed as I'm sure using pointers is the solution.

I'm not asking for someone to just hand me over a solution I would just like some tips on improvement and hopefully help grasping pointers and their mechanics. Thank you in advance.


As a general rule of thumb, if you are passing a naked non-const pointer, that should be a "code smell" of sorts. That said, you can learn that lesson much later.


Pointers are actually relatively straight forward, at least compared to other aspects of C++! :)

Think of pointers if you will like GPS coordinates. They simply refer to a location in memory, however, like with GPS coordinates, when you resolve them, they are actually a physical location. For example, [color="#3366BB"][font="sans-serif"]

42.9837°N 81.2497°W

[/font] are the GPS coordinates of my hometown, but once you resolved them you have the city of London, Ontario.

With code pointers it much the same thing:

int i = 42;
int * intPointer = &i;

Just like the GPS example, intPointer represents the location of the integer i. However, just like with coordinates, once you actually resolve them, or dereference in C++ parlance, you get the actual object itself, so:

cout << *intPointer;

Would print out 42, because that is the value at the location pointed to by i. At this point I suppose it makes sense to point out that '*' when applied to a pointer is what causes it to de-reference ( look at the value pointed at, not the location ), while the earlier used '&' when applied to a type says "give me the address/location of this thingy". The pointer is declared as the type it is pointed to; so a pointer point at an int is declared as "int *" while a pointer pointing at a class Widget is declared as "Widget *".



That's the gist of pointers, now the why. One very good reason is scope/lifetime.

Consider this function:

Player CreatePlayer()
{
Player p;
p.Name = "Bob";
// ... all kinds of other stuff
return p;
}


This code works perfectly fine, however when it returns, it actually creates a completely new copy of Player, so if the Player class is really large, this is going to be quite slow and wasteful. So, instead off going through the motions twice, you could:

Player* CreatePlayer()
{
Player * p = new Player();
p->Name = "Bob";
return p;
}

What happens here is CreatePlayer creates a new Player as a pointer instead of as a local variable. Since it is simply a pointer to a location in memory, you have to say "OK, now fill it with type Player, which is essentially what new does ( it actually does so by calling Player's constructor, but thats another story ). Now, since you are dealing with a player instead of a standard variable, you have to use -> instead of "." when accessing members. Now what made this call so much different than the earlier call? This is where scope comes into the equation.


Consider this:

{
int i = 42;
{
int j = 43;
}
int k = i + j;
}

Once wrong with this equation? Well, you can't access j because it went out of scope. That is exactly what the { } characters delineate. The lifetime of a variable is only as long as it is in scope. So once j hits the next } POOF, it ceased to exist.

So, what do you do when you want a variable to last longer than it's scope? This is where pointers can come in.

Consider this function again:


Player* CreatePlayer()
{
Player * p = new Player();
p->Name = "Bob";
return p;
}

At the end of this method, instead of a copy being returned, it's still the original p variable that exists, and the address of it is returned to the caller. This presents one of the biggest downsides to pointers too... leaks.

{
Player * ourPlayer = CreatePlayer();
// do stuff
}

That code just leaked a Player object, since variables created via pointers do not get destroyed when they go out of scope. Therefore you have to delete it yourself, such as:

{
Player * ourPlayer = CreatePlayer();
delete ourPlayer;
}


This has another downside, in that you need to be careful that a deleted pointer is never accessed again, consider:

{
Player * ourPlayer = CreatePlayer();
delete ourPlayer;
// stuff
cout << ourPlayer->Name;
}



KABOOOM!


Want to know the worst part... that's not even guaranteed to go KABOOM!, because even though you deleted the pointer, keeping in mind pointers are just a reference to a location in memory, even though you deleted the pointer, it doesn't mean that C++ has deleted what the pointer pointed to! That means that code may actually work, at least until something overwrites that location of memory. These create some seriously nasty and hard to find bugs! This is also why you will often see:

{
delete ourPlayer;
ourPlayer = NULL;
}

Because the guarantees that pointer will blow up if accessed again. Trust me, you would rather get a null pointer exception than have code that sorta works through that bug, so get in the habit of null'ing out your pointers.



Another big value of pointers is passing them around, lets say you had somethings like this:


{
Gameworld world;
ChangeWorld(world);
}

Gameworld ChangeWorld(Gameworld gameWorld)
{
gameWorld.time = "4:00pm";
// do other stuff to game world
return gameWorld;
}

Looks good and works, right? Well sorta. Here is the problem, you actually created 3! Gameworld objects in this process! The first one when you declared it, then when you passed it into ChangeWorld, since it was passed by value it actually created another local copy, then when you returned it, it created yet another copy to overwrite the original world object! This may be ok with small types like int, but with large classes or in a tight loop this is going to bring your program to a screeching halt.

Now, you can do the exact same thing with pointers:


{
Gameworld * world = new Gameworld();
ChangeWorld(world);
}

void ChangeWorld(Gameworld *gameWorld)
{
gameWorld->time = "4:00pm";
// do other stuff to game world
}

In this case, only one instance is ever created and even more importantly, you don't have to return the updated Gameworld object, as you actually made changes directly to the original.

That said C++ ( and not C ) provides a special quasi pointer called a reference.

When a reference is declared in a function, it changes things a bit. Generally when you are calling a function you are passing "by value", where as when you use a reference you are passing, predictably enough, by reference. You can think of this as the same as passing in a pointer, except the compiler does the work for you behind the scenes. So considering the earlier example, passed by reference it would be:


{
Gameworld world;
ChangeWorld(world);
}

void ChangeWorld(Gameworld &gameWorld)
{
gameWorld.time = "4:00pm";
// do other stuff to game world
}


So, it looks just like the first version, where it create a local copy, however because of that magical ampersand you are telling the compiler to pass in a reference to the object instead of a copy of the object, so in the function when you make changes, you are in fact making changes to the original.


Clear as mud?
Sorry it took me so long to respond. I don't mean to bring up an old thread. I would just like to say thank you for your very educational post. It took me at least 5 reads to grasp it but I finally understand pointers and implemented them in my game loop, the result is a much clearer and more optimized one that I'm very grateful for. Thanks again.
Went through the code today and cleaned it up a lot and commented it as much as I could to explain how everything works. Got the main source file game.cpp down to 96 lines.


// game.cpp
#include "Map.h"
#include "Player.h"
#include "Store.h"
#include "Display.h"
#include <cstdlib>
#include <iostream>
#include <fstream>
using namespace std;

int main()
{
////////////INITIALIZE GLOBAL VARIABLES////////////////////
//Initialize the game map, the player, and the display which contains functions used to display the various menus throughout the game..
//Also initialize an input variable to navigate the different menus of the game.
Map gameMap;
Player mainPlayer;
Display display;
int selection = 0;
////////////////////BEGIN START MENU/////////////////////
//Display the Start menu and prompt the user to load a previous game or start a new game.
display.Splash();
display.StartMenu();
cin >> selection;
cin.ignore();
if(selection == 2) {
//Load Game.
mainPlayer.loadGame();
gameMap.loadPlayerPos();
}
else {
//Create New Player
mainPlayer.createClass();
}
/////////////////////END START MENU /////////////////////////
/////////////////////////////////////////////////////////////
////////////////////BEGIN GAME LOOP//////////////////////////
/*Initialize control variable for main game loop.
If this variable is set true the game loop will exit.*/
bool done = false;
while( !done ) {
//Clear the screen.
system("CLS");
selection = 0;
// Each loop cycle we output the player position and a selection menu.
display.MainMenu(gameMap);
cin >> selection;
// Test the selection. If Selection == valid. Game Loop keeps going.
// If Selection != valid. Game loop restarts and prompts user for another selection.
if(!display.testSelection(selection, 1, 6))
continue;
//Initialize monster and set it to zero, in case the combat simulation is ran.
Monster* monster = 0;
switch( selection ) {
case 1://Player Moves
//Update players position on the game map.
gameMap.movePlayer();
/*If the Player has moved to the stores location on the game map they enter it.
Here the player can purchase weapons, items, and spells.
When the player is finished at the store he exits and returns to the game loop.*/
if( gameMap.getPlayerXPos() == 2 && gameMap.getPlayerYPos() == 3)
{
//Initialize store and enter it.
Store store;
store.enter(mainPlayer);
}
//If the player isnt at the stores location then test for a combat encounter.
else {
if(mainPlayer.combatSimulation( monster, mainPlayer, gameMap) >= 1)
done = true;
}
break;
case 2://Player Rests
if(mainPlayer.rest(monster, mainPlayer, gameMap) >= 1)
done = true;
break;
case 3://View Stats
mainPlayer.viewStats();
break;
case 4://View Map Landmarks.
mainPlayer.viewLandmarks();
break;
case 5://Save Game
mainPlayer.saveGame(gameMap.getPlayerXPos(), gameMap.getPlayerYPos());
break;
case 6://Quit
done = true;
break;
}
}
}
/////////////////END GAME LOOP///////////////////////////


Anymore comments or suggestions would be great. I'm still having some trouble with implementing the multiple enemies.
I'm not sure how I should go about making it balanced. If the player faces 3 enemies, should he have 3 attack turns? I'm just not sure.

An updated version of the game is attached to this post.
NOTE: The dungeon is not operational. It is part of a quest line I'm going to be implementing.
NPHshreddar, is it possible for you to put your project somewhere else? Because I can't download it for some reason (the first 5 downloads were my unsuccessful attempts).
With regards to the game.cpp you put on the forum, you probably shouldn't put srand in the game loop.

NPHshreddar, is it possible for you to put your project somewhere else? Because I can't download it for some reason (the first 5 downloads were my unsuccessful attempts).
With regards to the game.cpp you put on the forum, you probably shouldn't put srand in the game loop.


External Download Link

There ya go. Its hosted on fileswap.

Also, I removed the srand() function from the game loop and put it into the random function so it is called every time random is. Next feature is a quest line with some side quests. I'll be adding more map landmarks as well. I think I'm gonna be doing a redesign of the view map landmarks function to make more accurate and dynamic map. Ive also been considering consolidating all of the display functions for the store into a single function withtin the display class. We'll see.
You should not call srand inside the while loop. That will mess up the logic behind the random functions. Just call it once at the beginning of main.

If the player should get three turns against three enemies doesn't really make sense to me. If he got himself into a gang fight, he must deal with it biggrin.gif

You could give the player a bonus turn if he does something smart or something, but these things is entirely up to you, the game creator

Also, I removed the srand() function from the game loop and put it into the random function so it is called every time random is.


Don't do this.
It should be called only once before you start pulling values from the rand function

This topic is closed to new replies.

Advertisement