Jump to content
  • Advertisement
Sign in to follow this  
DeathsBargin

Multiple Options...[C++]

This topic is 4872 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Im having problems with the options here. Im trying to allow the player to either.. 1) Leave the table dirty and take the wepon. or 2) Clean the table and take the wepon. *EDIT* also allow the player to leave without even taking the wepon. But im having problems accomplishing this. I need to know a better way to code it so that it would allow me to do what i want (Stated above). this is what i have (It wont compile properly)
#include <cstdlib>
#include <iostream>

using namespace std;

int main(int argc, char *argv[])
{
    bool dmover= false;
    bool gunin= false;
    bool crmove= false;
    int plan;
      
    cout<<"Hello\n";
    cout<<"Welcome to the Demo!\n";
    cout<<"This is test room 1.\n";
      //Room Description
        do {
    cout<<"You are in a large dusty room, with a table, a gun on the table, and a door to\nyour right.\n"; 
    cout<<"What would you like to do?\n";
    cout<<"1) Take the gun.\n";
    cout<<"2) Clean the room.\n";
    cout<<"3) Go right.\n";
    cin>>plan;
    // that was so that it says the description as dusty untill he says dusts the room
    }while (plan !=2);
    if (plan == 1){
             cout<<"You take the gun off of the dusty table.\n";
    else if (plan == 2) {
             cout<<"You swipe the room clean with your hand.\n";
             cout<<"You are in a large room with a table, a gun on the table, and a door to your\nright.\n";
             cout<<"What would you like to do?\n"; 
             cout<<"1) Take the gun.\n";
             cout<<"2) Go right.\n";
             cin>>plan;
             }
    if (plan == 1) {
             cout<<"You take the gun off of the now clean table\n";
             cout<<"You are in a large room with an empty table and a door to the right.\n";
             cout<<"What would you like to do?\n";
             cout<<"1) Go right.\n";
             cin>>plan;
       }
       if (plan == 1) {
                cout<<"You enter test room number two.\n Currently the end of the demo.";
             
             }
    system("PAUSE");
    return EXIT_SUCCESS;
}


thanks Death

Share this post


Link to post
Share on other sites
Advertisement
I don't like your example that much. (what happens when you have 20 rooms?)

In any case;

set weapontaken to false, set tableclean to false

initial
The table is dirty, there is a weapon on the table.

if user takes weapon
set weapontaken to true

if user cleans table
set table clean to true

if tableclean is false
The table is dirty
else
The table is clean

if weapontaken is false
, there is a weapon on the table.
else
.

That's really all you have to do oO

Share this post


Link to post
Share on other sites
I see..... I was making things a bit more difficult than they had to be..
But is it normal to have like 10000000 variables?

*EDIT*
edit of edit...(found the problem)

Share this post


Link to post
Share on other sites
Quote:
Original post by DeathsBargin
I see..... I was making things a bit more difficult than they had to be..
But is it normal to have like 10000000 variables?

*EDIT*
*** Source Snippet Removed ***
I tried your approach, Now im getting errors, for this. Whats the problem here?
No, it's not normal.

The problem I think.. is your trying to write something complicated only with simple stuff you can do in C++, what you really need to do is design a management system that's scalable.

I can see already probably two things you want to design; rooms, and actions that alter a room. In order to design a good text-based system I would use pointers, classes, structures, and so on. I don't really know your competence level with C++ though.

Share this post


Link to post
Share on other sites
I would also suggest the book "Design Patterns." It may help you organize those classes and structs that you will need.

Cheers,

Bob

Share this post


Link to post
Share on other sites
An important idea here is that of being able to model things - to create bits of data that represent things in the game world. The language provides models for "primitives" - i.e. variables of various primitive types. We model "a thing that could be true or false" with a boolean variable. We model "a number" with some numeric type (could be char, short, int, long, float or double, or a class designed to represent other types of numbers, depending on the situation). We model "a bit of text" with the library class std::string (not a language primitive, but something from the standard library in this case).

For other sorts of things, we need to construct a model, by determining what is interesting about the things, what is true about every thing of that type, and what may vary between things of that type.

Let's model a room, in the game as it stands so far. This will deliberately be very simplistic. I will basically be assuming only the functionality that you've shown for this room, and assuming that it applies to every room. Also, I will assume there are just two rooms, because that's all that's needed to get the point across.

So.
- A room may or may not contain a gun. It may or may not be dusty.
- Every room has some description, which depends on whether the gun is present or the dust has been cleaned away.
- You can leave the room to the right. This results in the player being in another room. There are two rooms, each of which connects to the other "on the right".
- If there is a gun in the current room, it can be picked up (which results in the room no longer containing a gun). A missing gun cannot be replaced. Similarly for the dust.
- When a room comes into existence, it has a gun and is dusty.

From this we can draw some conclusions about what data is needed to model a room:

- We need a boolean to say if there is a gun or not.
- Similarly to say whether the room is dusty.
- We need to indicate where you will end up if you go to the right. We can do this by storing a pointer to another Room as part of the Room object. Then we make two Rooms and point them at each other. (We cannot contain a room object within another room object, because that containing would apply recursively: our contained room would have to have a room in it, etc. up to infinite size. A pointer is how we model "and over there, there is a...".)

And we can also say what can be done with a Room:

- We can create a new room from scratch.
- We can take a gun if it is present. We'll provide a "member function" takeGun() to model that.
- We can clean the room if it is dusty. Similarly we provide clean().
- We can go to the other room. So we provide goRight().
- We need to be able to set the pointer to the other room, and we can't do that in a constructor - when we make the first room, there's nothing to point it to yet. So we'll need a setRight(), or something like that.
- We can show the description of the room, and then prompt for input. But getting input isn't really something the room does; it's something done in the main code of the game. Also, the numbering of the options may change depending on how many things are currently possible in the room. So what we really need to do is provide some kind of list of valid actions for the room.

Actions? Yes. We can model those. That's a more abstract idea (I can't ask you in the real world to 'hand me an action'), but programming is all about abstraction, so let's go for it. List? We could model that, too, but the standard library does that for us. I will use the standard library class std::list to do it, although there are plenty of other valid choices as well.

