Using a class as inventory and rooms as functions

Started by
12 comments, last by Trienco 10 years, 8 months ago

Hello everybody.

I'm starting a game dev career and I'm working on a project right now. It's a very basic text-based game; what I want to achieve here is the good use of code and it's methods.

The game is about a guy who wakes up in a room with objects to look. Once he finds a key, he goes to another room, where he should look up for a code to open box and get another key to exit.

What I did is separate each room into a function, called room1() and room2(); each of them has a do while loop where the games asks the user what object wants to see (say table, closet, desk, etc)

Now the problem that I'm having is handling the inventory. You start with no keys (3 in total) I created a class called Inventory where are 3 booleans. What I want to do is, when the user finds a key, "true" it so the user can use it in the next room (for example the same key to go back to room 1)

So far, in main() I have an instance of Inventory called inventory and make the objects false, then room1(); But (of course), when I go to room1() inventory doesn't exist. Is there a way I can reach and modifying in them globally? without passing them through the function name? (say room1(key1))

And is this the best/cleanest way of coding?

Thanks everybody !

Advertisement

Before I go off on a tangent about rooms being objects and using a manager class:

Do you understand how to use Object-Oriented Programming in C++?

I'm a game programmer and computer science ninja !

Here's my 2D RPG-Ish Platformer Programmed in Python + Pygame, with a Custom Level Editor and Rendering System!

Here's my Custom IDE / Debugger Programmed in Pure Python and Designed from the Ground Up for Programming Education!

Want to ask about Python, Flask, wxPython, Pygame, C++, HTML5, CSS3, Javascript, jQuery, C++, Vimscript, SFML 1.6 / 2.0, or anything else? Recruiting for a game development team and need a passionate programmer? Just want to talk about programming? Email me here:

hobohm.business@gmail.com

or Personal-Message me on here !


And is this the best/cleanest way of coding?

well, its one way.

and about par for a first stab at it. IE we all started out that way. <g>.

for a small game, it would get the job done.

first thing i notice, you have a "game loop" (display contents, get and process input) for each room.

games typically have just one "game loop". in your case it might look something like:

while (! quitgame)

1. list contents of current room

2. based on current room, get and process user input


So far, in main() I have an instance of Inventory called inventory and make the objects false, then room1(); But (of course), when I go to room1() inventory doesn't exist. Is there a way I can reach and modifying in them globally? without passing them through the function name? (say room1(key1))

someone else here will have to help you with that, i stick mostly to c syntax myself. but don't worry, you'll get lots of suggestions. there tend to be many ways to skin a cat using c++ syntax.

but from an oo point of view...

off the top off my head, i'd say a room is a class, with data like a list of objects in the room, and methods like display_list and process_input.

then your game loop would look something like:

while (! quitgame)

room[current_room].display_list

room[current_room].process_input

where room[] is a list of room objects

and

current_room is a global of some sort, used by the game loop, but changed by the process_input methods when the player goes though a door. its is, in essence, a global "game state" variable, where each room is a "game state".

if keys are your only inventory item,you might use a "list of keys" object with 3 Booleans for keys 1,2, and 3, perhaps as an array BOOLEAN[3] or int[3], or BYTE[3]), and methods like add_key(key_num), remove_key(key_num) etc.

then your room[0].process_input method would call keylist.add_key(1) when they found the key to the next room. and perhaps keylist.addkey[0] when they find the key to the room they start in (so they can go back).

note that in applying oo design here, i'm thinking more in terms of data structures and the methods that use them (room contents data structure, inventory list data structure), and less in terms of "objects" (room, key).

from an objects standpoint, one would think "a room is an object", probably leading to a "list of room objects" data structure. but one would also think "a key is an object", probably leading to a list of keys objects, which is probably overkill. i mean, a class with one Boolean, and two methods, set, and clear? PLEEEZE! talk about OVER-ENGINEERING! <g>.

in the long run, you'll find the KISS principle (Keep It Stupid-Simple, IE so simple a total idiot could get it) to be a good friend when coding. it makes code easy to read, makes code easy to write, and often tends to be what runs the fastest.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

Hello guys, thanks so much for your response.


Do you understand how to use Object-Oriented Programming in C++?

I have a background in OOP but I have many years I don't write any code, where do you think I should start?


games typically have just one "game loop". in your case it might look something like:

while (! quitgame)
1. list contents of current room
2. based on current room, get and process user input

That's a good one Norm, yes I believe that a game should have a "game loop", in my case, that holds the loop for each room.

And thanks Norm for that whole explanation.

What I'll do now is to post the code I have so far:


#include <iostream>
#include <stdio.h>

using namespace std;

class Inventory {
public:
    bool key1;
    bool key2;
    bool key3;
};

void room1();
void room2();

int main() {
    Inventory inventory;
    inventory.key1 = false;
    inventory.key2 = false;
    inventory.key3 = false;

    cout<<"N O   E X I T\n";
    room1();

    return 0;
}

void room1() {
    Inventory inventory;
    int num = 0;

    do {
        cout<<"\nWhat do you want to look?:\n";
        cout<<"1. Desk\n";
        cout<<"2. Table\n";
        cout<<"3. Door\n";
        cin>>num;

        if (num == 1) {
            cout<<"You found a key in the desk.\n";
            inventory.key1 = true;
        }

        else if (num == 2) {
            cout<<"A big table, nothing interesting.\n";
        }

        else if (num == 3) {
            if (inventory.key1) {
                cout<<"Now you can open and go to next room.\n";
                room2();
                break;
            }
            else {
                cout<<"Door is closed.\n";
            }
        }
    }
    while (num != 4);
}

void room2() {
    Inventory inventory;
    int num = 0;

    cout<<"Room 2.\n";

    do {
        cout<<"\nWhat do you want to look?:\n";
        cout<<"1. Sofa\n";
        cout<<"2. Closet\n";
        cout<<"3. Door back to room 1\n";
        cout<<"4. Main Gate\n";
        cin>>num;

        if (num == 1) {
            cout<<"A sofa, nothing interesting.\n";
        }

        else if (num == 2) {
            cout<<"An open closet, here you found another key.\n";
            inventory.key2 = true;
        }

        else if (num == 3) {
            room1();
            break;
        }

        else if (num == 4) {
            if (inventory.key3) {
                cout<<"T H E   E N D";
                break;
            }
            else {
                cout<<"Main gate, but is closed.\n";
            }
        }
    }
    while (num != 5);
}

What do you think?

Thanks so much for your help guys, I'm really happy to be here!

other than refactoring to make it a single loop, its mostly just cleanup and organization.

the key1 - key3 Booleans could probably be initialized in the constructor for the inventory class.

if you move main() to the bottom of the file, you don't need the forward declarations for room1 and room2. for procedural code like main() room1() and room2(), c code is meant to appear in the file from bottom up. ie the lowest level routine is the first thing in the file, and the highest level routine (main) is at the end. forward declarations are intended for use with circular references (A calls B, and B calls A), although its common to use them as you did, to "invert" the order of appearance of the functions in the source code, so the highest level (main) comes first, followed by lower level routines. apparently this is often done because it supposedly increases readability. the sad fact is that it actually tends to decrease readability due to the fact that the declaration and implementation have been split up into two sections of code in two different places. the result is additional searching, when trying to figure out someone else code written in this style. and even with your own code. if you need to check the implementation of a routine, odds are 50% of the time a search will find the forward declaration first, not the implementation. on top of all that, its more typing. and if you repeat the declaration in the implementation, then you have two copies of the declaration, both of which must be edited if the declaration changes. to me just it seems like a lot of unnecessary BS to go though just so you can read your file from top down, instead of bottom up, as C is intended to be laid out. right now in your project it may not seem like a biggie, but wait til you get a couple hundred unnecessary forward declarations going.

Norm Barrows

Rockland Software Productions

"Building PC games since 1989"

rocklandsoftware.net

PLAY CAVEMAN NOW!

http://rocklandsoftware.net/beta.php

room1 can call room2 which can call room1 which can call room2, ad infinitum, all without returning. You seriously need to reconsider your design.

What happens if you don't type in a number. What happens if you enter the number that breaks out of the do...while loop.

Why don't you pass inventory to each function. You may as well just have a local bool hasKey in each function in the moment, because you can only find 1 key in each room and when you call another function you will forget you ever had any others, at the moment. Actually, you won't forget you had them since the contents of Inventory will be the contents of the stack, so it could be anything.

You can't find key3 anyway.

Sorry to be harsh...

"Most people think, great God will come from the sky, take away everything, and make everybody feel high" - Bob Marley

I remember working on this exact same type of problem when I started college. Me and a friend worked on a text game for a whole semester. So much fun. And oh, how bad that code would look to me now (17 years later) and how quickly we could write the whole thing now (about 2 weekends). But it was such a great learning experience.

Anyway, I have more than one suggestion / lesson I'd like to share with you, but I'm going to start with just the 1 I think if most important. An example of "polymorphism" applied to the concept of a Room, to allow you to have a common game loop. I'm going to try to change as little as possible of your code to give you the minimum example. Also, I might have left little C++ things out because I've been doing C# for the last 8 years, but hopefully I kept it close enough to compiling C++ code that you can fill in any mistakes I might have made.

Here's the design of the simplest Game / Room API I could think of:


class Room
{
    public:
        virtual void DisplayMenu() = 0;
        virtual Room ProcessInput(int num) = 0;
};

class Game
{
    Room *currentRoom;

    public:
        void GameLoop();
};

Here's some simple code for the game loop:


void Game::GameLoop()
{
    // start the game in room 1
    currentRoom = new Room1();
    
    // run the game loop as long as there is a room to be in.
    while(currentRoom != null)
    {
        int menuSelection;
        
        // present the user a set of choices for the current room
        currentRoom->DisplayMenu();
        
        // get the user's menu selection
        cin >> menuSelection;
        
        // process
        currentRoom = currentRoom->ProcessInput(menuSelection);
    }
}

And finally, here's the code for your first 2 rooms:


