Jump to content
  • Advertisement
Sign in to follow this  
TigerKnee

Need some help with structure for a tile-based strategy game

This topic is 1747 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

I'm trying to make a simplistic space-themed strategy game in c++ and I need some help with this since I'm not a great programmer (my skill level is like, dabbling hobbyist?). I got a feeling my structure isn't the most robust which means I'm already in the process of ripping stuff apart and putting them back together.

 

The "game world" is just a simple 15x15 square tile setup. Outside of empty space, there are really only 2 possible objects that exist: "Planets" and "Ships." They're are set up like this.

struct mapObj {
	int type;
	int faction;

	int cX;
	int cY;
};
struct ship : public mapObj
{
	ship();
	ship(int ntype, int nfaction, int nX, int nY);

	int type;
  	int faction;
 
	int cX;
	int cY;

	int turnUsed;
	int specialUsed;

	void destroyShip();
};
struct planet : public mapObj
{
	int type;
	int faction;

	int cX;
	int cY;

	int currentHealth;
};

So I'm not too familiar with trying to use polymorphism but the two derived classes can get rid of repeated stuff like "type" and "faction" since I already derived them from the base class, right?

 

Anyway, I need to be able to compare variables in two objects together, whether it be Ship-Ship or Ship-Planet, and then I'm going to need to be able maybe destroy and create new ships, and I'm going to need to move them around on the map and check for occupied tiles. Currently the data for individual ships are being stored in a "playerData" class.

 

How should I go about doing this? Can I use pointers for this? I'm extremely unfamiliar about that concept. As in

mapObj* tileContent[15][15];

And this array will point to either nothing, a ship or a planet which I can use to compare to each other?

 

edit: Darn, should I have posted this in "For Beginners" instead?

Edited by TigerKnee

Share this post


Link to post
Share on other sites
Advertisement

Yes, if you inherit you can (and should) get rid of the redundant variables. The derived classes' identical variable names will actually hide (but not replace) the base class's variables.

 

The array you created doesn't create any pointers at all - it creates instances of mapObj, not pointers to mapObj. If you assigned a planet or ship to a mapObj, it'll badly "slice" the planet or ship and cram only the mapObj part of the data into the mapObj you are assigning it to, which isn't what you want. Most of the time, it's something nobody wants.
 

If you want to pretend a derived object is the base object, you have to do it through pointers or references. However, the objects that pointers and references point to have to live longer than the pointers and references themselves, or the object will get deleted and the pointer will be pointing at invalid memory, leading to all kinds of hard-to-detect bugs.

 

So! To make sure that all the ships and all the planets stay alive, you can create each new one in a std::vector (a resizable array class).

struct Point
{
	int x = 0;
	int y = 0;
};

enum class Type {Ship, Planet};
enum class Faction {Boskonian, Lensmen};

class Object
{
public:
	Faction faction;
	Point cellPosition;
	
	//A 'pure virtual' function. Derived classes will define the function.
	virtual Type GetType() = 0;
};

class Ship : public Object
{
public:
	//Override the virtual faction and return the proper type of this class.
	Type GetType() override { return Type::Ship; }
	
	unsigned turnsUsed = 0;
	unsigned specialsUsed = 0;
};

class Planet : public Object
{
public:
	//Override the virtual faction and return the proper type of this class.
	Type GetType() override { return Type::Planet; }
	
	int currentHealth = 100;
};

struct Board
{
	static const unsigned Width;
	static const unsigned Height;
	
	std::vector<Ship> ships;
	std::vector<Planet> planets;
	
	std::vector<Object*> cells;
	
private:
	Board()
	{
		//Create the board's cells, (Width * Height) cells.
		cells.resize((Width * Height), nullptr);
	}
	
	//Returns the object at a specific cell.
	//Returns a null pointer if no object exists there, or if that location is out of bounds.
	Object *GetObjectAt(Point position)
	{
		if(position.x < 0 || position.x > Width
		|| position.y < 0 || position.y > Height)
		{
			return nullptr;
		}
		
		unsigned index = (position.y * Width) + position.x;
		
		return cells.at(index);
	}
};

