Skip to main content
GameDev.net gamedev.net
🔒 Locked

Card game Class Structure

Started by Jaap85 Mar 17, 2015 at 7:38 PM 14 replies 12k views
Original Post
Jaap85
Jaap85

Hi everybody,

After a short pause i recently picked up programming for fun again. I am currently working on a collectible card game (like MtG but then with a somewhat smaller scope ;-).

The thing is that i am unsure how to create my class structure. Right now i have a class called Card and some subclasses for (for example, when staying in MtG terminology: an Instant).

However, when i am adding my cards, i have to use a lot of Switch statements. For example (still using instants from MtG):

Switch (card)

case Lightning Bolt:

dealDamage(3)

case Terror:

destroyCreature(creature)

Of course this is completely impossible to manage with a lot of cards. What would the class structure of MtG look like:

- Object

- Card : Object

- Sorcery/Instant/Creature : Card

- Terror, Lightning bolt, etc : Instant

So in the end you would get approximately 500+ classes. That doesn't feel right either.

What is the solution here?

Thank you very much in advance!

My personal blog on game development! Black Wolf Game Development
WozNZ
WozNZ

You might have 500 cards but how different are they in practice.

Say you have

Card1 inflict 5 damage on monster A Delayed 0 turns

Card2 inflict 6 damage on monster B Delayed 8 turns

They are actually exactly the same card with a few parameters to handle the differences.

Your first task should really be to group all your cards to find commonality, in OO examples you might see

Animal

Dog

Cat

....

The dog is the special code for all dogs, you don't need one for each breed

BeerNutts
BeerNutts

This smells like a good place for using Component Orient Programming. Build your objects using composition, not inheritance. Component Based Entity Systems are pretty popular now, but I think, for creating cards, this would be a good place for it.

Good luck.

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)
dworm
dworm

you sort of have to use delegates and event (talking c# dunno other languages how manage this stuff)and or a derivative from entity component imo suits this very well

this way each card has just a field (or a list of) which adds an event when the card is played/tapped or anything else

i would use a sort of EC system to split in even more simpler fuctions so you build each card just by adding simpler effects like "deal n damage when tapped" "draw 2 cards when killed" and so on

SeanMiddleditch
SeanMiddleditch
An ECS doesn't necessarily map well to cards, but a compositional approach certainly works very well. The key part is to make it data-driven, e.g. require a data file to define what an individual card does, not code. You DO NOT want to have to recompile your game every time you tweak how a card works!

An appropriate approach would be to define the individual Effects. E.g. DealDamageEffect, HealEffect, StunEffect, DismissEffect, etc.

A card is then just a collection of Abilities. An Ability defines one or more effects along with requirements, valid targets, whatever.

You can then combine this with a target system. A SelfTarget, an EnemyTarget, MultiTarget, FilteredTarget, etc.

Yet another piece might be a Dependency for using the card. ManaRequired, TurnRequirement, CreatureRequirement, and so on.

An individual card then is just a set of all these different components mixed in. Not necessarily like a component-based game object like you'd see in Unity or whatnot, but a class with an array of IRequirements, a separate array of ITargets, and whatever else you need.

Combine this would a tool to create cards using these components and you can have game designers pump out thousands of unique and interesting cards without every needing to change a single line of source code.

A card that says, "(Demon Zap) [tap] Deal 1 dark damage per red creature under your control to a target enemy creature." might then look something like (in a made-up terrible format):

Card:
  title = Demon Zap
  ability[0]:
    dependency[0]: Untapped
    dependency[1]: Comparison
      Count: Creatures(controller=self, color=red])
      GreaterThan: 0
    effect[0]: Tap
    effect[1]: Damage
      DamageAmount: Creatures(controller=self, color=red])
      DamageType: Dark
    target[0]: Creature
      TargetFilter: IsEnemyControlled(target)
Toss in data for art, vfx/sounds when abilities are used, and so on, and there you go.
Sean Middleditch – Game Systems Engineer – Join my team!
WozNZ
WozNZ

The others are right that composition > inheritance as it offers far more flexibility. While it involves more scaffolding it is normally worth it.

In some ways the JS version of objects actually makes sense over normal OO in that you have objects that support real fine grained composition in ways that normal OO does not due to the limits of the type constraints strict type systems bring, but that is a different subject lol,

That said, the more I move into functional the more I see the benefits of function composition over object composition. More reuse, easier to reason and easier to refactor

Still sounds like the OP is still at the stage of working out what is actually required, not getting that nailed down brings the typical start down a path and then realise your structure does not support some great new idea you have had and then you have a world of pain.

Ravyne
Ravyne




An ECS doesn't necessarily map well to cards, but a compositional approach certainly works very well.

This.

ECS is good when you have a lot of entities that all do a different subset of some known quantity of components/behaviors, and when one particular entity might vary over time, or be different from other entities in broadly the same category of entities.

Some of that is a good fit for a game like magic, but others are not. Data-driven composition gets you what you need, IMO, without quite all the overhead of a full-blown ECS.

throw table_exception("(? ???)? ? ???");
Jaap85
Jaap85

Thank you very much for the responses everybody! Since i am quite new to this topic (and to programming in general), what would be the correct terms to Google for an example or maybe even a tutorial in C# for a compositional approach?

My personal blog on game development! Black Wolf Game Development
BeerNutts
BeerNutts

Thank you very much for the responses everybody! Since i am quite new to this topic (and to programming in general), what would be the correct terms to Google for an example or maybe even a tutorial in C# for a compositional approach?

