Jump to content


Simple Question...


58 replies to this topic

#1 xISOx

    New Member

  • Members
  • PipPip
  • 28 posts

Posted 28 October 2005 - 01:22 AM

I'm pretty new to programming, and for practice, I have chose to write a simple text based game with C++, just so I can get used to alot of the commands and structure. I have this so far...

/*
 * This program source code and all associated executables and files that 
 * may or may not be packaged and distributed are registered under the 
 * general public licence(GPL) and are free to be copied, re-compiled, edited,
 * or just about anything else that you want to do with it.  It is not
 * required, but the authors(Nik 'xISOx' Daniel and David 'brfngmnky' Brees)
 * would like to be credited for their work on this project.  Thank You.
 * 
 * As this is intended to be a learning project, we will be trying to document
 * and explain what we do, but if you have no idea what any of the code is, 
 * the explanations may not make much sense.  Go buy yourself a programming
 * book if you want to learn how to program.
 *
 * The website for this project has yet to be determined, as it has no name.
 */
 
/* These things are the things that should appear at the beginning of any
 * source code files, they basicly hold the data for the functions that we 
 * use regularly. (cout, cin, int, char)  They are used by the pre-processor
 * of the compiler, meaning that they are only used by the compiler, not the
 * actual program.  The '<>' denote universal files, and a '""' would denote
 * a local file, but I'm not sure we'll be using those for this project.
 */
#include <iostream>
#include <fstream>
#include <stdio.h>
#include <string.h>
/* 
 * This next little line means that we will be using standard functions and 
 * calls in the code.  This prevents us from having to type std:: in front of
 * alot of the code.
 */
using namespace std;
//Defining the strings to be used for logon and saving purposes...
char playername[20];
char playerpass[20];
// Prototyping the functions...
void login (char playername[20], char playerpass[20]);
// The main function, what starts the actual program.
int main()
{    
    login(playername, playerpass);
    cout << "DEBUG: This is where the game would be, if we had one..." << endl;
    cin.get();
    return 0;
}

void login(char playername[20], char playerpass[20])
{
    int playerexists(0);
    char newpass[20];
    char verifypass[20];
    cout << "Welcome to <Game Name>, please enter your username:" << endl;
    cin.getline (playername,20);
    ifstream in(playername);
    in >> playerexists;
    if (playerexists == 1) {
         existinguser:
         cout << "DEBUG: Preloading data for existing user " << playername
              << "...";
         ifstream in(playername);
         in >> playerexists >> playername >> playerpass;
         cout << "Success." << endl;
         cout << "Welcome back, " << playername << ".  Please enter your "
              << "password:" << endl;
         cin.getline (verifypass,20);
         if (strcmp (verifypass,playerpass) == 0) { }
         else {
             cout << "Incorrect login, please try again...\nUsername:" << endl;
             cin.getline (playername,20);
             ifstream in(playername);
             in >> playerexists;   
             if (playerexists == 1) { goto existinguser; }
             else { goto newuser; }
         }                                    
    }
    else { 
         newuser:
         cout << "A new player! " << endl;
         do { 
              cout << "Please pick a nice password for " << playername << ":" 
                  << endl;        
              cin.getline (newpass,20);
              cout << "Please re-enter your password for verification:" << endl;
              cin.getline (verifypass,20);
         } while (strcmp (newpass,verifypass) != 0);
         cout << "DEBUG: Creating new PlayerFile..." << endl;
         playerexists = 1;
         playerpass = verifypass;       
         ofstream out(playername);
         out << playerexists << " " << playername << " " << playerpass;
         cout << "DEBUG: Done." << endl;
    }
    cout << "DEBUG: End of login function." << endl;
}

I am wondering if there is a way for the program to prompt the player for a command without making something like:

 
char arg[20];
cin.getline(arg,20);
switch (arg) {
     case save:
           save function call
     case north:
           north movement call
     case quit:
           save and then exit call
     default:
           cout << "What?\n" << endl;
}

Does this make any sense at all? Is this the way I will have to do it, it just seems ugly to me.


Also, how can you check to make sure a string has no spaces in it?

#2 IrishFarmer

    Member

  • Members
  • PipPip
  • 43 posts

Posted 28 October 2005 - 02:40 AM