So we have a Room class:


// First off, some header business and other red tape...

#include <iostream>
#include <list>
#include <string> // will be needed for the Action class...

// Normally, one defines classes in separate files, and the whole 'using
// namespace' business is a little trickier that way. I'm going for real
// simplicity for illustrative purposes here. But you should read:
// http://www.gamedev.net/reference/articles/article1798.asp

using namespace std;

class Action;

class Room {
bool hasGun;
bool isDusty;
Room* right;

public:
// Create a new room from scratch.
Room() : hasGun(true), isDusty(true), right(NULL) {}
// Take a gun if it is present.
void takeGun() {
if (hasGun) {
cout << "You take the gun." << endl;
hasGun = false;
}
}
// Notice that I'm not really putting any effort into modelling the gun or
// the dust proper, since so far they are irrelevant to the gameplay.
// Anyway, our menu will not offer the chance to takeGun() if it is not there,
// so the check is redundant - but good practice in general. (Although if it
// weren't printing a message, there would be no good reason to check.)

// Clean the room if it is dusty.
void clean() {
if (isDusty) {
cout << "You clean the room." << endl;
isDusty = false;
}
}

// Go right.
// We don't want to set the player's position here - the main code will
// instead have a pointer to a "current" Room. We'll return the pointer to
// the room on our right, so that the main loop can set it.
Room* goRight() { return right; }

// Set who is on the right.
// We do have a bit of a design problem here: this should only be called
// "at the beginning", but we have no way to enforce that right now. As the
// code becomes more complicated, though, there are ways... it just isn't
// really justified yet.
void setRight(const Room& other) { right = &other; }
// We passed in the Room by reference, meaning that the Room will be used
// as-is (not copied) when passed in. Then we set our pointer to point at it
// (not a copy). An alternative would be to accept a pointer and just set
// with that directly; then the calling code would be responsible for taking
// the address. By doing it this way, we do (write the logic for) the icky
// address-taking business only once. This is a Good Thing.

// Show a description of the room.
void display() {
cout << "You are in a room." << endl;
if (hasGun) {
cout << "There is a gun on the table." << endl;
}
if (isDusty) {
cout << "It is very dusty in here." << endl;
}
}

// Get a list of the things that are in the room.
list<Action> getActions();
// Note I'm not defining it yet. First I want to talk about the Action class.
}


Ok, so at the end there we have a vague reference to our intent to produce lists of Actions. What's an Action? Well:

- It has some "type" of action: either it is a picking-up-gun action, a room-cleaning action, or a going-right action. This sort of thing is normally modelled with inheritance, because the behaviours are so different. I am going to write a bit of a hack by just storing an enumerated type, and doing a different thing depending on its value. Just to avoid overloading you at this stage. :)
- It has a name, which depends precisely on the type.
- It can be performed upon a Room. It is not necessarily associated with any particular Room; the player will always act upon the Room he is actually in. In C++, you can define how operators (things like +, *, etc.) work with your class instances. For action-type objects, it makes sense to overload the "operator ()" - that is for treating the object as if it were a function.