const unsigned Board::Width = 15;
const unsigned Board::Height = 15;

Note: I'm using C++11 features in the above code - make sure you enable C++11 features in your compiler, because they are very very useful features and if you're learning C++, you might as well be learning the most recent version. Are you using Visual Studio or MinGW or GCC?

 

The problem with that, however, is it's possible for the cells of the board to get unsynced from the 'position' of the Objects. So instead, I'd get rid of the cells (but keep them there imaginarily in your mind), and look up what object is where, like this:

        //Returns the object at a specific cell.
	//Returns a null pointer if no object exists there, or if that location is out of bounds.
	Object *GetObjectAt(Point position)
	{
		//Check if a ship matches the position, and if so, return it.
		for(Ship &ship : ships)
		{
			if(ship.cellPosition == position)
				return &ship;
		}
		
		//Check if a planet matches the position, and if so, return it.
		for(Planet &planet : planets)
		{
			if(planet.cellPosition == position)
				return &planet;
		}
		
		//No ship or planet is here, so return null.
		return nullptr;
	}

And get rid of the 'cells' vector entirely.

Your game can still have 'cells' as a concept, but in this case, it doesn't need the 'cells' to be in the actual code.

Share this post


Link to post
Share on other sites

Note: I'm using C++11 features in the above code - make sure you enable C++11 features in your compiler, because they are very very useful features and if you're learning C++, you might as well be learning the most recent version. Are you using Visual Studio or MinGW or GCC?

 I'm using... uh, Visual Studio 2010. My stuff's kind of outdated because I haven't touched programming for a long time (a stint in the military, you see). Are C++11 features available in it?

Share this post


Link to post
Share on other sites

Note: I'm using C++11 features in the above code - make sure you enable C++11 features in your compiler, because they are very very useful features and if you're learning C++, you might as well be learning the most recent version. Are you using Visual Studio or MinGW or GCC?

 I'm using... uh, Visual Studio 2010. My stuff's kind of outdated because I haven't touched programming for a long time (a stint in the military, you see). Are C++11 features available in it?

"auto" and an early version of lambdas are available, but most C++11 features are not. VS2012 adds the very useful range-for loop, but many useful features are still missing.
If I were you, I'd probably switch straight into VS2013 preview version. VS2012 at least.

Share this post


Link to post
Share on other sites

Note: I'm using C++11 features in the above code - make sure you enable C++11 features in your compiler, because they are very very useful features and if you're learning C++, you might as well be learning the most recent version. Are you using Visual Studio or MinGW or GCC?

 I'm using... uh, Visual Studio 2010. My stuff's kind of outdated because I haven't touched programming for a long time (a stint in the military, you see). Are C++11 features available in it?