Well you could always use keyboard input, though code-wise it would look just as ugly.

Its kind of unwieldy if you don't know what you're doing with it, but there is the:

<conio.h>

header and the _getch() function. Of course, I'm guessing your text game isn't message based and thus just loops about as fast as the processor allows it, which can make for poor results with this function.

Otherwise the only real way I know to handle keys "well" so far (besides directx) is with the Windows API. In fact, if you're just starting in Game Design I would recommend you start there. Text based games are good and all, but the experience is very limited.

If you want, I have a 2D engine I can send you the source code for. Its designed with a backbuffer (no flickering), can handle bitmaps, midi music, and seperates the various logic of the game into functions that are 'reactions' to the different windows messages. Or, in other words, you'll know right where to put the code when the user hits a key or clicks the mouse, or its time to handle another frame cycle or the game needs to be redrawn.

Its not the final version, since I want to add a couple of more advanced features to it, but for your purposes it should work.

Anyway, hope that helps.

#3 NomadRock

    Senior Member

  • Members
  • PipPipPipPip
  • 785 posts

Posted 28 October 2005 - 04:50 AM

Basically yes, you are going to need a big case statement. Though if you store your case values, and functions they do into arrays, you can write a loop that will step through them.
Jesse Coyle

#4 monjardin

    Senior Member

  • Members
  • PipPipPipPip
  • 1033 posts

Posted 28 October 2005 - 01:14 PM

I think NomadRock means something like this:

// functions to handle user commands
void save();
void north();
void quit();

// function pointer type definition
typedef void (*func)();

// structure to hold a string and function pair
struct arg_func{
const char* arg;
func f;
};

// an array of string/function pairs
arg_func commands[] =
{
{ "start", start },
{ "north", north },
{ "quit", quit }
};
const int args = 3;

// after getting the user input
// loop through the stored commands
for( int i = 0; i < args; ++i )
{
// compare the input to the stored argument
if( !strcmp( arg, commands[i].arg ) )
{
commands[i].f(); // calls the function
break; // found the command, so we're done
}
}

This would be a lot cleaner using STL classes/algorithms. I did something similar recently for handling sections of an XML document.

I definitely think you should look at using a string class (e.g., std::string) instead of passing around char*'s. It will make your life much easier.

If you want to make sure a string has no whitespace, you will have to iterate over it and check each character. However, cin operator>> uses whitespace as a delimeter by default. So, if you did something like the following you would be guaranteed not to have any whitespace:

std::string arg;
std::cin >> arg;

-Judge

#5 xISOx

    New Member

  • Members
  • PipPip
  • 28 posts

Posted 28 October 2005 - 05:42 PM

IrishFarmer: Thanks for the offer, but at this point in time, other people's code confuses me. This is my absolute first project working with anything remotely like this, so I don't think having graphics to handle would be such a great idea.

Nomadrock/Monjardin: Thanks guys, that'll keep me going for a little while longer. :lol:

But, wouldn't typdef'ing void as *func mean that I couldn't use void for it's normal purpose anymore?

#6 geon

    Senior Member

  • Members
  • PipPipPipPip
  • 812 posts

Posted 28 October 2005 - 05:56 PM

I would advice you to code just the "engine" in a quite general way. The Rooms in your game could each be a textfile with some text to show when you enter, and a list of legal commands and other rooms you can reach from there (filenames).

Using a similar system, you can quite easily build a complete world, while you keep the code clean.

#7 xISOx

    New Member

  • Members
  • PipPip
  • 28 posts

Posted 28 October 2005 - 06:15 PM

OK, I have split up my files, and tried to impliment your idea into themain function, but I get these errors...

52 invalid conversion from `void (*)(char*, char*)' to `void (*)()'

56 no match for 'operator>>' in 'std::operator>> [with _CharT = char, _Traits = std::char_traits<char>](((std::basic_istream<char, std::char_traits<char> >&)(&std::cin)), ((char*)(&arg))) >> std::endl'


#include <iostream>

#include <fstream>

#include <stdio.h>

#include <string.h>

#include "prototype.h"

#include "global.h"

#include "systemfunc.cpp"

#include "playerfunc.cpp"

/* 

 * This next little line means that we will be using standard functions and 

 * calls in the code.  This prevents us from having to type std:: in front of

 * alot of the code.

 */