So we need:

- a type field.
- An operator().
- a name() function that will tell us the name of the action.
- A constructor that lets us set the action type. This does not ever change for a given Action instance.


enum { TAKE_GUN, CLEAN, GO_RIGHT } actionType;

class Action {
public:
Action(actionType t) : t(t) {}

// Get a name for the action.
string name() {
if (t == actionType::TAKE_GUN) { return "Take the gun."; }
else if (t == actionType::CLEAN) { return "Clean the room."; }
else { return "Go right."; } // must be GO_RIGHT
}

// Perform the action on the specified room.
// There's a bit of a problem here - we might need to return a Room*, but
// in other cases there is nothing to return. I will hack this for now by
// returning a null pointer for the other actions.
Room* operator() (Room* where) {
if (t == actionType::TAKE_GUN) { where->takeGun(); return NULL; }
else if (t == actionType::CLEAN) { where->clean(); return NULL; }
else { return where->goRight(); } // must be GO_RIGHT
}

private:
// We make this 'private' so that the compiler can complain if we write some
// "outside" code that tries to access or modify the type. This is a Good
// Thing; we only want the Action object to know or care about its type so
// that it can do its thing - anything else is begging for an accident to
// happen.
actionType t;
}


So now we have enough of a definition of Actions that we can think about creating a list of them for a Room. How are we going to do this? Well,

a) if the gun is available, we'll put a TAKE_GUN action in the list.
b) After that, if the Room is dusty, we'll offer a CLEAN action.
c) Finally, we'll always offer a GO_RIGHT action.

So basically we have a process where at each step we may want to append to the list. We need to know how to create a list, and append to it. We didn't write this list class, but we can use it pretty easily: we just need a plain old constructor call (with no arguments - actually all we need is to declare a variable of that type, and the compiler will assume the rest) to make a list, and we can use its "push_back" member function to append an Action.


// Note the syntax here for defining a class member function outside the
// class definition. This is so the compiler knows you don't want a separate,
// normal function that also happens to be called getActions().
list<Action> Room::getActions() {
list<Action> result;
if (hasGun) { result.push_back(Action(actionType::TAKE_GUN)); }
if (isDusty) { result.push_back(Action(actionType::CLEAN)); }
result.push_back(Action(actionType::GO_RIGHT));
return result;
}


On each of those push_back lines, I am making a constructor call 'in-line' in order to create an Action object, which is then appended to the list. You could just as easily declare variables of type Action (it would look like "Action a(actionType::TAKE_GUN);") and then .push_back() those, but it doesn't really gain you any clarity - the above is idiomatic.

Finally, the calling code needs to know what to do with a list of actions. It can do two things:

1) Display all the Actions.
2) Depending on user input, look up an Action in the list, and execute it.


void showMenu(const list<Action>& actions) {
// Again, I passed the list by reference to avoid copying it. The 'const'
// here is a promise that the function will not change the list that the
// caller passes to it - since the pass-by-reference would make that possible.

// To iterate (look it up in a dictionary) through the list, we will use an
// "iterator" object that the list provides. It behaves like a pointer to
// some element of the list (a real pointer wouldn't work because of how the
// list is implemented behind the scenes). The list provides "begin" and
// "end" functions to create iterators. The iterator is designed to "look
// like" a pointer; in particular, it provides an operator* and operator->
// that let us treat it like one (doing these properly for your own classes is
// tricky; be warned).
// The list does *not* let us treat it like an array; there is no direct
// operator[], so we have to use the iterators to iterate through. This is
// a Good Thing; all the standard library containers provide their own
// iterator types, so code change is kept to a minimum if we want to change
// the type of container we use later.
int label = 1;
for (list<Action>::iterator it = actions.begin(); it != actions.end(); ++it) {
cout << label << ") " << it->name() << endl;
label += 1; // for the next line.
}
}

