17 Jan, 2011, Kayle wrote in the 1st comment:
Votes: 0
So, I'm back to working on that ship system that I posted about in the inheritance thread oh so long ago, and I've come across a small problem that I hadn't anticipated before designing the system and selling it to the rest of the administrators of the MUD it's for… So here's my problem.

Ships will be created by players by taking various components and installing them onto a Chassis, which then becomes a ship once parts are installed. As the parts are assigned, they're stuck into various fields on the Ship class. The bit I'm having trouble with (because the rest of the process is easy) is weapons. The problem is that Chassis's will have varied amounts of places to mount a weapon. To use a common example, an A-Wing chassis would have 2 weapon mounts, while an X-wing would have 4 weapon mounts.

How do I handle this code-wise without making a bunch of fields like:
class Ship
{
public:
// Stuff

private:
Weapon *m_Weapon1;
Weapon *m_Weapon2;
Weapon *m_Weapon3;
Weapon *m_Weapon4;

}

?

Is it possible to do something like:
Weapon *m_Weapons[m_Chassis->getWeaponMounts( )]


or something along those lines?
17 Jan, 2011, oenone wrote in the 2nd comment:
Votes: 0
How about
std::vector<Weapon*> m_Weapons;

?
17 Jan, 2011, plamzi wrote in the 3rd comment:
Votes: 0
While I haven't written a single line in C++, as with any object-based language, it should be possible to instantiate the ship class with arguments in your constructor. That's one way to make sure you're instantiating a ship with an A-Wing chassis vs. an X-Wing chassis. If you instantiate ship with a chassis type, and the number of slots for this chassis type are predefined, you'd be able to do what you're suggesting.

If the chassis is not known at the time when you instantiate a ship, you can write setter methods and make sure you first call the setter method for the chassis type, and only then call the setter method for the array of pointers to weapons.

Of course, you can always have a flexible weapons slot structure of weapon struct + pointer to next weapon struct.
17 Jan, 2011, David Haley wrote in the 4th comment:
Votes: 0
I would just make it a vector, yes, and then have another field which stores the number of allowable weapons. Then in the API that plamzi suggested, make sure you check the vector length against that number, and you're set.
17 Jan, 2011, Runter wrote in the 5th comment:
Votes: 0
To do what your code suggested you'd do:
Weapon **m_Weapons;


Then allocate the memory for the array. But the vector solution is proper.
18 Jan, 2011, Kayle wrote in the 6th comment:
Votes: 0
Alright, so I don't really know why std::vector didn't occur to me in the first place, but that makes perfect sense. The one thing I'm not real sure on with regards to STL containers like std::list and std::vector, etc. is how to effectively and efficiently wrap them in get/set functions so that the vector itself isn't exposed outside the class. Since this'll actually be my first use of std::vector, I'll list out all the things I'm going to need to be able to do to these weapons, and have you guys double check that this is indeed the right solution for the problem.

1. Remove/Replace elements without shifting others to fill gaps. I.E. Ship has 4 weapon slots, Player removes weapon from slot 1 to replace with upgraded version in same slot. (Initial research points to this being impossible with vector)
2. Loop through all weapons for cycled fire, paired fire, and full fire modes. (cycled would be each weapon firing independently, paired would be two weapons firing at once, then the other two on the next shot, full would be all weapons. [using the x-wing as an example])


If these are possible with a vector, examples of how to do it properly would be greatly appreciated. Thanks.
18 Jan, 2011, oenone wrote in the 7th comment:
Votes: 0
Kayle said:
1. Remove/Replace elements without shifting others to fill gaps. I.E. Ship has 4 weapon slots, Player removes weapon from slot 1 to replace with upgraded version in same slot. (Initial research points to this being impossible with vector)