Thanks for serving (in whatever nation's army you are in)! If it's not too much trouble, updating to the latest software and C++ compiler would be a great idea. You don't have to stay updated, but it's good to update to the very latest stable version of your software every year or two.
In this case, I'd highly recommend it, because the new C++ standard (C++11) was finalized and published in 2011, and the later compiler versions have started to implement the C++11 features. I'm not personally familiar with Visual Studio's C++11 status, so I'd try to update to the very latest hoping for as many C++11 features as possible.
 
In the tiny snippet of code I posted above, I actually used five different C++11 features that you will want to have access to, and that I find very useful and use on a regular basis. C++11 just makes the language overall nicer in a dozen different ways.
 
A simple example:
struct Point
{
	int x = 0; //<-- It's so much easier and safer and cleaner to initialize your variables straight in the struct or class.
	int y = 0; //Previous versions of C++ only allowed that for certain types in certain situations. C++11 makes it fully available.
};
C++11 also extends and expands the standard library with very useful classes.

Share this post


Link to post
Share on other sites

struct Point
{
	int x = 0; //<-- It's so much easier and safer and cleaner to initialize your variables straight in the struct or class.
	int y = 0; //Previous versions of C++ only allowed that for certain types in certain situations. C++11 makes it fully available.
};
C++11 also extends and expands the standard library with very useful classes.


Why did that take so long to make it into the standard? It seems to make so much sense.

Share this post


Link to post
Share on other sites

Why did that take so long to make it into the standard? It seems to make so much sense.

 

No idea, but it's been on my C++ wishlist for a long time! smile.png

 

Probably part of the reason is that they standardize in waves. They don't standardize one feature, then another feature, then another feature - but like to release a whole new standard with alot of new features that work together.

Part of the reasons each new standard takes a long time, is anything they standardize, unlike some other languages, they have to support for pretty much ever - they don't like even deprecating things, though they will when they need to (e.g. std::auto_ptr). So they don't want to rush into things. C++ is a language that needs written code to be stable and maintainable for multiple decades ideally, so they do everything they can to reduce breaking existing code, and do everything they can to ensure that new additions don't need to be deprecated later.

 

Though they are trying to move towards more incremental standards that come closer apart. The next standard, a minor upgrade, is set to be released in 2014 if everything goes well. The C++14 standard isn't as big a deal to upgrade to as the C++11 standard - the C++11 standard adds a huge amount of useful features and standard library classes. They also release Technical Reports (which they did for C++11 as well), to try out new features in compilers for several years to see if they actually work out well.

 

I don't know why that specific feature wasn't in C++ originally, but probably because C didn't have that feature. C probably didn't have that feature, because structs didn't have constructors (or member functions) and normal variables aren't initialized (as an optimization). That's just my guessing, though!

Share this post


Link to post
Share on other sites

So I haven't completely rewritten my code/classes or upgraded yet, but I need some clarification on the nature of Base/Derived classes and accessing stuff that's only in Derived classes, and the GetObjAt function that was provided. Here's what I experimented on (names are... uh, a bit messy, sorry if there's any confusion anywhere):

 

edit: I changed GetObjAt a little bit, but only the fact that I didn't use a Position struct and just used int x/y values.

// For the purpose of this code, there is already a ship at coordinates x10,y10.
// ship *s1 = GetObjAt(10,10); 
// This doesn't compile. Since GetObjAt returns... a pointer to a MapObj, I think? It means I can't just turn it into a derived class ship pointer?

mapObj *s1 = GetObjAt(10,10); // This compiles, however, I can only access functions and variables from the base class. 
//If I wanted to find specific stuff from derived classes e.g turns used for ships, health for planets, I'm not able to access them with this.

if (s1 != nullptr) // if I don't do this and s1 is null, it'll crash because the next few lines try to access stuff from nothing.
{
	if (s1->clType == 1) // clType = either ship (1) or planet (2)
	{
		ship* pShip = (ship*) s1; // Now I can access stuff in the derived class. 
		cout << "Special used: " << pShip->specialUsed << endl; //But is there any problems with doing stuff like this?
 	}

	if (s1->clType == 2) // Planet (2)
	{
		//Blah blah cast to Planet instead.
	}
}

Am I on the right track with this, or is there a cleaner method that I should be using instead?

Edited by TigerKnee

Share this post


Link to post
Share on other sites

You are basically just hacking together your own version of dynamic_cast and repeatedly switch on the type of each object. Whenever you need that its a sign of bad design.

You would be better off just having a vector<ship> and a vector<planet> and then working on those whole vectors knowing already whats inside each of them.

Share this post


Link to post
Share on other sites

Am I on the right track with this, or is there a cleaner method that I should be using instead?

I'm not convinced that your ships and planets are actually so related that you should try to use them polymorphically. Polymorphism is about using operations interchangeably on related objects, without having to care which actual type it is.

No matter what you do, you should not have casts all over the place in your code; having to cast is a sign you are probably doing something poorly.

If you get a ton of new object types, and need the grid to be able to point to them, I can see how it makes sense for them to have a common base class for that purpose. But in that case, it would be cleanest to keep the base class totally empty. I'd keep the Object pointers strictly inside the grid data structure, give the grid getter functions which return a specific type, and do no casting whatsoever outside those functions.

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.

Participate in the game development conversation and more when you create an account on GameDev.net!

Sign me up!