Bash my code!

Started by
8 comments, last by Skizz 16 years, 10 months ago
Hello again, since my recent thread here I've decided to recode my game caPman (any similarities to Pacman are purely coincidental) from the ground up. After a few days of work, it's now in playable state. Nowhere near complete, but the basic gameplay works. Graphics are fully skinnable, and custom levels are easily made ( all based on text files). I've decided to post it here including all source code to give all of you an opportunity to trashtalk my coding, and to help me (and maybe others) learn from my mistakes. Maybe some of you will even enjoy playing it for a few minutes :) This is a beginner's program. To be exact, this is my 3rd complete game (after a Minesweeper and Tetris clone) , so don't expect too much. caPman is written in C++ using the Allegro library. All images and code made by me. Windows version + source code downloadable here: http://download.yousendit.com/DC84768630DBEA7B Let the flaming begin! Edit: updated URL [Edited by - Gyna on June 12, 2007 2:05:14 PM]
Advertisement
Quote:Original post by Gyna
Hello again, since my recent thread here I've decided to recode my game caPman (any similarities to Pacman are purely coincidental) from the ground up.

After a few days of work, it's now in playable state. Nowhere near complete, but the basic gameplay works.
Graphics are fully skinnable, and custom levels are easily made ( all based on text files).

I've decided to post it here including all source code to give all of you an opportunity to trashtalk my coding, and to help me (and maybe others) learn from my mistakes. Maybe some of you will even enjoy playing it for a few minutes :)

This is a beginner's program. To be exact, this is my 3rd complete game (after a Minesweeper and Tetris clone) , so don't expect too much.

caPman is written in C++ using the Allegro library. All images and code made by me.

Windows version + source code downloadable here:
Download me and my source!

Let the flaming begin!


Beginner in Game Development?  Read here. And read here.

 

Bumping with updated .exe and source.

New since yesterday:

ability to level up - 2 random ghosts get smarter and faster each level up

level, score, lives and fruit display

highscore system (yes i know it saves them in plain text - whats a good way to make editing highscores harder? )

and probably some other stuff I forgot to mention.


To come:

sound

more levels

different kinds of fruit

some polish

and maybe a level editor



ok well im not gona say this is a good way to do what you asked just a way to do it.

string ToSave; //Store your high score value in hereint DefaultIncrement = 4;for(int i =0; ToSave != NULL; i++;){DefaultIncrement * i;ToSave +=DefaultIncrement;}//Write the line to file


Doing something like that will change the value before its put into file
kind of a crude encryption

just remmber though when reading it you would have to do the reverse :)

i did something like that back when and it worked quite well.

Well hope that helps some what or at least gives you an idea
Thanks for the suggestion Jouei, sounds interesting and I'm going to give it a try.
On another note, I'm not satisfied with the way I do the variable loading from the config file. It seems very unwieldy, requires lots of code, and somehow I think there must be a better way to do it.

First, I do a getline ( file, buffer, '='), and then I have a long chain of if-else commands to get the value into the right variable.

Any suggestions? Here's the code:

