3
\$\begingroup\$

Turn based games sometimes have different win conditions for different maps and mission.

For example, control some structure for X turns, last X turns without losing a single soldier.

They usually also have standard win conditions across all maps like eliminate all enemy units and structures, etc.

How would I implement map/mission specific win condition checks?

My current implementation utilizes a finite state machine that loads the play state. The play state has objects and logic for gameplay. I could write custom play states for each mission, but I feel like that is not the best way to go as I would have large portions of duplicate logic and code.

Ideally I would like to tie in win conditions into my map/mission file data. When I load a map, the win conditions are loaded also, but am not sure how I would achieve that.

Any suggestions?

\$\endgroup\$
  • 1
    \$\begingroup\$ It's quite unclear what you're asking. Are you asking how to implement the conditions? Or is it where to put data for the conditions? Or something else? \$\endgroup\$ – Vaillancourt Mar 21 '15 at 18:21
  • \$\begingroup\$ I'm asking about the design overall. I want to have the option to include map specific win conditions along with standard win conditions and am asking the best way to do this with a state based architecture \$\endgroup\$ – MrJman006 Mar 22 '15 at 18:44
  • \$\begingroup\$ I am also open to other architectures that would support varying win conditions in my game play \$\endgroup\$ – MrJman006 Mar 22 '15 at 18:45
  • \$\begingroup\$ I edited the question to hopefully make it more clear. \$\endgroup\$ – MrJman006 Mar 22 '15 at 18:51
1
\$\begingroup\$

This is very similar to how one might handle Achievements, or Challenges, or any other situation needing you to keep track of a series of events and counters.

Simply coding the rules into a scripting language does work. You would need to be sure that all events related to conditions you care about are exposed to the language. You might also need to allow scripts to register conditions and current values with an external UI system, of course.

A more structured version of this would be to just keep a list of conditions, current state, and so on. In many ways, you'd just be building a limited and very domain-specific scripting system; the advantages of this approach are that any other interested system (the UI, your map editor, debug console, etc.) can trivially integrate with this system as there is a well-defined structure to how it works.

If you're trying to throw something together that works with the minimal pain and effort, I'd probably go with the first approach (scripting). If you plan to put in the time to make a high-quality map editor for user-created content or want to develop very high-quality debugging tools (which map designers love, even if they're your in-house designers), I would go with the second option.

The second option can be coded up using either C-like arrays of structs/unions with enumerations that define requirements for conditions, e.g.:

enum condition_t { controlled_houses, killed_enemies };
struct condition_defition_t {
  condition_t type;
  int data; // number of houses required, or number of killed enemies required, etc.
};

condition_definition_t* conditions; // per-map; load this data from your map file

int evaluate_for_player(condition_definition_t* condition, size_t num_conditions, player_t* player) {
  for (size_t i = 0; i != num_conditions; ++condition) {
    switch (condition->type) {
    case controlled_houses:
      if (player->houses < condition->data)
        return 0; // does not have enough houses to win
      break;
    case killed_enemies:
      if (player->kills < condition->data)
        return 0; // did not kill enough enemies
      break;
   }
   return 1; // all conditions have passed
}

With a little extra work, you can also support OR and AND conditions, ranges, or whatever other complexity you need for your game design. It's often better to avoid state in the condition itself; e.g., if the condition is "controlled castle for 5 turns" then just keep track of how long a player has controlled the castle in the player, and the condition simply checks that value. As your conditions grow in complexity, you may need to add some kind of per-player condition_state_t, though.

A polymorphic approach to the above would just replace the enum and the big switch statement with an abstract base class and derived classes that implement specific rules, e.g.

struct ICondition {
  virtual bool IsSatisfied(Player const& player) = 0;
};

class KilledEnemiesCondition : public ICondition {
  int _requiredKills;

public;
  KilledEnemiesCondition(int num_required) : _requiredKills(num_required) {}

  bool IsSatisfied(Player const& player) override {
    return player.GetNumKilledEnemies() >= _requiredKills;
  }
};

class OrCondition : public ICondition {
  std::vector<std::unique_ptr<ICondition>> _conditions;

public:
  bool IsSatisfied(Player const& player) override {
    return std::find_if(_conditions.begin(), _conditions.end(), std::mem_fun(&ICondition::IsSatisfied)) != _conditions.end();
  }
};

And the loop would use that interface. As shown, this approach does make it a bit easier to add more complex conditions like an OR condition. Loading the conditions from file would likely need extra work to deserialize the definitions into the proper classes with the proper data, of course, and you'll probably find the polymorphic version to just be bigger and more complex; that complexity may or may not be worth it to you, depending on various factors (e.g., required complexity of conditions, the API you want for exposing these to other systems, whether you already have a good serialization system in place, and your comfort level with that programming paradigm).

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ Hey Sean, This is great. Almost exactly what I was looking for. In your opinion, is scripting a good thing to embrace for high quality projects (for games mainly). I know some big games utilize scripting, but you can achieve the same results without it and maybe even gain a performance boost right? \$\endgroup\$ – MrJman006 Mar 23 '15 at 6:27
  • \$\begingroup\$ @MrJman006: that question is an entirely different topic. Almost every modern game engine has scripting, but the degree a game will rely on it varies wildly across products. Performance will vary just as much; there's a huge difference between an engine that sparingly uses statically-typed scripts that are precompiled into native code for final consumer builds and an engine that heavily relies on embedded stock Python using a horribly bloated multi-layered binding API (said binding APIs tend to be where most perf loss in scripting comes from, in my experience). \$\endgroup\$ – Sean Middleditch Mar 23 '15 at 6:43
  • \$\begingroup\$ I see. I will have to look into that a little more then. Thanks again for your input. \$\endgroup\$ – MrJman006 Mar 23 '15 at 14:22
1
\$\begingroup\$

We used hardcoded conditions with some variables in early days. That was working good enough, but quite cumbersome. There were standard objects (houses, army, citizens, time) and deeds (kill, preserve) connected with win/defeat goals. So for example we could have:

  • kill houses of player 5 to win
  • kill army of player 5 to win
  • preserve citizens of player 4 to win
  • preserve own houses to win
  • reach time 6000 to loose

As you can see thats somewhat flexible and easy to rig in code.

Later on we decided to support modding and for that we have incorporated scripting language support (Pascal Script in our case, but you can use Lua or something else just as well). Scripting language has handlers for game events (OnTick, OnHousePlaced, OnUnitKilled, etc. ~60 of them) and game state queries (IsHouseAtXY, IsUnitWounded, IsAlliance, ~50) and game handlers (GiveHouse, UnitKill, ResourceGive, etc ~60). That all combines into very rich scrpted maps with complex scripts, some even making an RPG from our RTS game :)

| improve this answer | |
\$\endgroup\$
-1
\$\begingroup\$

i'll be a bit abstract. Why don't you use a Observer pattern

After each turn you call NotifyObservers that invoke the Notify method inside each observer. Every observer is a win/defeat condition.

At the start of a stage, you "register" one or more observers.

| improve this answer | |
\$\endgroup\$
  • \$\begingroup\$ I don't know why your answer got down voted, may it should have been a comment? Either way, the observer pattern is something I will definitely look into. Thank you for this. \$\endgroup\$ – MrJman006 Mar 23 '15 at 6:21

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.