// Execute the specified item and return true if found;
// return false if the action isn't found.
// There's a trick here: if the action was found and it was a go-right
// action, we need to modify the main loop's 'current Room' pointer. We'll
// handle this by passing the pointer in by reference - not a constant
// reference, since we may deliberately change it. A change to "current"
// will then be "seen" by the calling code.
bool execute(const list<Action>& actions, int which, Room*& current) {
// We'll iterate through the list again, until the label matches the desired
// value.
int label = 1;
for (list<Action>::iterator it = actions.begin(); it != actions.end(); ++it) {
if (label == which) {
// This is the one to execute.
Room* next = (*it)();
// That's: dereference the iterator to get an Action, and then invoke
// the Action's operator().

// If it returned a non-NULL value, then that's the room to go to.
if (next != NULL) { current = next; }
// Stop looking for other actions and bail out.
return true;
}
label += 1; // for the next line.
}
// If we got here, no action matched.
return false;
}


Finally, based off of all of this, we can write the main game loop:


int main() {
cout << "Welcome, and so on and so forth." << endl;

// Create two Rooms and link them to each other.
Room a, b;
a.setRight(b);
b.setRight(a);

Room* current = &a; // we'll start in Room A.

while (true) {
current->display(); // show the room.
list<Action> options = current->getActions();
showMenu(options);
// Loop until we get a meaningful response.
// This does not check for bad input, which is suprisingly tricky. I can
// cover that in another post if needed.
boolean acted = false;
do {
cout << "Well, what will you do?" << endl;
int choice;
cin >> choice;
} while !execute(options, choice, current);
// I.e., do polling for input until ("while not") able to execute a command.
}
}


There is lots that could be fixed up or expanded upon here, but I hope to have given you some good ideas :)

Share this post


Link to post
Share on other sites
Naturally, there are bugs and typos in there. I typed it all out top to bottom without feeding it to a compiler. ;) Here is a corrected version, with some comments on the corrections.


// First off, some header business and other red tape...

#include <iostream>
#include <list>
#include <string> // will be needed for the Action class...
#include <limits> // for std::numeric_limits

// Normally, one defines classes in separate files, and the whole 'using
// namespace' business is a little trickier that way. I'm going for real
// simplicity for illustrative purposes here. But you should read:
// http://www.gamedev.net/reference/articles/article1798.asp

using namespace std;

class Action;

class Room {
bool hasGun;
bool isDusty;
Room* right;

public:
// Create a new room from scratch.
Room() : hasGun(true), isDusty(true), right(NULL) {}
// Take a gun if it is present.
void takeGun() {
if (hasGun) {
cout << "You take the gun." << endl;
hasGun = false;
}
}
// Notice that I'm not really putting any effort into modelling the gun or
// the dust proper, since so far they are irrelevant to the gameplay.
// Anyway, our menu will not offer the chance to takeGun() if it is not there,
// so the check is redundant - but good practice in general. (Although if it
// weren't printing a message, there would be no good reason to check.)

// Clean the room if it is dusty.
void clean() {
if (isDusty) {
cout << "You clean the room." << endl;
isDusty = false;
}
}

// Go right.
// We don't want to set the player's position here - the main code will
// instead have a pointer to a "current" Room. We'll return the pointer to
// the room on our right, so that the main loop can set it.
Room* goRight() { return right; }

// Set who is on the right.
// We do have a bit of a design problem here: this should only be called
// "at the beginning", but we have no way to enforce that right now. As the
// code becomes more complicated, though, there are ways... it just isn't
// really justified yet.
void setRight(Room& other) { right = &other; }
// We passed in the Room by reference, meaning that the Room will be used
// as-is (not copied) when passed in. Then we set our pointer to point at it
// (not a copy). An alternative would be to accept a pointer and just set
// with that directly; then the calling code would be responsible for taking
// the address. By doing it this way, we do (write the logic for) the icky
// address-taking business only once. This is a Good Thing.

// Show a description of the room.
void display() {
cout << "You are in a room." << endl;
if (hasGun) {
cout << "There is a gun on the table." << endl;
}
if (isDusty) {
cout << "It is very dusty in here." << endl;
}
}

// Get a list of the things that are in the room.
list<Action> getActions();
// Note I'm not defining it yet. First I want to talk about the Action class.
};

typedef enum { TAKE_GUN, CLEAN, GO_RIGHT } actionType;

