Reworking cumbersome game logic code

Started by
9 comments, last by Kylotan 16 years, 2 months ago
I'm currently writing an American Football game in C#(mono) using OpenTK for graphics. I've come to a place where my system of handling game logic has become cumbersome and this is making it hard to track down a few pesky bugs. Here is how my program is structured: Render Window contains: -Field Scene(main game-play scene) contains: --Game Manager(manages the clock, score etc.) contains: ---Two Team classes each of which has: ----A collection of players each of which is responsible for its own logic. The problem comes in how the game manager handles the state of the game and passes that state to the teams and their players. Currently, the Game manager, each team and each player have a switch statement inside of their update functions which performs something different depending on the state of the game. This gets really tricky considering that there are 6 different possible game states and 15 or more variables to handle for each player. My question is, is there a more elegant way to handle this that doesn't use multiple long and hard to manage switch statements?
----Stevo
Advertisement
Well, the first thing I would do is simply move the state handling code out of the switch statements to separate methods. Then, even a 6-state system only has a 6-line switch statement, each line calling 1 method. And 15 variables isn't really that many.

Some people may suggest moving to the State design pattern, but I don't think that solves your problem, just moves it. It is helpful if you find that certain variables only apply in a certain state however, as it can reduce clutter in your main class. But I tend to find such gains short-lived as you often realise that even separate states often have far more in common than they have that is different.

Perhaps if you could elaborate on the kind of bugs you're running into, there could be some more specific recommendations.
From what you've said, it seems you're trying to encapsulate a little too much. Can you say a bit more about what the 3 classes do. Btw:

Quote:
Two Team classes


I'm assuming you mean ONE team class and TWO team class objects?


Quote:
Well, the first thing I would do is simply move the state handling code out of the switch statements to separate methods.

Just to add to that suggestion, if the update function doesn't often change case statements you could turn this into a function pointer for better readability and a little more speed.
@Kylotan:
The current bug that is bothering me is related to reseting the players after a play finishes. Basically something is not being reset properly and when the players try to reform for the next play they end up moving (illegally) before the snap (and I haven't yet programed the players to make mistakes...). I'm having a hard time tracking it down because I'm not sure in which class (game manager, team, or player) and in which part of which switch statement my error is.

But this is not the only reason that I want to get rid of the switch statements. The other reason is related to expanding my code later on. I would like to make it easier to add more game states later on if I would like. There is also the issue of having to juggle multiple states. (eg. both teams could be in the "play clock" phase of the game but one is on "offense" and the other is on "defense".)

I will also move the state handling code out of the switch statement like you suggested

Quote:From what you've said, it seems you're trying to encapsulate a little too much. Can you say a bit more about what the 3 classes do.

Sure.
For now the Render window only contains the field scene but later on I would like to add other "scenes" for things like the main game menu or a settings menu.

The field scene contains the mesh for the stadium and the field, as well as the camera for the scene. It also contains the game manager.

The game manager contains the two teams (which is one class, and two instances like you said), handles the clock and the game state and will eventually check for penalties.

The team class holds the players for that team, handles loading formations and plays from files, and makes sure that the players are where they are supposed to be before the ball is snapped.

The player class basically follows the directions given to it by the team to which it belongs, but later I plan to implement some smart-ish AI in this class.

Basically I was trying to emulate the real-world organizational hierarchy of a football game in my code.
----Stevo
Hm, the only advice I can give (I don't know anything about American football) is to redesign your game classes and you'll probably find much that can be improved. Using UML or at least your own variation will help you see when you end up having to chain information down to child classes.
Quote:Original post by Stevo14
@Kylotan:
The current bug that is bothering me is related to reseting the players after a play finishes. Basically something is not being reset properly and when the players try to reform for the next play they end up moving (illegally) before the snap (and I haven't yet programed the players to make mistakes...). I'm having a hard time tracking it down because I'm not sure in which class (game manager, team, or player) and in which part of which switch statement my error is.


Split classes up according to responsibilities. Each takes responsibility for pretty much everything to do with that class. And make each responsible for checking that it's in the state you assume it is. Moving things out of switch statements isn't really going to change any of this. The ideal is that there should be no doubt which class a bug is in.

- Which class decides that a play finishes? If the game manager, then have that class tell the two teams. Have each team tell all its players. And have the game manager ASSERT that if a play is not in progress, both teams know this. Have teams ASSERT that if a play is not in progress, all their players know this.

Quote:But this is not the only reason that I want to get rid of the switch statements. The other reason is related to expanding my code later on. I would like to make it easier to add more game states later on if I would like. There is also the issue of having to juggle multiple states. (eg. both teams could be in the "play clock" phase of the game but one is on "offense" and the other is on "defense".)


Decide whether these are orthogonal states (which can vary independently) or substates (where they exist only as part of higher-level states). You can't use switch statements for each of more than a tiny number of orthogonal states as you'd end up with too many permutations. So you need to factor it out somehow. Obviously not every thing that is a 'state' requires a switch statement - how would you do a switch statement for things like position or velocity? Yet they are part of a player's state. So you have to decide which things create arbitrary divisions of behaviour, and which ones can simply be queried to modify the overall behaviour.
Quote:Original post by Kylotan
Some people may suggest moving to the State design pattern, but I don't think that solves your problem, just moves it.


He's trying to find the bugs; presumably he's not worried about the difficulty of fixing them (no "reworking" of "cumbersome" code can do that, except perhaps incidentally by causing a copy-paste error to vanish as redundancy is eliminated).

State (or perhaps Strategy - really, I don't distinguish :) ) pattern:

Teams and classes get their update function broken up into functions that "update" for each game state.

Each game state gets represented by a GameManagerUpdater (ugh), with an operator() that performs the update. (These are of course inherited from a common base with a pure virtual operator()). Each implementation does the state-specific update logic, and calls the appropriate state-specific update function in the player and team objects. To change the game state, simply create an updater for the new state and give it to the GameManager. One common approach is to let the GMU operator() return a reference to a GMU instance: either itself, or to the "next" state (because the update logic has determined that the game state should change).

This is a common way to switch from external to internal polymorphism.
Ok, so, according to everyone's advice, I've re-worked the team class and the player class by removing their "Update()" functions and creating a seperate function for each game state. Now their is only one switch statement in the GameManager class which calls the correct function for both teams, which in turn calls the correct function for each player. The code seems less redundant now at least, and will probably prove to be less cumbersome in the future.
Quote:He's trying to find the bugs; presumably he's not worried about the difficulty of fixing them
It is true that I am currently trying to simply find where the bug(s) is(are), but later on it might be nice to have my code setup so that the bug(s) is(are) not unnecessarily hard to fix.[smile]

I am also interested in learning more about the state model that has been mentioned. Could someone provide some links or would a simple Google search suffice?
----Stevo
Sure thing. :)

(About the second link: When you create a strategy, it's really the same idea as collecting polymorphic objects in a container to avoid external polymorphism. Just that you have a single item that you continuously replace, instead of a container of things. And that you need to bundle up part of the larger class and use composition to hold onto the bundle, where you just had a bunch of related but unorganized member variables before.)
Hey, thanks for the links. I read both of them and, although they were a little bit over my head (which can probably be fixed by spending some quality time with Google), they were very interesting. The further I delve into object-oriented programming the more interesting it gets.[smile]
----Stevo

This topic is closed to new replies.

Advertisement