using namespace std;

// The main function, what starts the actual program.

int main()

{    

//    login(playername, playerpass);

    cout << "DEBUG: This is where the game would be, if we had one..." << endl;

    typedef void (*func)();

    struct arg_func{

           const char* arg;

           func f;

    };

    arg_func commands[] =

    {

    { "lol", lol },

    { "login", login }

    };

    const int args = 3;

    

    char arg[20];

    cin >> arg >> endl;

     

    for( int i = 0; i < args; ++i )

    {

         if( !strcmp(arg, commands[i].arg))

         {

             commands[i].f();

             break;

         }

    }         

           

    

    

    cin.get();

    return 0;

}



#8 Reedbeta

    DevMaster Staff

  • Administrators
  • 4782 posts
  • LocationBellevue, WA

Posted 28 October 2005 - 06:24 PM

Check the definitions of your lol and login functions. One of them is declared as taking two strings, but you are calling them with no arguments.

For the second error, you can't use >> into a C-style string. Try using cin.getline(arg, 20).
reedbeta.com - developer blog, OpenGL demos, and other projects

#9 xISOx

    New Member

  • Members
  • PipPip
  • 28 posts

Posted 28 October 2005 - 07:07 PM

Yuck, I can get the first command in the playerfunc.cpp command to work, but anything else crashes the game...

I have put in checks against invalid options, but I still can't get the second one to work... maybe if I prototype it... nah, that didn't do anything.

Interesting, I took out my checks, so there is nothing else in the for loop, and it works, it just kills the program after two commands are entered.

Any ideas?

#10 eddie

    Senior Member

  • Members
  • PipPipPipPip
  • 751 posts

Posted 28 October 2005 - 07:48 PM

xISOx, to give you another option, that's more jive with C++, you might want to take a look at Functors.

Basically the idea is that you create a class with an overloaded operator(), that can then be called like a function. Something like:


class Save

{

public:

    void operator()()

    {

        // Do your save operations.

    }    

}


int main(int argc, char *argv[])

{

    Save saveFunctor;


/// logic here.


    switch(argument)

    {

        case SAVE:

        {

            saveFunctor();

        }

    }



    return(0);

}


I find Functor's preferable to function pointers for a variety of reasons:

- Functor's can maintain state, whereas function's cannot (without using global variables or static function variables, which all introduce re-entrance issues)
- Functor's (allegedly) can be procedurally integrated by the compiler, so it seems that it can optimized better by the compiler (admittedly, I got this from the parashift CPP Lite faq, I can't empirically verify this)
- Functor's are a lot more elegant than the normal typedefing that's required with function pointers.

If you still want to go with function pointers, I suggest looking at the tutorials at http://www.function-pointer.org/.

Hope that helps. :) I pray you try Functors. :)

#11 roxtar

    Member

  • Members
  • PipPip
  • 94 posts

Posted 28 October 2005 - 07:58 PM

xISOx said:

But, wouldn't typdef'ing void as *func mean that I couldn't use void for it's normal purpose anymore?

I don't think you understood what he did. He has actually given a new name for a pointer to a function which takes no arguments and returns void. Also even if you do something like

typedef int blah;

you will be able to do both

blah x=60;

int y=70;

typedef is just a convenience.

#12 eddie

    Senior Member

  • Members
  • PipPipPipPip
  • 751 posts

Posted 28 October 2005 - 08:53 PM

roxtar said:

I don't think you understood what he did. He has actually given a new name for a pointer to a function which takes no arguments and returns void.

Exactly. I think the xISOx is getting confused by defines vs typedefs, as well as the weird way with which C/C++ do function pointers.


// function pointer type definition

typedef void (*func)();


I know this is weird, but what's actually happening there is we're declaring a function pointer of return type 'void', that takes no parameters, and labelling that 'type' as 'func'.

As roxtar explained, you're not really 'redefining' the meaning of 'void', you're simply saying that 'in place of the word "func", use this much uglier looking type' to the compiler.

Check the http://www.function-pointer.org/ site for more information.

It really shines in the case of function pointers, and generics (templates).

#13 xISOx

    New Member

  • Members
  • PipPip
  • 28 posts

Posted 29 October 2005 - 02:03 AM