cfg.open( cfgfile.c_str() );	while (cfg)	{		do {			if ( cfg.eof() ) break;			c=cfg.get();		} while (c < 'A' || c > 'z'); 		pos= cfg.tellg();		pos--;		if (pos<0) pos=0;		cfg.seekg(pos);				getline(cfg, buffer, '=');		if (buffer == "FIELDWIDTH") cfg >> width;		else if (buffer == "FIELDHEIGHT") cfg >> height;		else if (buffer == "TILESIZE") cfg >> tilesize; 
one way would be to list the words in the cfg file.
when you have the list you parse them like

string Holder = list.front; list.pop();
string Value = list.front; list.pop();
map Values[ Holder ] = Value;

then when you want a specific value

float PlayerHealth = tofloat( Values["HEALTH"] );


You can create a neat solution that involves templates. What I'm going to do is associate value names with specific member variables, keeping the overhead of doing so to a minimum and also the minimising the cost of maintaining the code, i.e. given two strings "hitpoints" and "100" to set a member variable 'm_hitpoints' of a class to the value 100.

We can take advantage of C++'s pointer-to-member types to implement this. The general form is:
type classname::*pointer;

where 'type' is the member type, 'classname' is the class owning the member and 'pointer' is a variable name.

Secondly, the STL provides a useful map class which allows for a one to one association of values. So, we ideally want something along the lines of:
std::map <char *, type classname::*>

where the association is between a string (the "hitpoints") and a pointer to a member (the 'm_hitpoints'). Unfortunately, the second template parameter to the map class needs to be a fixed type (i.e. all values in the map are the same type) whereas what is ideally wanted is a facility to point to members of any type. Therefore there needs to be some kind of proxy object to allow this:
std::map <char *, proxy *>

One way to implement this proxy is to use a virtual base class:
class proxy{  virtual void SetValue (char *item_name, item_to_set) = 0;};

but this still needs some type information which can be added via templates:
template <class T>class ISetter{public:  virtual void Set (char *buffer, T *object) = 0;};

This can be used as the second template parameter to map providing the type T is defined when the map type is instantiated. The reason for the above will hopefully become clearer.

Given the virtual base class, there needs to be concrete implementations of it to be useful:
template <class T, class V>class Setter : public ISetter <T>{public:  Setter (V T::*member) : m_member (member)  {  }  virtual void Set (char *buffer, T *object)  {    ValueConverter::Convert (buffer, object->*m_member);  }private:  V T::*m_member;};

Whoa. That's a load of new stuff. The Setter class above wraps the pointer to member functionality, m_member is the actual pointer to member and since the class is a template it's a pointer to any type of any class. The constructor is straightforward and only sets the pointer to member. The Set function is the concrete implementation of the base class virtual function. In the Set function there is the value string ('buffer'), the object containing the item to set ('object') and the pointer to the member to set ('m_member'). That's everything needed to set a value. The ValueConverter class is just a set of helpers:
class ValueConverter{public:  // if the conversion from int is invalid for the given type,  // the compiler will generate an error/warning...  template <class T>  static void Convert (char *buffer, T &value)  {    value = atoi (buffer);  }  // ...in which case, do an explicit implementation  static void Convert (char *buffer, float &value)  {    value = (float) atof (buffer);  }};

After all that, a map can be created:
  std::map <char *, ISetter <some type> *>

There still needs to be a definition for 'some type'. Putting the map into a templated class solves this:
template <class T>class Settable{protected:  static bool SetValue (char *item, char *value, T *object)  {    bool      found = false;    if (m_setters.find (item) != m_setters.end ())    {      m_setters [item]->Set (value, object);      found = true;    }    return found;  }  static std::map <char *, ISetter <T> *>    m_setters;};

This class defines the mapping between value name and member and provides a method to call the appropriate setter function for a given value name. SetValue returns true if the value name is defined and false if not. m_setters is a static member so the mapping is shared by all instances of the template. This is OK since the use of a pointer-to-member needs an object pointer as well which is provided as the argument to SetValue.

Now to use the above:
class Test1 : private Settable <Test1>{public:  Test1 (void) :    m_int_value (0),    m_short_value (0),    m_float_value (0.0f)  {    // since m_setters is static, it only needs initialising once    if (m_setters.empty ())    {      m_setters ["intvalue"] = new Setter <Test1, int> (&Test1::m_int_value);      m_setters ["shortvalue"] = new Setter <Test1, short> (&Test1::m_short_value);      m_setters ["floatvalue"] = new Setter <Test1, float> (&Test1::m_float_value);    }  }  bool SetValue (char *item, char *value)  {    return Settable::SetValue (item, value, this);  }private:  int    m_int_value;  short    m_short_value;  float    m_float_value;};// the actual m_setters for the Test1 classstd::map <char *, ISetter <Test1> *> Settable <Test1>::m_setters;

and to set values:
  Test1 t,p;  t.SetValue ("intvalue", "42");  t.SetValue ("shortvalue", "666");  t.SetValue ("floatvalue", "3.1415");  p.SetValue ("intvalue", "1");  p.SetValue ("shortvalue", "2");  p.SetValue ("floatvalue", "3");

This makes the loader code is of the form:
while not end of file  get next line  parse line to get item name and value string  object.SetValue (item name, value)end while


Hope that helps, or at least gives you ideas.

Skizz
Forgot to add that the only maintenance needed is to ensure the constructor for the class is in sync with the members, i.e. if you change from an int to a float, update the constructor to create a Setter of the right type. The compiler should point out any problems due to types being wrong. Adding new members requires adding entries in the constructor.

Skizz
LackOfGrace and Skizz, thanks for your suggestions, that's a ton of info!

Skizz, from the first look I've had at your solution, it seems like a very elegant way to do this. But at the moment it's still a bit over my head, I haven't used templates nor STL containers yet. Pointer to member types are new for me too :) Going to do some studying on those topics and post back here if I still have questions.

In the meantime, thanks alot for your input! Must've taken ages to type up :)
No problem, feel free to ask as many questions as you want.

Looking at my response above, I've noticed that the solution should work recursively. You can overload the ValueConverter::Convert function to accept a class type that then calls the loader function recursively allowing for data files of the form:

pos={x=10,y=20,z=15}

where the bit inside '{}' is the buffer argument to

ValueConverter::Convert (char *buffer, Vector3 &vec)

for example and providing that the owner class does:

m_setters ["pos"] = new Setter <Test1, Vector3> (&Test1::m_pos);

and the file parser identifies text between '{}' as a single value. It would also be possible to extend the system to allow data to be written to files as well as read from file.

Don't worry if it doesn't make sense, it will do eventually.

Skizz

This topic is closed to new replies.

Advertisement