Maybe in that case, a std::map<slot_id_t,Weapon*> would be better… (slot_id_t could be as simple as int).
get(slot_id_t slot) would return the weapon for the slot, set(slot_id_t slot, Weapon* w) would set the weapon for the slot (replacing if other there, or erroring if slot already full). del(slot_id_t slot) could remove the weapon… the std::map could be hidden this way, and you could add checks for valid slot ids, like maximum, etc..
18 Jan, 2011, Runter wrote in the 8th comment:
Votes: 0
Quote
2. Loop through all weapons for cycled fire, paired fire, and full fire modes. (cycled would be each weapon firing independently, paired would be two weapons firing at once, then the other two on the next shot, full would be all weapons. [using the x-wing as an example])



A solution might be a list of lists containing groups of weapons with state that determines in what modes the group qualifies for. In the xwing example you'd have just two groups with 2 weapons in each. Each group qualifying for all modes of fire. For single fire mode you'd cycle each time you choose a weapon to fire from a group. This might facilitate exotic or player developed weapon fire systems.
18 Jan, 2011, JohnnyStarr wrote in the 9th comment:
Votes: 0
The downside to std::vector is using the array access operator [] isn't forgiving if you make mistake.
I don't see any downside in using std::list since you wouldn't need random access capability on what, 10 elements tops?
I understand wanting to rely on the STL to perform algorithms / low level operations for you, but this seems like a simple for(;;) loop to me.
18 Jan, 2011, JohnnyStarr wrote in the 10th comment:
Votes: 0
This an obvious implementation, but I don't really see anything wrong with it either.

class Ship {
public:
void FireWeapons(int t);

private:
std::list<Weapon*> m_weaons;
};

void Ship::FireWeapons(int t) {
std::list<Weapon*>::iterator w;
for (w = m_weapons.begin(); w != m_weapons.end(); w++)
if ((*w)->type == t) (*w)->Fire();
// add anything you can think of
}

// Somewhere

Ship* sh = new Ship("Millenium Falcon");
sh->FireWeapons(WT_STD);
sh->FireWeapons(WT_ULTRA);
sh->FireWeapons(WT_HANS_SECRET_SUPER_BEAM);
19 Jan, 2011, quixadhal wrote in the 11th comment:
Votes: 0
That works as long as you always group weapons by type. If you wanted to assign commands to fire individual weapons, or maybe group by mounting area (starboard, aft, etc), I'd use a std::map. Why? Individual weapons can be accessed by specific key ("aft0", "aft1"). You could also make weapon groups where the value is an array (std::vector) of keys to the mapping.

So, weapon_group["starboard"] might map to { "star0", "star1", "star2" } on a medium sized ship, or 0..20 on a larger ship. weapon_group["blaster"] would map to an array of all blasters, etc.

You can do it with lists too, of course, but looping through several lists to nail the few weapons you want seems inefficient. Using mappings, you could narrow your focus a few groups at a time. You want to fire all blasters on the port side? start with weapon_group["port"], then see which of those are keys in weapon_group["blaster"], and then go call weapon[k].fire() or whatever.
21 Jan, 2011, David Haley wrote in the 12th comment:
Votes: 0
Whatever the implementation, any of this looping or accessing should be done with private helper functions anyhow. Whether or not you use a list, map, etc. depends on the nature of operations you want to perform. If individual weapons have no meaningful identity, a list is fine. If individual weapons have an id and are referred to in particular, e.g. in a ship status report, you might consider mapping from id to weapon as some have suggested.

It's not just a question of efficiency; it's a mistake to think only in those terms. It's a question of easily expressing what you want to do. If you want to look up a particular weapon and you have to loop over a bunch of stuff to find it, that's an extra cognitive step that just gets in your way. By contrast a map gives you the natural operations that you want. (Of course, helper functions reduce the extra operational overhead.)

Basically my advice here is to think about what kind of operations you want to do on parts, not in terms of "I want to fire ze missiles after ze nap" but in terms of "I need to access them in order", "I need to access them by name/type/something-else", "I need to <…>".
22 Jan, 2011, Kayle wrote in the 13th comment:
Votes: 0
I'm definitely going to need to access them by name, order, and possibly type. I also need to be able to preserve the order even if one is removed. As the example earlier, Ship has 4 weapons slots, All 4 slots are occupied, Owner salvages a weapon that's more powerful then what he currently has and decides to swap on of them out, so he pulls off the weapon currently in Slot 1, leaving slot 1 open, and 2-4 still occupied, he then installs the new weapon into slot 1 preserving the initial order while upgrading one installed weapon.
22 Jan, 2011, drifton wrote in the 14th comment:
Votes: 0
here is my take on the code to use