class Action {
public:
Action(actionType t) : t(t) {}

// Get a name for the action.
string name() const { // 'const' here promises "this function does not
// change the Action object itself."
if (t == TAKE_GUN) { return "Take the gun."; }
else if (t == CLEAN) { return "Clean the room."; }
else { return "Go right."; } // must be GO_RIGHT
}

// Perform the action on the specified room.
// There's a bit of a problem here - we might need to return a Room*, but
// in other cases there is nothing to return. I will hack this for now by
// returning a null pointer for the other actions.
Room* operator() (Room* where) const {
if (t == TAKE_GUN) { where->takeGun(); return NULL; }
else if (t == CLEAN) { where->clean(); return NULL; }
else { return where->goRight(); } // must be GO_RIGHT
}

private:
// We make this 'private' so that the compiler can complain if we write some
// "outside" code that tries to access or modify the type. This is a Good
// Thing; we only want the Action object to know or care about its type so
// that it can do its thing - anything else is begging for an accident to
// happen.
actionType t;
};

// Note the syntax here for defining a class member function outside the
// class definition. This is so the compiler knows you don't want a separate,
// normal function that also happens to be called getActions().
list<Action> Room::getActions() {
list<Action> result;
if (hasGun) { result.push_back(Action(TAKE_GUN)); }
if (isDusty) { result.push_back(Action(CLEAN)); }
result.push_back(Action(GO_RIGHT));
return result;
}

void showMenu(const list<Action>& actions) {
// Again, I passed the list by reference to avoid copying it. The 'const'
// here is a promise that the function will not change the list that the
// caller passes to it - since the pass-by-reference would make that possible.

// To iterate (look it up in a dictionary) through the list, we will use an
// "iterator" object that the list provides. It behaves like a pointer to
// some element of the list (a real pointer wouldn't work because of how the
// list is implemented behind the scenes). The list provides "begin" and
// "end" functions to create iterators. The iterator is designed to "look
// like" a pointer; in particular, it provides an operator* and operator->
// that let us treat it like one (doing these properly for your own classes is
// tricky; be warned).
// The list does *not* let us treat it like an array; there is no direct
// operator[], so we have to use the iterators to iterate through. This is
// a Good Thing; all the standard library containers provide their own
// iterator types, so code change is kept to a minimum if we want to change
// the type of container we use later.
// We need to use a 'const_iterator' for a const list.
int label = 1;
for (list<Action>::const_iterator it = actions.begin(); it != actions.end(); ++it) {
cout << label << ") " << it->name() << endl;
label += 1; // for the next line.
}
}

// Execute the specified item and return true if found;
// return false if the action isn't found.
// There's a trick here: if the action was found and it was a go-right
// action, we need to modify the main loop's 'current Room' pointer. We'll
// handle this by passing the pointer in by reference - not a constant
// reference, since we may deliberately change it. A change to "current"
// will then be "seen" by the calling code.
bool execute(const list<Action>& actions, int which, Room*& current) {
// We'll iterate through the list again, until the label matches the desired
// value.
int label = 1;
for (list<Action>::const_iterator it = actions.begin(); it != actions.end(); ++it) {
if (label == which) {
// This is the one to execute.
Room* next = (*it)(current);
// That's: dereference the iterator to get an Action, and then invoke
// the Action's operator() on the current room.

// If it returned a non-NULL value, then that's the room to go to.
if (next != NULL) { current = next; }
// Stop looking for other actions and bail out.
return true;
}
label += 1; // for the next line.
}
// If we got here, no action matched.
return false;
}

int main() {
cout << "Welcome, and so on and so forth." << endl;

// Create two Rooms and link them to each other.
Room a, b;
a.setRight(b);
b.setRight(a);

Room* current = &a; // we'll start in Room A.

while (true) {
current->display(); // show the room.
list<Action> options = current->getActions();
showMenu(options);
// Loop until we get a meaningful response.
bool acted = false;
int choice;
while(true) {
cout << "Well, what will you do?" << endl;
// Here's some simple checking for bad (i.e. not a number) input.
// (numbers out of range will be handled by the execute() logic.)
if (!(cin >> choice)) {
// We couldn't read a number. We need to do some cleanup
// (See: http://www.augustcouncil.com/~tgibson/tutorial/iotips.html#directly)
cin.clear();
cin.ignore(numeric_limits<streamsize>::max(), '\n');
// and skip the execute() attempt.
continue;
}
// If execution is ok, we're done. I originally wrote this as a
// do-while loop, but 'continue' in a do-while loop apparently doesn't
// skip the while-clause like I thought it did. :/
if (execute(options, choice, current)) break;
}
}
}

Share this post


Link to post
Share on other sites
Thankss alot.
I havent had enough time to completley read all of it. But I have to go to school now so I wont have enough time to do it at the moment.

Ill finish reading it later.

Death

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

GameDev.net is your game development community. Create an account for your GameDev Portfolio and participate in the largest developer community in the games industry.

Sign me up!