问题
I am trying to write some futuristic card game simulator. The game itself has complicated rules and a lot of cards (~1000 total). There are few types of card (<10, types like spells, healing, moving, attacking, defending etc..). Every card belongs to exacly one type but the actions they do can be various, ex: Card1 belongs to type Healing, its action is to heal 1 Health point, Card2 also belong to type Healing, but its action is heal and ex. drop another card.
Another thing is that the card can do some hard complex action which is conditional based long chain of operations to do (optional or forced), which is in example: draw a card, if its in X color, hold it, if its not drop it and recive Y mana points and if you want, also a blessing
The problem is how to design the structure of classes to make card types and define action for every single card in game and them store them all in some array for later using them in game ?
The only thing that comes in my mind is some type of inheritance like:
class CardBase
{
public:
virtual bool use(int action) = 0; // Every card should implement this
private:
std::string _name;
// type ?
}
// Split into types maybe ?
class CardOne: public CardBase
{
bool use(int action) { /*do whatever card should do on given action ID ex. heal*/ }
}
class CardTwo: public CardBase
...
// 1000 cards later... is this still a good idea... ?
And later storing and using like:
std::vector<CardBase*> cards; // build card deck
if (cards.at(0)->use())
std::cout << "Card used !";
else
std::cout << "You cannot do that !";
I have also though of masive switch case but its disgusting just thinking about it...
回答1:
If you create a nested interface withing the card, you can fill it with completly different acting classes dirived from that interface.
Write an algorithm to add a card of type when those lack the percentage you need of those. That way it won't matter if you have 52 or 123456789.0f
cards
Add a static int within the IType decendants to add 1 each time you add a card. That will save so much processing time when you get to a larger number of cards.
You could consider to change:
virtual bool CSpell::use( int action )
to
virtual bool CSpell::use( void* action )
That way you can pass in everything you new specifically what you need per type. Not only integers can be passed but entire structs and classes too.
e.g.:
class IType { public: virtual bool use( int action ) = 0; };
class ISpell : public IType { }; class CSpell : public ISpell { public: bool use( int action ){ return false; } };
class IAbillity {};
class CAbillity : public IAbillity { };
class CSpecialAbillity : public IAbillity { };
class CCard
{
public:
enum{ Spell, Trap, Monster, Wizard };
CCard( std::string name, IType* type, bool gc = false ) : _name( name ), _type( type ) { }
~CCard(){ if( _type /* && _gc_responsible */ ) delete _type; }
virtual bool use( int action ){ return _type ? _type->use( action ) : false; }
private:
std::string _name;
// Whatever type you have: call use()
IType* _type = 0;
// An abillity espacially for a monster, or a wizard, can be set here
IAbillity* _abillity = 0;
};
class CDeck : std::vector<CCard*>
{
public:
int load( std::string filename ){}
int save( std::string filename ){}
int addCards( int amount )
{
// Make your algorithm
// Run it each time you want to add cards
// loop
// Check how many cards you have from each type
// the one that lacks the most (percentage wise), add it
}
private:
};
int main()
{
CDeck deck;
deck.addCards( 10 );
// play, eat, sleep, invite people
deck.addCards( 10 );
// play, eat, sleep, invite people
deck.addCards( 20 );
// cool, aparty, invite people
deck.addCards( 1000 );
}
回答2:
The similiar cards (eg. healing cards) could be represented by the same class. Then you can create the instances with the specific values.
class HealerCard: public CardBase
{
int amount;
HealerCard(int amount) {this->amount = amount;}
bool use(int action) { /*heal by this->amount*/ }
}
With the above class you can create different healer card instances:
//... Let's create 10 of each amount of healer cards from 1 to 10
int amount, i, cnt = 0;
for (amount=1; amount <= 10; amount++) {
cards[cnt++] = new HealerCard(amount);
}
Note: Syntax might be off a bit, my c++ is a bit rusty :)
回答3:
You could not hardcode anything, and dynamically execute your cards actions with something like lua (never used it, just heard of it). This has a few advantages:
- You don't have to recompile to change a card
- Less likely to exhibit bugs
- You can make new cards really easily
- You can load different card sets in from a file
- User can make their own cards easily
There are also a few disadvantages:
- It's way harder
- It will be a little slower
回答4:
You could design classes like you stated: a baseCard class, then 10 or so classes that extend that representing your multiple types. Then you could have a csv* file that contains information about each of the 1000 different cards following a format such as this: [type] , [value] , [number of instances] , [other attributes]. You can then do a read of the text file and initialize various instances of the necessary class with the given information by doing some basic parsing of the file. This way you can separate the design of the class from the instances. In fact, you could create a deck class that does all of this initialization then use a single instance of a deck class in your program.
Healer , 1 , 10, ? ,
Healer , 3 , 5 , ? ,
Healer , 5 , 2 , ? ,
Healer , 10 , 1 , ? ,
Spell , 1 , 20, ? ,
Spell , 3 , 10 , ? ,
Defense , 1 , 7, ? ,
Defense , 3 , 4, ? ,
...
*csv stands for comma separated values and is a basic text file that has values separated by commas.
来源:https://stackoverflow.com/questions/27771699/card-game-with-huge-amount-of-cards-and-complex-actions