I think that's going to be the crux of the issue. Building objects using components, and properly operating on them isn't as easy to grasp as some other methodologies, and, since you're a beginner, it's going to complicate the issue.

I don't know C# very well, but if you google "c# entity component system" you're bound to find a bunch of information to help you along. Read as much as you can, and try to understand what's going on.

I believe it's going to be tough for you. You might want to start simple, and limit the number of "options" of your cards, and make a simple, but fun, card based game. Then, as you grow into game programming, you can build.

You're going to make mistakes, you're going to do things the wrong way, but you'll learn from it. Read, program, and repeat, you'll get there.

Good luck!

My Gamedev Journal: 2D Game Making, the Easy Way

---(Old Blog, still has good info): 2dGameMaking
-----
"No one ever posts on that message board; it's too crowded." - Yoga Berra (sorta)
Jaap85
Jaap85

great, thanks a lot! I will definitely give that a try :)

My personal blog on game development! Black Wolf Game Development
Xai
Xai

Here's a VERY short (partial) sample of some ideas ... I'm going to throw out some specifics as an example, the intent is that you can see a small example and then adapt it into something very different to meet your specific needs ... I'm just trying to show you a little about the "composition" thing. (and there are hundreds of ways this can be done, this is just a simple quick starting idea).

I'm going to use SOME pseudocode for parts below, representing things that would exist OUTSIDE the scope of that part I'm showing you ... you will need to adapt to your system of couse. The main 2 things I'm assuming are that there is some base "game object" (something interface or class like IGameObject, GameObject or ITargetable that all of your "affectable" objects inheirit from ... so they can be passed around the api ... this does NOT assume that that core interface defines all the properties at that level, but instead there can be further interfaces or derived types that add features ... ). I'm also not trying to dictate how you pass around your "context" (ie game state, world info etc ..) I'm just assuming you have some simple way to access such a thing outside the scope of this example.


public interface IEffect
{
  bool CanTarget(IGameObject target);
  void ApplyEffect(IGameObject target);
}
 
public class Card
{
  List<IEffect> Effects { get; }
  // lots of other unshown stuff likely here  too ...
}
 
public DamageEffect : IEffect
{
  public DamageType DamageType { get; set; } // an enum for Physical, Lightning, Arcane, whatever ... the use might involve damage modifiers, or just different graphical effects
  public float DamageAmount {get; set; }
 
  bool CanTarget(IGameObject target)
  {
    return (target.Owner != card.Owner);
  }
 
  void ApplyEffect(IGameObject target)
  {
    float realDamage = DamageAmount - target.DefenseRating(DamageType);
    target.ReduceHealth(realDamage);
  }
}

Once again, just a primative illustration of the idea ... not a working implementation ... tons more to add like target selection options (single enemy, self, enemy and self, group, all enemies, ...)

But if you can get a system working that deals with multiple effects being arbitraily combined with single enemy target support ... then you can add self target cards, group targets cards ... and finally the ability for a single card to have different targetting for different parts of it (its effects) ... such as "blast all enemies of a single type for X damage each, and heal 1 for each target affected" ... but start small and work your way up ...

Xai
Xai

The part I didn't say is that ... this is really just standard OO and polymorphism ... OO has always had factories and such ... people just think of it as "composition over inheiritance" because in 1 specific detail (the card's effects) you are composing a set of objects, instead of coding those compositions as a class ... but really this is just standard data driven OO ... no different than the way physics engines have worked since the dawn of time (A physical object is composed of a set of "parts" ... each combination is not coded manually as a separate class).

I think the reason people get confused is that OO classes all teach things wrong ... they talk about Animals with Dogs and Cats ... or Shapes with Square and Circle classes ... but that is a bad example, because it isn't based on what the computer is doing in a real world data-oriented way ... better examples would be something like a UI system that has Control, Button, TextBox ... where there is a very useful based behavior related to accepting input and knowing where you are on the screen ... but then the derived classes are actually different in their behavior ... and they have a valid logical reason to be aggregated ... I've never seen an "Animal" or "Shape" base class do anything useful ... not like a "Control" base class, which can support child control containment, etc. Another good example might be an "Image" that has derived classes for a Bitmap (or similar) and another for a VectorDrawing ... (which in turn has a list of shapes).

Acharis
Acharis

Card games definitely are data driven and less OO. Just create basic classes (land/creature/global enchantment/creature enchantment/artifact) and (sorcery/instant/permanent) (OOP might be quite useful here since). And once you have these forget about code and switch to data driven approach (you definitely don't want to make "Lightning ball" class).

Xai
Xai

Agree with Acharis 100% .... but once you have graphics you will probably be making a "Lightning Ball" visual effect (like 1 method, and an entry in an enum/switch statement somewhere) ... that your data driven card(s) will reference if they want to use it.

Xai
Xai

You can almost think of the whole design as one where you mainly use the "traditional" oo for the micro level concepts (effect types, visual effects, etc), but you use composition / data driven techniques heavily at the macro level (a specific card is mostly just a set of sets of those micro objects).

Jaap85
Jaap85

Thank you very much for the elaboration Xai, i will definitely keep these in mind when constructing my game. I now also see that this form of composition is quite related to an object oriented approach. Although i do have to improve my skills with interfaces and specific C# stuff like delegates, i get the global idea and should be able to get this working one way or the other.

Thanks again!

My personal blog on game development! Black Wolf Game Development

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.