Hmm... I think I'll just use switch statements... this is a little advanced for me...

Ugh, I'm completely and utterly confused now.

Is there a way to have a user enter more than one word and it stores each word into a seperate variable?

Switch won't work with strings... I think I'm gonna cry...

#14 eddie

    Senior Member

  • Members
  • PipPipPipPip
  • 751 posts

Posted 29 October 2005 - 09:09 AM

Don't feel bad about using switch. It's a great tool, and you can always optimize into using functors, function pointers, etc, later. :)

Don't be confused either, feel free to ask questions, or just go with what you know until you're more solid. :)

TO have a user enter more than one word and store each word into a separate variable... I'd probably do something inside of a loop with a push_back to a vector.



std::vector<std::string> vecString;

for(/* loop conditions */)

{

  std::string myString;

  cin >> myString;

  vecString.push_back(myString);

}



Then you can access it using the indexing operator (vecString[0]), or better yet, STL iterators.

That said, what are you trying to do at this point? I assume it's still the same thing as before, the whole 'save', 'load', etc?

What you *could* do, is a bit of a change in your 'UI' design.

Why not have the user menu look like this:

[INDENT]User, what do you want to do?

1) Save
2) Load
....

Input:[/INDENT]

Then your code simply has to take in an integer, and you can do a switch based on the integer (you can even use the fact that an integer and an enum silently cast to each other on most compilers, and still use an enum. :)).

Depending on what you want, this might be nicer. If I"m playing a game, I'm not sure I'd want to type in 'save' or 'load' all the time anyways: but that's totally up to you. :)

HTH

#15 xISOx

    New Member

  • Members
  • PipPip
  • 28 posts

Posted 30 October 2005 - 01:49 AM

Well, the point of the program is to have something like a MUD, only with only one player, and obviously seriously limited in functionality by my horrible programming skills.

I am wanting to set up a user prompt for commands that they can do in the game. At any time they would be able to use the save function, or do a number of other things. That's why the numbered options wouldn't work too well.

Well, I'm having no luck with C++ style strings...

//Defines for player command input...
string commandline;
string userinput;

Gives me:

