Advertisement

How to program cards in a TCG/CCG?

Started by March 09, 2022 02:32 PM
9 comments, last by Nyx13 2 years, 9 months ago

Hi everyone,

I started developing a card game some time ago, and I am starting to test it with physical assets, but it probably wont work the best since there is quite a bit to keep track off.

My doubt is how would I program the cards? I am aware of some options, like using a class for each card type and have cards be stored in some type of persistence volume and load them, but some behaviors might be to complex to describe within a database(?). Or using a scripting language like Lua where cards are programmed with (this comes from the ideia that having some type of JSON that could store the card information and behavior, while also being fast and easy to edit would be good).

I have made some research, but the closest I have found to my question is 8 years old.

Some advice or links to articles or forums touching the topic would be appreciated.

class Card
{
	int attack{0};
	int health_recovery{0};
	std::string name{};
public:
	void load_from_database{std::string file_name);
};
Advertisement

Correction:

class Card
{
	int attack{0};
	int health_recovery{0};
	std::string name{};
	std::string type{};
public:
	void load_from_database{std::string file_name);
};

@Acosix Ok, I did not make it clear. What I am looking for is not actual code implementations/snippets, but things along the lines of architectures, designs and ways to organize the code/information

I just gave an example.

Nyx13 said:
What I am looking for is not actual code implementations/snippets, but things along the lines of architectures, designs and ways to organize the code/information

You gave the designs yourself in the post.

For a single developer, building arrays of cards and functions is probably the easiest option. A single large file with all the card definitions, consisting of card IDs, names, and arrays containing functions and parameters for each ability. By far the easiest is a file with a single large array containing all the IDs, strings, functions, parameters, etc., as a single chunk of data.

For more developers, building the abilities out of data will be necessary despite the added complexity. As the project gets bigger you'll be unable to do it in code as artists, designers, and producers will need to make changes without rebuilding the game. Live updates will grow out of necessity, both for development time and updating the game in production.

Advertisement

I would absolutely build abilities as attachable components to the base “card” container.

Each ability can have its own configuration (which may be JSON, or a database row from a table specific to that ability kind, or whatever.)

Decks and hands are vectors of pointers to card object, and card object has a container of abilities/properties. When some kind of event happens ("enemy targets card" or “UI needs to know which abilities are usable” or whatever,) iterate through each ability for each card in the intersting set (hand, board, whatever) and collect the appropriate data.

Whether you store each card type as a JSON or XML or YAML blob, with sub-fields for each ability/property, or whether you normalize that out to a set of database tables, is entirely up to you. There's something to be said for one data file per card type ("card name") in some hierarchical format, and going from there. No database needed for that, really. The database can then store which cards a player owns, but that just needs tuples of (playerid, cardid, count) where cardid maps to the appropriate config file for the card.

Card art, flavor text, costs, benefits, activatable abilities, and all the rest, could all be components added to the card, that respond to different events. “Render” could be an event, for example, and any ability that is active might render a little particle system, whereas the base card art component would just draw its picture and flavor text, and the “available points” property would draw its in-memory value.

Note: in-memory values can change, on-disk values for card types do not change. If you need cards that “remember” gains/losses, you'll probably want a separate table for “playerid, cardid, cardinstanceid, property-values”

enum Bool { True, False, FileNotFound };

frob said:
You gave the designs yourself in the post.

@frob i was wondering if there were any other (better) options, but thank you very much.

@hplus0603 Thank you very much! This helps a lot!

Nyx13 said:
i was wondering if there were any other (better) options, but thank you very much

The cards themselves are little more than collections and arrays.

For example, the cards have a name, a description, some ability text, some artwork, a type of card like permanent or spell, all of these come in standard forms for your game as a simple array of structures.

Cards have a cost, so you have an array of cost functions to play the card. Abilities may have a cost, and they use the same cost functions. It may be “pay 4 mana”, it may be “tap this card”, it may be “sacrifice a card you control”, it may have multiple costs. That's an array.

Cards have zero or more abilities, and each ability has an array of actions. Each of those abilities is an array of composition. Costs and targeting need to be part of those arrays and collections, too.

For for example, a card ability array for this card might conceptually look like this:

[ [ Cost: [ Tap this card; Exile: self, target land; ]
    Ability: [ Add mana of any color ]
  ]
  [ Cost: [ Pay Mana: black; Tap this card; Exile: target any player's graveyard, target any instant or sorcery ]
    Ability: [ Change life: all opponents, -2 ]
  ]
  [ Cost: [ Pay mana: green; Tap this card; Exile: target any player's graveyard, target any creature card ]
    Ability: [ Change life: self, 2 ]
  ]
]

Or maybe an ability array for this card:

[ [ Cost: [ ]
    Ability: [ Deal damage: any target, 5; Draw cards: self, 5; Change armor: self, 5; Summon creature: self, card 731 ]
  ]
]

Or maybe an ability array for this card:

[ [ Cost: [ Triggered ability: Object destroyed: any ally, landmark ]
    Ability: [ Change Power+Health: this card, 1, 1 ]
  ]
]

Exactly how you implement that array would be up to you, but it could easily be a pattern of tokens you build and parse. It could be a mapping of functions to numbers, it could be function pointers, it could be text strings.

The detail about the cards can live in one array in memory, and really won't be that big. If you're storing just IDs you're looking at perhaps a few kilobytes even for hundreds of card definitions. Cards in play only need a reference to the card definition.

The questions about where card data lives, meaning if you keep them in a database, if you keep them in data files, if you keep them static in code, that's almost irrelevant to the card game. A statically compiled array is super easy for a single programmer as I wrote, since you can build up a single file that is easy to use with simple arrays for all the information about every card. Keeping them in data files means leading it every time, but it still boils out to the same arrays of data. A deck system like we used on Hearthstone will query the server for the definitions for the cards in use, but they're still fundamentally the same data arrays. You could build a huge architecture about real-time updates of card definitions held around the world, but it would still ultimately boil down to arrays of attributes like above.

And the cards themselves, they're a reference to the card definition, plus an array of whatever modified local things have adjusted it.

It is easy to over-think this kind of thing. It takes time to build up all the various cost functions, the targeting functions, the ability functions, but once you have enough of them, composing those into interesting cards is straightforward.

frob said:
It is easy to over-think this kind of thing

This might end up being my mistake, thank you very much

This topic is closed to new replies.

Advertisement