C++ Workshop - Project 2

Started by
94 comments, last by Warlord_Shaun 16 years ago
Quote:Original post by Deyja
The forums tend to eat anything between less-than and greater-than when they aren't inside source tags. These forums allow html. There's no robust way to distinquish between an html tag and, say, a template parameter list - that's why we have source tags.


Additional notes:

- You can use HTML entity escape sequences, e.g. &lt; becomes <, &rt; becomes &rt and &amp; becomes &.

- APs generally get screwed over on this stuff. Please register (or log in if you've already registered).
Advertisement
Quote:Original post by Ceoddyn
I have a question relating to the workshop as a whole - what will happen after we complete the book?


Upon completion of the textbook we'll have 1 more final project. At the end of the project the workshop will be 'completed.'

As for what GameDev plans to do with the Workshop after that, I have no idea. My advice would be to rename the threads based on the chapter contents, ie "Arrays", "Polymorphism", etc....and then just rename the forum from CPP Workshop to "CPP Language" or somesuch. We've got the beginnings of a strong question/answer base here, and I still see quite a few questions on FB about C++. So I think a forum dedicated to the C++ Language, given the language's popularity for game programming, isnt a bad idea.

Cheers!
Jeromy Walsh
Sr. Tools & Engine Programmer | Software Engineer
Microsoft Windows Phone Team
Chronicles of Elyria (An In-development MMORPG)
GameDevelopedia.com - Blog & Tutorials
GDNet Mentoring: XNA Workshop | C# Workshop | C++ Workshop
"The question is not how far, the question is do you possess the constitution, the depth of faith, to go as far as is needed?" - Il Duche, Boondock Saints
So im working on creating the data that will comprise the individual cells of my game, and I want to use a spread sheet to fill in that data... export the spread sheet in a probably comma delimeted format so I can then have the game read that file and create all the cell objects for the game. My question is... would it be better to write a program that created the objects ahead of time and store them as a binary file and then just read the objects from the file... or use the coma delimited file and just convert the text to the needed types of input for the object to be created?

The book doesnt really touch upon the former rather than to say when storing a object in a binary file, the types are preserved. It doesnt say how to call those objects at a later time or a different instance of the program.
First off, no kind of file can do any kind of "preserving of types" intrinsically - the file is *only* a sequence of bytes. The interpretation of those bytes is 100% up to your program.

Next, be careful to distinguish the concept of "a binary/text file" (which implies, but does not dictate, something about what kinds of patterns of bytes the file contains) from reading or writing a file "in binary/text mode". Usually you want to pair the mode to the file type, but there is nothing really forcing that. There is also the concept of using "binary/text I/O operations" (.read()/.write() etc. versus operator<</>>) which is something different yet again.

Next, [OPINION]note that sometimes it makes sense to make a "program that creates the objects ahead of time" in a higher-level language (like Python), in order to prepare it for the core program (which can then be written more simply, though still in C++).[/OPINION] If you just have one CSV file, you need some way to define which kinds of objects are defined in what areas of the file. It's probably a better idea to rearrange things into your own file format which has an explicit *structure*.

Finally, read this.
Zalhman, thanks for the article. Helped explain some things.

This is where im at in my concept of reading files to create objects for the game.

If you read the comments in the source you will see my troubles.

I need to distinguish and end of line in a coma delimited file. In hex the end of line shows up as 0D 0A, but that doesnt translate into any text.

Next I need to take one of the vectors (of char) and concatinate the elements and asign that to a string.. while the other vector (of char) I need to concatinate and turn it into an int.

I may also have other errors as some concepts are still scetchy with vectors.

here is the .h (purposely left the .cpp out since its all straight forward)
class Object{public:    void Setid(int id);	int Getid()const;	void Setchar(std::string word);	std::string Getchar() const;private    int itsid;    std::string itsword;};


here is the main.cpp
#include <fstream>#include <string>#include <vector>#include "object.h"using namespace std;int mian(){    vector <Object> Objv*; //pointer to a vector of objects	std::string filename="test.csv";	ifstream fin(filename); // open the file	char ch;	while (fin.get(ch)) // runs till there is no more	{	 for (int i=0;;i++)	 {	   Object newobj=new Object;  //give the obj a throw away name	   Objv->push_back();  //adds to the vector	             // this now reads the file until the end of a line is reached	  while (ch != //end of line) the hex view of the file shows 0D 0A for end of line	  {	  int commaiterator=0;	  vector <std::string> objtext;	  vector <std::string> objid;	  	  while (ch !=',' && commaiterator==0)	  {	   commaiterator=1;	   for (int i=0;;i++)	   {	    objtext=fin.get(ch);	    objtext.push_back();	    }	    if (ch==',')	     {		   objid.pop_back();  // tighten up the vector	     }	   } // until first comma	  	  while (ch !=',' && commaiterator==1)	  {	   commaiterator=2;	   for (int i;;i++)	   {	    objid=fin.get(ch);		objid.push_back();	    if (ch==',')	    {		  objid.pop_back(); //tighten up the vector	    }	   }	  } // until second comma	 	  int idsize;	  int textsize;	  objtext=objtext.size():	  idsize=objid.size():	  std::string newtext;	  int newid;	  for (int i=0; i<textsize; i++)	  {	   newtext=objtext //need someway to collect the vector	  }      for (int i=0; i<idsize; i++)	  {	   newid=objid //need someway to collect the vector	  }		  	  /// how do i turn objint and objtext into something	  // so that i can do this	  Objv->Setchar(newtext);	  Objv->Setid(newid);	  } // end end of line while	 } // end object for	} //end while	fin.close();	std::string word;	int id;	int size=Objv.size();	for (int i=0; i<size; i++)	{	word=Objv.Getchar();	id=Objv.Getid();	cout<<"Its word: "<<word<<" and its id: "<<id<<endl;	}    	cin<<ch; //just a stop break before exiting console	for (int i=0; (Objv->size())<i; i++)	{	  delete Objv;	}	return 0;}


here is the hex for the input file
seg000:00000000  6F 62 6A 31 2C 31 0D 0A  6F 62 6A 32 2C 32 0D 0A  obj1,1  obj2,2seg000:00000010  6F 62 6A 33 2C 33 0D 0A                           obj3,3
Please see embedded comments. I removed your comments, and cleaned up your formatting a bit too.

#include <fstream>#include <string>#include <vector>#include "object.h"using namespace std;int mian() { // obviously not right ;)  vector<Object> Objv*;  // No reason to make a pointer to a vector. Just make a vector.  // Also, the pointer right now isn't initialized, so trying to use it wouldn't  // work.  std::string filename = "test.csv";  // If you're 'using namespace std;', you don't need std:: there ;)  // Also, you can initialize strings in new style as well:  // string filename("test.csv");  ifstream fin(filename);  // The ifstream constructor requires a const char*, not a std::string; and  // you have to convert explicitly (via .c_str()).  // But here, there is no reason for ANY of that.  // Just do:  // ifstream fin("test.csv"); directly.  char ch;  while (fin.get(ch)) {    for (int i=0;;i++) {      Object newobj = new Object;      // 'new' would dynamically allocate an object, and give you a pointer.      // So you can't initialize an instance like this, only a pointer-to-      // instance. But you don't want dynamic allocation here anyway. To declare      // the 'newobj' and initialize it with the default Object constructor      // (that seems to be what you want), just do:      // Object newobj;      // Yes, it's really that simple. For *class* types, this *will* initialize      // the variable, using the default constructor.      // In particular, do NOT write:      // Object newobj();      // because that would actually be a function declaration.      Objv->push_back();      // You need to say what you're pushing back :)	         // this now reads the file until the end of a line is reached      while (ch != '\n') {        // Just use \n. You opened the file in text mode, which means that        // hex 0d 0a sequence (carriage return and line feed) will be translated        // automatically into a single \n character.	int commaiterator = 0;        // This is badly named.	vector<std::string> objtext;	vector<std::string> objid;	  	while (ch != ',' && commaiterator == 0) {	  commaiterator = 1;	  for (int i=0;;i++) {	    objtext = fin.get(ch);	    objtext.push_back();            // Uh, I think you want to be writing into a string, and then            // push_back'ing the string onto the vector?            // Also, how are you planning to break out of this loop?	  }	  if (ch==',') {            objid.pop_back();            // This does not "tighten up" the vector. If you want to            // shrink the space to fit the elements, use:            vector<string>(objid).swap(objid);            // Except, why are you operating on objid now instead of objtext?          }        } // until first comma	          while (ch != ',' && commaiterator == 1) {	  commaiterator=2;	  for (int i;;i++) { // need to initialize i to 0	    objid=fin.get(ch);            objid.push_back();	    if (ch==',') {              objid.pop_back();              // See above. Also, why is this 'if' inside the 'for' this time,              // but outside the 'for' previously?	    }	  }	} // until second comma	 	int idsize;	int textsize;	objtext=objtext.size(): // should be textsize= ?	idsize=objid.size():        // Should be semicolons instead of colons        // Also, initialize instead	std::string newtext;	int newid;	for (int i=0; i<textsize; i++) {	  newtext=objtext // missing semicolon          // I have no idea what you mean by "collect the vector".	}              for (int i=0; i<idsize; i++) {	  newid=objid // similarly here	}	        Objv->Setchar(newtext);        // Oh, you were looking for a string to number conversion here?        // Use a std::stringstream to do the conversion; examples about around here	Objv->Setid(newid);      } // end end of line while    } // end object for  } //end while  fin.close(); // not necessary  std::string word;  int id;  int size=Objv.size();  for (int i=0; i<size; i++) {    word=Objv.Getchar();    id=Objv.Getid();    cout<<"Its word: "<<word<<" and its id: "<<id<<endl;  }      cin<<ch;  for (int i=0; (Objv->size())<i; i++) {    // No; you made a pointer to a vector, not a vector of pointers.    delete Objv;  }  return 0; // don't need this}


But that said, you're going about things *completely* the wrong way, and the above is pretty much unsalvagable garbage anyway. :(

Notes:

- As indicated in the embedded comments, you basically have a text file, so you should just read in text mode (as you currently do) and don't need to worry about those end-of-line sequences; they will be translated automatically.

- You don't need to iterate character by character. Use std::getline() to read the file up to some value, directly reading strings.

- Don't collect everything into two "parallel" vectors and then try to process the vectors. Instead, create Object instances as you go.

- Give the object a constructor, so that you can do that directly. Use an initializer list. (Do the string-to-int conversion *before* invoking the constructor).

- Don't use those god-awful gets and sets. Please.

It's really a lot easier to do this than you think:

// *** spoilers! ***// This is based off the sample data you provided.// Note that in .csv files, things could actually be much more complex,// in cases where the data is supposed to contain commas and/or quotation marks.// Proper, robust parsing of .csv is *not easy*; you might want to look for// a library for it, in fact.#include <fstream>#include <string>#include <vector>struct Object {  int itsid;  std::string itsword;  Object(int anid, const std::string& aword) : itsid(anid), itsword(aword) {}};using namespace std;int main() {  vector<Object> objects;  ifstream fin("test.csv");  string text;  int id;  // While we are able to consume any leading whitespace (this will also  // skip past the newline left by a previous read if any), read up until a   // comma into text, and subsequently read into a number:  while (fin >> ws && getline(fin, text, ',') && fin >> id ) {    // Create an object with those values and put it in the vector.    objects.push_back(Object(id, text));  }  // display them.  for (int i=0; i < objects.size; ++i) {    Object& o = objects;    cout << "Its word: " << o.itsword << " and its id: " << o.itsid << endl;  }}

Quote:Original post by Zahlman
- As indicated in the embedded comments, you basically have a text file, so you should just read in text mode (as you currently do) and don't need to worry about those end-of-line sequences; they will be translated automatically.


Now that I know what the 0A 0D mean that will make it easier for text mode anyway. Im going to have to push this to binary I think since some of the objects will eventually have pointers to other types of objects. But, one step at a time.

I also dont seem to quite understand whats going on here

while (fin >> ws && getline(fin, text, ',') && fin >> id ) {    // Create an object with those values and put it in the vector.    objects.push_back(Object(id, text));  }


I understand the push back statement... thats actually a nice way to do it except did you mean objects(id, text) since that is the reference name you gave the Object. (object was a bad class name.. but made sense to me)




Quote:
- Don't use those god-awful gets and sets. Please.


sorry, havent been at this for very long. I like doing things the long way as it tends to make more logical sense when every step is written out. Ill start to shorten code as I gain more understanding of whats going on.


You said in my source:
Quote:
vector<Object> Objv*;
// No reason to make a pointer to a vector. Just make a vector.
// Also, the pointer right now isn't initialized, so trying to use it wouldn't
// work.

//and

Object newobj = new Object;
// 'new' would dynamically allocate an object, and give you a pointer.
// So you can't initialize an instance like this, only a pointer-to-
// instance. But you don't want dynamic allocation here anyway. To declare
// the 'newobj' and initialize it with the default Object constructor
// (that seems to be what you want), just do:
// Object newobj;
// Yes, it's really that simple. For *class* types, this *will* initialize
// the variable, using the default constructor.
// In particular, do NOT write:
// Object newobj();
// because that would actually be a function declaration.


I like pointers much more than straight reference (right word?) that exist on the stack. With a pointer I know it wont go out of scope and gives me more felxability... not to mention not having to mess with the copy constructor and destructor everytime I manipulate an object. I think what I meant to do was
vector<Object> objvec;*pobjvec=objvec;


The reason I want to dynamically create the object is for the same reasons I want the vecter to be a pointer. In the end these vectors are only temporary holders of the objects. Once the objects are created in the larger project they will contain an x,y coordinate that will map them to a multidimensional array so I can activate objects by array arithmatic. Oh, and I should have done this Object *newobj = new Object;

Please excuse the noobish lingo... but that really is the hardest thing to learn. The rest comes from doing.
I was thinking about using a more readible format, either XML or some pseudo class declarations, and build some kind of parser to instantiate objects for this. Or maybe instantiate my objects with DOM nodes which contain their starting values.

either:
<room id="1">   <exit room="2"/>   <exit room="3"/>   <exit room="4"/>   <item type="weapon" name="BastardSword"/>   <enemy level="2"/></room>


or:
class Room{   id=1   exit.room=2   exit.room=3   exit.room=4   item.type=weapon   item.name=BastardSword   enemy.level=2}


This way it is easier to design your game. Once you have a problem inside your binary data it is hard to fix.
You're giving away all my ideas! :P

Anyway, I'd like to know how long would this kind of mini-project take for an experienced programmer? It seems to be taking awfully long for me, and the code's getting hard to read way too easily; I've attached the current state of affairs, and while it doesn't have containers and combat in it, the source is already feeling bloated to me.

edit: wee, finally got some time to spare. Containers are in, and you can now manipulate your inventory as well.

edit2: cleaned up the code a bit, and ended up tucking most of the RTTI under "natural" polymorphism :blush:

bloat compiled bloat

[Edited by - Darkstrike on October 29, 2006 4:15:53 AM]
Quote:Original post by westond
Now that I know what the 0A 0D mean that will make it easier for text mode anyway. Im going to have to push this to binary I think since some of the objects will eventually have pointers to other types of objects. But, one step at a time.


Ouch. Be VERY careful. You CANNOT just write a pointer value into a file and expect it to work when you read it back in. Why? Because even if you used arcane hacks (and believe me, they are arcane) to try to make every object be at the same place in memory as it was in the previous run (in addition to having them merely be "linked up" in the same way), you can't guarantee that all of the same chunks will be *available* to you this time. Instead, you need to write some kind of data that abstractly represents the way the objects pointed at each other before.

Seriously, just read the FAQ chapter (the link above).

Quote:I also dont seem to quite understand whats going on here


It's explained in the comment directly above:

Quote:
// While we are able to consume any leading whitespace (this will also
// skip past the newline left by a previous read if any), read up until a
// comma into text, and subsequently read into a number:
while (fin >> ws && getline(fin, text, ',') && fin >> id ) {


"while (fin >> ws", for example, attempts to do "fin >> ws". This operator actually will always return 'fin' back again. The skipping of any leading whitespace (this is the somewhat magical behaviour of reading into std::ws - it's the same manner of beast as std::endl for example) is done *as a side effect*. Then, since we're in a boolean context, 'fin' evaluates as true if and only if the stream is still "good"; i.e. the operation was successful. Similarly the other stream reads.

More info.

Quote:
I understand the push back statement... thats actually a nice way to do it


It's standard (idiomatic) to add things to a vector that way.

Quote:except did you mean objects(id, text) since that is the reference name you gave the Object. (object was a bad class name.. but made sense to me)


No. Here, 'Object(id, text)' is a constructor call. It creates a temporary Object, and passes it to the push_back. push_back accepts the temporary by const reference (because it's a *const* reference, this will work with a temporary), and copies the Object into the vector. At the end of the statement, the temporary is out of scope, and its destructor (which happens in our case not to do anything) is called.

This is normal and idiomatic use of the language; it's how you get "literals" of object type instead of only having numeric/character/string literals.

Quote:
I like pointers much more than straight reference (right word?) that exist on the stack.


Values.

Quote:With a pointer I know it wont go out of scope and gives me more felxability...


The only flexibility you gain is the flexibility to mess up, add complexity to what you have to write, and add performance overhead. A stack value doesn't go out of scope until the end of the scope you declared it in; if you're having problems, think more about how you scope your variables. Anyway, you *want* to scope your variables as tightly as you possibly can; this is simply a generalization of the standard "globals are bad mm'kay" advice.

Quote:not to mention not having to mess with the copy constructor and destructor everytime I manipulate an object.


Um. I don't have any real idea what you're thinking here. If you're talking about calling a copy constructor or destructor, that's done *automatically by the language*. In fact, it's using extra dynamic allocation that produces extra burden for yourself: you become required to "mess with the destructor" by calling delete (or delete[] as necessary).

If you're talking about *writing* a copy constructor or destructor, then (a) whether or not you *need* one has absolutely, 100% nothing to do with whether you're going to create those objects on the stack or on the heap, and everything to do with dynamically allocated *members of the object*; and (b) the actual process of writing it is again totally independant. An object should not *itself, ever* care whether it is on the heap or on the stack (because it's pretty much impossible for it to *find out* anyway).

Quote:I think what I meant to do was
vector<Object> objvec;
*pobjvec=objvec;


That still doesn't make it a vector-of-pointers-to-Object (which you still don't need). It merely creates the vector-of-Object on the stack, and then copies it to the vector-of-Object that pobjvec is already pointing at. Note that this, in turn, is different from causing pobjvec to point at objvec (you would do that with "pobjvec = &objvec"). Doing things this way (a) requires that pobjvec *already* points at a valid vector-of-Object; (b) overwrites the contents of that vector-of-Object with the contents of objvec.

Quote:The reason I want to dynamically create the object is for the same reasons I want the vecter to be a pointer. In the end these vectors are only temporary holders of the objects. Once the objects are created in the larger project they will contain an x,y coordinate that will map them to a multidimensional array so I can activate objects by array arithmatic. Oh, and I should have done this Object *newobj = new Object;


That's not a reason, and to be honest you're making less and less sense. Please, I'm trying to make this easier for you. Why resist that?

This topic is closed to new replies.

Advertisement