class Room1 : Room
{
    public:
        void DisplayMenu()
        {
            cout<<"\nWhat do you want to look?:\n";
            cout<<"1. Desk\n";
            cout<<"2. Table\n";
            cout<<"3. Door\n";
        }
        
        Room ProcessInput(int num)
        {
            Room nextRoom = this;
            
            if (num == 1) {
                cout<<"You found a key in the desk.\n";
                inventory.key1 = true;
            }
            else if (num == 2) {
                cout<<"A big table, nothing interesting.\n";
            }
            else if (num == 3) {
                if (inventory.key1) {
                    cout<<"Now you can open and go to next room.\n";
                    nextRoom = new Room2();
                }
                else {
                    cout<<"Door is closed.\n";
                }
            }
            else if (num == 4) {
                nextRoom = null; // you seemed to be using "4" to exit?, I'd probably make this universally 0 or something.
            }
            
            return nextRoom;
        }
};

class Room2 : Room
{
    public:
        void DisplayMenu()
        {
            cout<<"Room 2.\n";
            
            cout<<"\nWhat do you want to look?:\n";
            cout<<"1. Sofa\n";
            cout<<"2. Closet\n";
            cout<<"3. Door back to room 1\n";
            cout<<"4. Main Gate\n";
        }
        
        Room ProcessInput(int num)
        {
            Room nextRoom = this;
            
            if (num == 1) {
                cout<<"A sofa, nothing interesting.\n";
            }
            else if (num == 2) {
                cout<<"An open closet, here you found another key.\n";
                inventory.key2 = true;
            }
            else if (num == 3) {
                nextRoom = new Room1();
            }
            else if (num == 4) {
                if (inventory.key3) {
                    cout<<"T H E   E N D";
                    nextRoom = null;
                }
                else {
                    cout<<"Main gate, but is closed.\n";
                }
            }
            else if (num == 5) {
                nextRoom = null; // you seemed to be using "5" to exit?, I'd probably make this universally 0 or something.
            }
            
            return nextRoom;
        }
};

Now I'm not suggesting this is some great OO design that's going to save your world. I'm definitely not suggesting you should stop at these exact 2 room function (DisplayMenu and ProcessInput) for your final design long term. But I don't want to show you the design you might end up with 3 years down the road, right now. I want to show you how to move forward a few months in your learning curve and start working in a more OO design style with some useful polymorphism.

Good luck to you.

Here's some simple code for the game loop:

// start the game in room 1
currentRoom = new Room1();

Hrm.

Lesson 1: Always delete what you new.

Granted it is a simple program but every time you switch rooms you're locking the memory for the old room and throwing it out into computer space.

To answer the OP's original question, if you want to access Inventory globally, without passing it to each function, then it needs to be declared globally, like this:

 
class TInventory
{
  public:
  Inventory() :
    key1(false), // initializer list
    key2(false), 
    key3(false) {}
 
  bool key1;
  bool key2;
  bool key3;
};
 
// Declares the Inventory variable globally, so it can be access from any function
TInventory Inventory;
 
int main()
{
  // Now Inventory is available in all functions, no need to declare it again

When you get better at classes, you'll do this better, but that's how you globally declare a variable.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)

A while back I wrote an article: state machines in games. Two of those state machines (IIRC: 'funner state machine' and 'adaptable state machine' sub-projects) were the beginnings of such a dungeon. You might find the article enlightening for your project.

For a text adventure, there are verbs and nouns.

Nouns are 'things'. There are many things in the game. Each thing has a name or handle for use. Each thing has a description. Some things are symbolic ("north", "down") and some things are concrete ("bucket", "rope").

Verbs are actions. Actions may only be useful for some nouns. "Walk North" makes sense, "Walk Bucket" does not. Things like "eat rope" may not make sense.

You'll need to find a smart way to handle that, such as translating verbs into an enum, and then passing the enum to the object. Some games use compound statements. "Put water in bucket". This may be parsed as: verb noun noun.

So you can then convert a verb to a number or an enumeration, a noun to a game object. Then add a function like: GameObject::DoAction( Verb action, GameObject* optionalTarget) or whatever gets you going.

Moving on then...

The player has an inventory of things, even if that inventory is empty. Every room has an inventory of nouns, even if that inventory is empty. Objects may have an inventory of nouns.

Just to make things easier, I'd say all game objects have an inventory. Only some objects are valid in the inventory; you cannot put a room into another object's inventory, and you can only add things like poison and blood to a knife's inventory.

Where does that take you?

You have a simple language of commands, taking the form (Verb#, GameObject target, GameObject subTarget) with a subtarget that defaults to null. If parsing succeeds, you can call target.DoAction( verb, subTarget ) and the object can validate if that action makes sense, and then do the action.

Some of the actions can be gated. You can only enter a certain room if you have the key. In your case there are three specific keys, and each key is needed for a specific room. If they use "Walk to red room" and they don't have the key for the red room, that action is blocked.

Generally thinking in systems like this will simplify your game. It is almost always easier to make a general purpose room and a general purpose object than it is to hard code each individual object in the game.

This topic is closed to new replies.

Advertisement