8
\$\begingroup\$

I've been thinking this over for days and I'm still not sure what to do. I'm trying to refactor a combat system in PHP (...sorry.) Here's what exists so far:

  • There are two (so far) types of entities that can participate in combat. Let's just call them players and NPCs. Their data is already written pretty well.
  • When involved in combat, these entities are wrapped with another object in the DB called a Combatant, which gives them information about the particular fight. They can be involved in multiple combats at once.
  • I'm trying to write the logic engine for combat by having combatants injected into it.
  • I want to be able to mock everything for testing.

In order to separate logic and data, I want to have two interfaces / base classes, one being ICombatantData and the other ICombatantLogic. The two implementers of data will be one for the real objects stored in the database, and the other for my mock objects.

I'm now running into uncertainties with designing the logic side of things. I can have one implementer for each of players and NPCs, but then I have an issue. A combatant needs to be able to return the entity that it wraps. Should this getter method be part of logic or data? I feel strongly that it should be in data, because the logic part is used for executing combat, and won't be available if someone is just looking up information about an upcoming fight. But the data classes only separate mock from DB, not player from NPC. If I try having two child classes of the DB data implementer, one for each entity type, then how do I architect that while keeping my mocks in the loop? Do I need some third interface like IEntityProvider that I inject into the data classes?

Also with some of the ideas I've been considering, I feel like I'll have to put checks in place to make sure you don't mismatch things, like making the logic for an NPC accidentally wrap the data for a player. Does that make any sense? Is that a situation that would even be possible if the architecture is correct, or would the right design prohibit that completely so I don't need to check for it?

If someone could help me just layout a class diagram or something for this it would help me a lot. Thanks.

edit

Also useful to note, the mock data class doesn't really need the Entity, since I'll just be specifying all the parameters like combat stats directly instead. So maybe that will affect the correct design.

\$\endgroup\$
1
  • \$\begingroup\$ My guess is Combatant.entity is not used during combat and therefore should not exist. Perhaps you need another class like EntityVsEntityCombat which wraps combat logic, contains Entity <--> Combatant mappings and updates Entity states after combat has finished? Maybe some more info on your current architecture could help. \$\endgroup\$
    – Torious
    Commented Nov 3, 2012 at 22:50

1 Answer 1

1
\$\begingroup\$

Part of the way I've approached this in the past is, rather than having completely separate representations for players and NPCs while requiring that they both implement a common interface, driving toward convergence of representation between them to the greatest extent that I can, as by subclassing them from a common Character model into which I push as much about them as makes any sense to generalize. This helps avoid issues with running NPC operations on players and such by making operations more generally applicable, since there's less natural tendency for the representations to diverge than if they're completely independent implementations. Basic leveraging of polymorphism helps handle the cases that have to diverge (for instance, if you made your CombatantLogic responsible for handling what happens when somebody dies, you would have to do typechecking to make sure you used the right logic; so don't, have players and NPCs implement separate, appropriate die() methods).

I agree that your Entity is part of data. However, building on what I was saying, I would actually tend toward removing or limiting the role of your CombatantData in favor of having the combat logic draw values directly from the Entity, perhaps with CombatantData only storing expensive, single-combat-specific calculated values. Your test mockups would then be oriented more toward providing fake Entity models than toward populating CombatantData. (Having CombatantData duplicate a lot of information from the Entity bothers me in much the same way that a denormalized database does. However, if you believe in the Law of Demeter, which I passionately do not, you won't want to do things the way I'm suggesting. Of course, if you believe in the Law of Demeter, I'm not sure your CombatantData should even provide access to the Entity.)

\$\endgroup\$
2
  • \$\begingroup\$ CombatantData doesn't really duplicate entity data, it includes state of the entity in combat, like current health and any status effects on it. \$\endgroup\$
    – Tesserex
    Commented Jul 6, 2012 at 18:37
  • \$\begingroup\$ @Tesserex: Ah, okay. If that state doesn't persist between combats, then that's sensible. Disregard that part, then. :) Does the rest of what I'm saying make sense? \$\endgroup\$
    – chaos
    Commented Jul 6, 2012 at 19:19

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .