C++ design advice

Started by
5 comments, last by Pacman18 8 years, 3 months ago

hi guys,

I've been learning to program for about 2 months now, with C++11 from Bjarne Stroustrup's "The C++ language" book.

I've read the section covering basic facilities, variables, containers etc. I'm now reading about the abstraction mechanisms and classes and I think I understand them, however I have trouble figuring out how best to use them to write good code. Since I'm an absolute basic beginner I have just been using console apps to practice, for classes practice I decided to try and write a text based rpg like game with a few rooms just to test. I'm realising that learning the syntax is easy but the hard part of programming is design.

I was wondering if I could get some advice on the best way to use classes to make such a game, or any game/program really so that it is easily extendable without modifying the classes too much. In my program so far I've made some classes and functions to handle a waiting prompt, the setup of a new area etc, but when I come to write the room in the main cpp file, it just seems wrong and a weird way to go about it. Any advice is appreciated, below is my code example ;

header file ;


#include <iostream>
#include <string>
#include <vector>
#include <windows.h>


#define TITLE "\tTest RPG\n\t---------\n\n\n"
#define PROMPT "\n> "

void wait();
void setupareas();
void bunker();
void cafe();


void wait()
{
    print("\n\n\n1. Back\n\n\n> ");
    char c;
    std::cin >> c;
}

class area
{
public:
    short m_seen = 0;
    std::string m_name;
    std::string m_description;
    std::vector<std::string> m_menu1;
    std::vector<std::string> m_menu2;
    void setname(std::string name);
    void setdescription(std::string description);
    void presetmenu(std::vector<std::string> menu);
    void postsetmenu(std::vector<std::string> menu);
    void printmenu1();
    void printmenu2();
    void printdescription();
} ;

void area::setname(std::string name)
{
    m_name = name;
}

void area::setdescription(std::string description)
{
    m_description = description;
}

void area::presetmenu(std::vector<std::string> menu)
{
    for (auto i : menu)
    {
        m_menu1.push_back(i);
    }
}

void area::postsetmenu(std::vector<std::string> menu)
{
    for (auto i : menu)
    {
        m_menu2.push_back(i);
    }
}

void area::printmenu1()
{
    std::cout << TITLE;
    print(m_name);
    std::cout << std::endl << std::endl;
    for (auto i : m_menu1)
    {
        print(i);
        std::cout << std::endl << std::endl;
    }
}

void area::printmenu2()
{
    std::cout << TITLE;
    print(m_name);
    std::cout << std::endl << std::endl;
    for (auto i : m_menu2)
    {
        print(i);
        std::cout << std::endl << std::endl;
    }
}

void area::printdescription()
{
    m_seen = 1;
    char wait;
    system("cls");
    print(m_description);
    print("\n\n\n1. Back\n\n");
    print(PROMPT);
    std::cin >> wait;
}

Then the main.cpp file where I 'setup' the rooms and run the game


#include "head.h"

area bunker;
area cafe;

using namespace std;

int main(void)
{
    setupareas();
    bunker();
    return 0;
}

void setupareas()
{
    bunker.setname("Bunker\n_________");
    bunker.setdescription("You are in the bunker");
    vector<string> temp {"1.Look Around", "2. Go North", "3. Go South"};
    bunker.presetmenu(temp);
    temp = {"1.Look Around", "2. Go North", "3. Go South", "4. Examine poster"};
    bunker.postsetmenu(temp);

    cafe.setname("Cafe\n_________");
    cafe.setdescription("You are in the cafe");
    temp = {"1.Look Around", "2. Go South"};
    cafe.presetmenu(temp);
    temp = {"1.Look Around", "2. Go South", "3. Speak with coffee lady", "4. Speak with worker"};
    cafe.postsetmenu(temp);
}

void bunker()
{
    bool here = true;
    while (here)
    {
        system("cls");
        if (bunker.m_seen == 0)
        {
            bunker.printmenu1();
        }
        else
        {
            bunker.printmenu2();
        }
        print(PROMPT);
        char choice;
        cin >> choice;
        switch(choice)
        {
            case '1': bunker.printdescription(); break;
            case '2': cafe(); break;
            case '3': break;
            case '4':
            system("cls");
            print("Poster");
            wait();
            break;
        }
    }
}

void cafe()
{
    bool here = true;
    while (here)
    {
        system("cls");
        if (cafe.m_seen == 0)
        {
            cafe.printmenu1();
        }
        else
        {
            cafe.printmenu2();
        }
        print(PROMPT);
        char choice;
        cin >> choice;
        switch(choice)
        {
            case '1': cafe.printdescription(); break;
            case '2': bunker(); break;
            case '3':
            system("cls");
            print("lady");
            wait(); break;
            case '4':
            system("cls");
            print("worker");
            wait(); break;
        }
    }
}
Advertisement

