One more point I wish to make. We've talked about the low-level details, but we also need to consider the proper top-down approach. This is based on a few year of experience developing games, and inheriting projects from other people developing games.MVC
is a good thing. Even if you're just running a simulation without a view, it's good to separate the model and the controller.
Your model contains values like a current match, an array of players and their positions, a referee, a ball etc, each with a reasonably abstracted bunch of setters and getters to change those values.
Your controller is a set of atomic actions or events (like "Match starts" or "Player A tackles Player B") that transform the model from one valid state into another.
(If that sound familiar to you, then you've heard of ACID
, where a database transaction transforms the database from one consistent state into another. Unsurprisingly, implementing the controller as a set of atomic actions works equally well if your model lives in a database.)
If you stick to that architecture, then your Player class is just the model. And the controller lives somewhere else, and it has a function (or method or class) with a signature like ActionTackle(Match &match, Player &tackler, Player &tacklee), and that function does everything related to the action - do all precondition checks, update both players, figure out if the tackle was legal at that point in the game and if the ref saw anything, possibly handle penalties, injuries, figure out if other players or the audience start a riot.. it can get complicated.
Now, within ActionTackle, how do you update the players in response to the tackle?
- Do you update the players directly, using reasonable abstractions like setProneState(), sustainInjury(), receivePenalty(), ...?
- Do you call tackler.tackle(&tacklee) and have it update both player objects?
- Do you call tackler.tackle(&tacklee, &referee) and tacklee.beTackled(&tackler, &referee), letting the model determine the chance of penalty and injury?
- Do you call tackler.tackle(bool penalty) and tacklee.beTackled(bool injured), determining the outcomes in the controller, but letting the model handle it?
- Do you abstract everything away, implementing Player::canTackle(), Player::canBeTackled(), Player::prepareToTackle(), Player::prepareToBeTackled(), Player::tackle(bool), Player::beTackled(bool), Player::postTackleCleanup(), Player::postBeTackledCleanup()?
So the point I'm going to make is this: the model has no business knowing what a "tackle" is, because that's an action, and that's the controller's job. It certainly has no business determining the chance of penalties or injuries. In the list above, I've always gone for number 1.
As a rule of thumb, if your model has a method that's only called from a single place in your code, that's always suspicious. Is it too specific? Can it be split into reusable parts? Can it be generalized to be more useful? Should it be inlined into the controller?
As another rule of thumb, if a model object's method receives another model object as a parameter, that's also suspicious. If there's an interaction between both objects, let the controller figure out the results of that interaction, and pass the results.
As usual, exceptions apply.