10 C:\Program Files\Apache Group\Apache2\htdocs\GT\global.h `string' does not name a type


?!?!?!?!?!?!?!?!?!

That's in my included file for all my global variables. I have compiled a program using strings before on this compiler, so I don't know what's up with it...

#16 monjardin

    Senior Member

  • Members
  • PipPipPipPip
  • 1033 posts

Posted 30 October 2005 - 03:46 AM

The STL string class is in the <string> header as opposed to the <string.h> header. So, adding "#include <string>" should fix your problem (given that you have the "using namespace std;" line as in your example).
Which compiler are you using?

#17 eddie

    Senior Member

  • Members
  • PipPipPipPip
  • 751 posts

Posted 30 October 2005 - 08:28 AM

Well, you should preface your string with std::string, allowing you're using the STL string.

It's bad practice to use 'using' in a header, as it pollutes every compile unit you use that header in (read: every cpp, and other header that's included after that one).

Make sure you #include <string> before those declarations in that file! (#include <string.h> will automatically put your header file into the using std; namespace, which you should avoid!).

One last note about your game design: it sounds like you're going for a Sierra kings-quest/heros-questish type game (in terms of command processing, anyhow), and so you'll end up having to do string compares against things in a series of if's or some such (if you don't go the functor, loop route).

Just another option if you like: there are a large amount of keys on a keyboard. :) You can bind keys to 'save', 'load', etc. F1 to save, F2 to load, etc, and then you can do a switch statement based upon the scancode for the key (which then can map to a nicer architecture for re-mapping those keys, etc).

I only give that as an option, because you probably don't want to have to build a parser and interpreter for incoming text, and handling error items. Single keys representing items are a lot easier to handle.

Just my two cents. :)

#18 xISOx

    New Member

  • Members
  • PipPip
  • 28 posts

Posted 30 October 2005 - 01:03 PM

I am getting alot of stuff to work now, I just keep having one recurring error that is bugging me. Alot of times, when I enter a line of input, then another line, the program will crash.

Any ideas?

Also, how do you use the switch function with a string?

std::vector<string> commandline;
         for (int x(0); x<3; x++) {
             cin >> userinput;
             commandline.push_back(userinput);
         }

Will loop through and get three different lines of input, this isn't really what I wanted. I want them to be able to enter something like "look sword" and i can grab the look and the sword part as two seperate variables...

#19 moe

    Valued Member

  • Members
  • PipPipPip
  • 270 posts

Posted 30 October 2005 - 02:03 PM

Another approach could be something like this. Note I wrote it without testing.
So be careful about the syntax when using copy-paste. It should be correct but
I don't give a guaranty when I didn't compile and test :)



#include <string>

#include <map>



// define available commands

enum

{

    SAVE=1,

    LOAD,

    QUIT

};



// a sample when it makes sense to use typedef's

typedef map<string, int>	mapCommandID;

/*

    now instead of writing "map<string, int>" all the time you

    can simply write "mapCommandID". this makes your source easier to

    read. "mapCommandID" gives a much better hint about what your map

    actually does. The compiler will just replace the part

    "mapCommandID" with "map<string, int>" wherever it shows up

    in your source code.

*/


mapCommandID	g_mapCommand;    // the actual map



// function to assign your commands to a map

void FillCommandID()

{

    g_mapCommand["save"] 	= SAVE;

    g_mapCommand["load"] 	= LOAD;

    g_mapCommand["quit"] 	= QUIT;

    return;

};




int main()

{

    // set up the commands in your map

    FillCommandID();



    // buffer to hold your command

    char cmd[128];

    // read a command

    cin >> cmd;



    // use the map to act on your command

    switch ( g_mapCommand[cmd] ) 

    {

        case SAVE:

            SafeToFile();

        case LOAD:

            LoadFromFile();

        case QUIT:

            CleanUpAndExit();

        default:

            NotifyInvalidCommand();

    }


    return 0;

}




///////////////////////////////////////////////////////////

///////// another approach for your main-function /////////

///////////////////////////////////////////////////////////

/*

    if you still want to get rid of the switch then you

    could use the functors as it was mentioned in earlier posts.

    basically you would have an array of function pointers.

*/

int main()

{

    int iNumberOfFunctions = 4;                                    // number of avaiable funtions

    void (*arrayFuncPointers[iNumberOfFunctions]) ();	// the array with function pointers

    // set the function pointers to the actual funtion

    arrayFuncPointers[0] = NotifyInvalidCommand;    

    arrayFuncPointers[SAVE] = SafeToFile;

    arrayFuncPointers[LOAD] = LoadFromFile;

    arrayFuncPointers[QUIT] = CleanUpAndExit;



    // set up the commands in your map

    FillCommandID();

	

    // buffer to hold your command

    char cmd[128];

    // read a command

    cin >> cmd;

	

    // call the function

    arrayFuncPointers[ g_mapCommand[cmd] ]();

	

    return 0;

}



Hope that made sense.

edited for lousy grammar...

#20 moe

    Valued Member

  • Members
  • PipPipPip
  • 270 posts

Posted 30 October 2005 - 03:35 PM

About the crashes you mentioned.
(this should apply to vectors as well as to maps I stick to the sample I made in my previous post)


If you did properly assign a value to your map then you have:

g_mapCommand[„safe“] = SAFE;
here „SAFE“ stands for „1“

so you can do this:

int iSomeInteger = g_mapCommand[„safe“] ;

it will be the same as:

int iSomeInteger = 1;

If you did not assign a correct value to a map you get something like this:

int iSomeInteger = g_mapCommand[„nonsense“];

since this was not assigned to your map it is as if you wrote:

int iSomeInteger = NULL;

because the map did not contain the referred string. So the map returns NULL. But you can’t have an integer be NULL.

To avoid crashes you could do this:


if( NULL != g_mapCommand[cmd] )

{

    arrayFuncPointers[ g_mapCommand[cmd] ]();

}

else

{

    NotifyInvalidCommand();

}


Quote

Will loop through and get three different lines of input, this isn't really what I wanted. I want them to be able to enter something like "look sword" and i can grab the look and the sword part as two separate variables...
To do this you would need to parse your command line. Are you sure that’s what you want? Because then it gets more complicated. It would probably be best to write a class as a parser.


edit: Guess I should learn how to write properly…





1 user(s) are reading this topic

0 members, 1 guests, 0 anonymous users