The first thing that jumps out at me is that you are a lot going on in the class. You should ideally keep class members private and function public. You could delegate the object initialization upon creation to the class constructor and maybe cut out some of the code. Sadly, I am busy right this second, but I will use your code as a basis and write up code based on what I mean.

thanks a lot, no rush. I'll be grateful for any examples you may provide!

You should camelCase your functions and variables. i.e. 'setDescription'; it helps with readability. Hard to give too much advice without overloading you but one thing I think you should try to do is make bunker/cafe functions either part of the area class or part of another class that sits above them and deals with the transitions from one area to the next. Hopefully they would be the exact same function/method but because they have different data they will behave differently. They already look very similar so you are almost there.

Also here's something fun for you to do, see what happens if you keep going from the bunker to the cafe then to the bunker then the cafe and so on, I think you will rapidly hit an issue. It's for that reason I think you should move your area transition 'up' in the hierarchy.

Imagine you can only go North, East, South or West (your game may differ) then you might have an enum for that:

enum Direction { North, East, South West};

You already have an 'Area' class so that's good. You may then want another class that holds how they are all related to each other. I.e. the bunker may be north of the cafe, to the east there may be a dungeon. You could have another class then which manager where your player actually is, it would have a pointer/reference to the current area the player is and it would do something like:


while(true)
{
   Direction moveDirection = currentArea->Do();
   Area* newArea = areaMap.GetArea(currentArea, moveDirection);
   if(newArea != null_ptr)
      currentarea = newArea;
} 

The Do() method would print your description and options and so on that the current bunker/cafe function is doing. It would stay in there until the player chooses West or so on at which point it will then return that direction. The parent object will then check the area map in the desired direction and hopefully find a new area which will be selected. It will then end up calling Do() on the new area and the process repeats. To quit you could add 'Quit' to the direction enum and return that.

Interested in Fractals? Check out my App, Fractal Scout, free on the Google Play store.

Games like that are classic state machines. Rooms are states with transitions between rooms. Objects either are in states or have states, or sometimes both.

Classes generally should only have a single responsibility. In the case of this type of text based game, manipulation of state is a single responsibility, and it is something that tends to get reused many times. You can have a state machine of the world and location in it, another state machine (or multiple) for the objects in the game, another state machine for the flow of the story. When designing code it is good to identify responsibilities, especially responsibilities that are duplicated in many times or places that may not be immediately obvious. Identifying how things work, the responsibilities involved, is a critical skill.

You might get some ideas for the code by reading this article and reviewing the code.

Well, I feel like my advice is dwarfed by the others so I will just give a quick example using a chunk of your code for what I meant. With me I give my classes their own header and source files (I prefer to prefix class header/source with a capital C [for class] followed by class name; some prefer to drop the C and just use the name):

CArea.h:


#pragma once
#include <vector>
#include <string>

class Area {
        // I always try to make members private
	short m_seen = 0; 
	std::string m_name;
	std::string m_description;
	std::vector<std::string> m_menu1;
	std::vector<std::string> m_menu2;
public:
	Area(std::string name, std::string description); // can make the constructor replace two functions
        //
	// void setname(std::string name);
	// void setdescription(std::string description);
	// no point in adding the other functions as I left them unchanged for this example
	void printDescription() const;
};



CArea.cpp


#include "CArea.h"
#include <iostream>
Area::Area(std::string name, std::string description) :m_name{ name }, m_description{ description } {}

void Area::printDescription() const
{
	std::cout << m_name << '\n';
	std::cout << m_description << '\n';
}

main.cpp


#include "CArea.h"

//area bunker;
//area cafe;



int main(void)
{
        // This way you can initialize name/description upon object creation
        // rather than using public members or setter functions
	Area bunker{ "Bunker\n_________", "You are in the bunker" };
	Area cafe{ "Cafe\n_________", "You are in the cafe" };

	bunker.printDescription();
	cafe.printDescription();
	//setupareas();
	//bunker();
	return 0;
}

Output:


Bunker
_________
You are in the bunker
Cafe
_________
You are in the cafe
Press any key to continue . . .

wow thanks guys, I knew I must of been going about it the wrong way. I'm going to create another class to handle where the player is and review the gamestate idea.

thanks a lot, no doubt i'll be back with another question later as I re-do the code!

This topic is closed to new replies.

Advertisement