1
\$\begingroup\$

I'm currently trying a develop a small game similar to http://www.estiah.com/.

Basically, there are 2 teams of 1 or more actors each, everyone with their same deck. Turn order is fixed and each card is automatically played when drawn, so games play out automatically. I already got the basic game loop, game state and history and turn order and currently try to create a fluent API, that makes it easier to add cards later on.

Each card consists of multiple effects, that can either target the current user, a random ally/enemy, a specific ally/enemy based on some criteria (like, enemy with lowest hp) or all allies/enemies/everyone. On top of this, effects may also have some kind of setup/cleanup, that should only be executed once before/after all targets have been processed.

Below are the use cases i can currently think of:

  • apply 1 effect to self
  • apply 1 effect to a random ally/enemy
  • apply 1 effect to a ally/enemy matching criteria
  • apply 1 effect to all allies/enemies
  • apply 1 effect to everyone
  • apply multiple effects to either of the above (targets should not change between effects, even if, for example, the "enemy with lowest hp" is different after the first effect)
  • execute effects based on a condition on the user
  • execute effects based on a condition for each target

Conditions/targets should be resolved during effect execution, since previous effects might influence them (unless multiple effects are joined and assigned to the same target, in which case it should not change between effects, as stated above).

Since the theory probably isn't very expressive, i already developed a basic system, which can be found over here: https://codereview.stackexchange.com/questions/189732/designing-a-system-to-apply-multiple-effects-to-one-or-multiple-targets.

The current system allows me to do things like:

public class MyCard implements Card {
    public Effect getEffect() {
        dealPhysicalDamage(10).to(randomEnemy());
    }
}

Where dealPhysicalDamage(...) returns an instance of TargetedEffect, randomEnemy() returns an instance of TargetSingle and the to(...) method binds the target to the effect, converting it into a plan Effect, which in turn can get executed by the game engine. This modifies the current game state, which gets deep cloned into a history for later usage as a battle log.

I've been trying for days to come up with a better system that gives me the freedom to apply 1-n effects to 1-n targets (single target effects will be the majority), while keeping the API as compact as possible. I'm probably too deep in the code to get a clear view, so maybe someone else can come up with an idea.

\$\endgroup\$
  • \$\begingroup\$ Whats stopping your for making To( TargetInstance[] ) which would process all targets and returns one Effect for each target, or even a composite Effect? \$\endgroup\$ – Leo Mar 20 '18 at 11:30
1
\$\begingroup\$

You probably are too deep in the code. This is not a particularly complex system when you consider the ordering, or the processing, from what I can see.

I've been trying for days to come up with a better system that gives me the freedom to apply 1-n effects to 1-n targets

OK, assuming that's your main concern...

public class Game
{
    public ArrayList<Card> cardsInPlay;

    public ArrayList<Card> GetCardsAffectedBy(Effect effect)
    {
        ArrayList<Card> results = new ArrayList<Card>();
        //run some sort of a filter here to know what is affected by what
        //this will be based on your cards'/effects' varying logic
        //and what else is in play, and by whom i.e. which player
        for (Card card : cardsInPlay)
        {
            if (card is pertinent to this effect) //your logic here!
                results.Add(card);
        }
        return results;
    }
}

public class Card
{
    public Game game; //set from Game itself on Card creation

    public void applyEffects
    {
        for (Effect effect : played.effects)
        {
            ArrayList<Card> cardsAffected = game.GetCardsAffectedBy(effect);
            for (Card card : cardsAffected)
            {
                effect.applyTo(card)
            }
            //or apply to players, enemies, allies, whatever.
        }
    }
}
Game.playCard(card); //or whatever, but call card.applyEffects();

Pass in whatever extra paramaters to whichever functions you like, e.g. GetCardsAffectedBy might need ID of player who just played that effect. We might need to keep data in Game itself till the turn resolves, e.g. a list of the effects played so far so we can evaluate order... and so on.

As for...

(single target effects will be the majority)

...Don't concern yourself with that. Loops accommodate one or many, that's all you need to know. Act as though there are always more than one, and the one-card case will be handled by-the-by.

Reporting

While it's important to build a game with logging / reporting in mind, I think you need to stop allowing that aspect from limiting your thinking in regards to making the game work relatively efficiently. Remember, it's not a game if you can't play it, and it's not an efficient, maintainable codebase if you can't tweak it to make things elegant. Really, logging comes as a secondary concern to these aspects, in my opinion at least. In my case I tend to do data driven design with a hierarchical data model and control hierarchy, and I expose all members as public so that any reporting tools can access these down the line (not a fan of shadowing every member with a get/set pair).

Adding cards

Having certain supported gameplay effects, there is plenty you can add by just changing names and numbers in accordance with whatever that component can represent. However, when it comes to new logic, obviously you have to write the code that supports that. How flexible you make things is entirely up to you. You may choose to keep the cards / effects minimally parameterised.

More specifically though, it's difficult to help you with API design while we have no idea how you envision creating new cards. Will you be using an editor or do this in text files by hand? Will you allow card proposals by players, is there an acceptance/rejection process? Will there be a facility for image, sound effect upload? etc. etc. Once again, it is better to focus on getting the the game playable first, and worry about these petty details a bit later.

\$\endgroup\$
  • \$\begingroup\$ I ended up skipping the innermost for-loop and pass all targets to the effect, since some cards also have to do setup/cleanup that only should be done once independent from the number of targets. For convenience i created a subinterface of Effect, that basically contains the loop and allows to write simple, repeatable effects. \$\endgroup\$ – Iavra Mar 24 '18 at 21:20

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.