this depends on how object orient you wish to be

//create custom container
class cWeaponContainer
{
public:
int DefineNumberOfWeaponSlots(int NumberOfSlots)
{
cWeaponSlot * TempWeaponSlots;
if(NumberOfSlots < m_NumberOfSlots)
{
//put out of bound slot items in inventory
}
TempWeaponSlots = new cWeaponSlot[NumberOfSlots]
//handle the differences between the two list

delete [] m_WeaponSlots;
m_WeaponSlots = TempWeaponSlots;
m_NumberOfSlots = NumberOfSlots;
}
m_WeaponSlot * GetWeaponSlot(int Slot)
{
if(Slot >= 0 && Slot <= m_NumberOfSlots)
{
if(m_WeaponSlots != 0)
{
return m_WeaponSlot[Slot];
}
}
return 0; //null
}
int SetWeaponSlot(int Slot, cWeaponSlot * WeaponSlot)
{
if(Slot >= 0 && Slot <= m_NumberOfSlots)
{
if(m_WeaponSlots != 0)
{
m_WeaponSlot[Slot] = WeaponSlot;
return 1;
}
}
return 0;
}
int RemWeapon(int Slot)// ect…

private:
int m_NumberOfSlots;
cWeaponSlot * m_WeaponSlots[];
}


so using an array of pointers you could set the none used slots to null/0 and test against that for empty slots
fill them as needed just make sure you use proper memory management so slot[0] could be could be ionblaster1 slot[1] = null // empty and slot[2] and slot[3] = high power lasers.

most of this is just container management for the weapons and a lot of boring code, you have a good idea of how to manage this

you would be able to overload the getSlot function to accept string and iterate over each slot with a for loop, and compare them
you could also use a modified version of what ever method you used to copy the oldslot array to the new slot array to create a return list weapons that match your search query then preform an action with that list
22 Jan, 2011, David Haley wrote in the 15th comment:
Votes: 0
Don't reimplement container classes. If you like, provide a more user-friendly interface that wraps the STL interface, but you really don't want to be writing your own vector search functions, etc.
22 Jan, 2011, Runter wrote in the 16th comment:
Votes: 0
Yes.. Perhaps the most compelling initial reason to use C++ is the STL. :\
22 Jan, 2011, drifton wrote in the 17th comment:
Votes: 0
:redface:

sorry i keep wanting to reenvent the wheel,

he may still want to use a wrapper class built over a vector or linked list to provide the functionality of what he is looking for, even a map can be used, but if he wants to pull different types of data using different fields of data IE Location, Type , Damager per second, ID, some kind of custom search function is going to need to be written to access that data

for game purpose he could create an empty weapon slot object that is just a place holder with in a vector, then he could use the index operator of the vector the test what is in slot n, as long as he verifies the range first
23 Jan, 2011, quixadhal wrote in the 18th comment:
Votes: 0
I'd use a std::map with the keys being your weapon slot numbers then, and have additional std::maps for your other keys that point to the same structures. That way, adding a new sort key is as simple as adding a new map.

You might also be able to use a multimap if they allow multiple keys to map to the same values, but I'm not sure they do. You'd also then have to ensure your keys don't overlap, so perhaps regular maps are cleaner anyways.
23 Jan, 2011, David Haley wrote in the 19th comment:
Votes: 0
Yes, you might need different search functions. But except in very rare circumstances, you should not be implementing the array management yourself, for example.

Or, you could just have several maps, as Quix suggested, one per lookup type.

Multimaps are containers that map one key to any number of values. Naturally, you can choose to map different keys to the same value if you so desire, just as you can with normal maps.
23 Jan, 2011, Cratylus wrote in the 20th comment:
Votes: 0
MB Admins: This thread crashes my Android's web browser.
0.0/57