I'm putting together a program to try and recreate a board game. My goal was to keep the rules and actually actions of the game separate from its presentation, so I am creating a library that contains the objects and methods that define what you can do as you play the game. Besides the classes that define the games objects, I was planning to create two classes to "front" the game. The first class GameRunner
was the class that the presentation layer would use to call actions on the game such as AddPlayer
, TakeGems
, BuildCard
etc. Then I wanted a second class to handle the game state, aptly named GameState
, that would keep track of the players and their field, as well as the market for the game. In GameState
I created the methods that would affect the game state to define how players are added and cards are purchased.
What I have noticed is that all of the game logic is being defined in GameState
and GameRunner
now is essentially just turning into a pass through class, in other words, all it does is call the appropriate method in GameState
and return the state object:
public GameState AddPlayer(string name)
{
gameState.AddPlayer(name);
return gameState;
}
This just seems repetitive to me. Does this make sense as a design? A separation of concerns between actions and what they do? Or should I just make GameState
a class that has the objects that make the state of the game, and just have GameRunner
manipulate those state fields from it's methods instead. Example:
public GameState AddPlayer(string name)
{
if(Players.Count < 4)
{
gameState.Players.Add(new Player(name))
gameState.Success = true;
}
else
{
gameState.Success = false;
}
return gameState;
}
1 Answer 1
By returning GameState from your GameRunner functions, you are making the client of GameRunner dependent on GameState as well. IE the GameRunner client knows the details of GameRunner's implementation.
I think the delegation of tasks to the GameState (as in the first example) is the better of the two options, thus when the logic of (in this instance) adding a player to a Game changes, it's only required to be updated in one place: the GameState itself.
The GameRunner acts as an Anti Corruption Layer translating between the concerns of the Presentation Layer and that of the game's engine. As such it should contain presentation logic, and delegate changes to the game to GameState. If the GameRunner were to be concerned with the logic of the game as well, it would violate the Single Responsibility Principle. (There should be only one reason for a module, class, or function to change). It would now have to change if the presentation logic needed an update, or if the game logic needed to change.
-
Thanks. That makes more sense of it that I could have. Since I'm thinking of having multiple ways of presenting the game, potentially a WPF app then possibly trying to host it online, does it make sense to have GameRunner focus on only one of those implementations and then later create a transformation layer to convert it to what the other presentation would need?Evan Frisch– Evan Frisch2015年05月30日 13:26:56 +00:00Commented May 30, 2015 at 13:26
-
1@Walker: you might transform this into a MVVM architecture (see en.wikipedia.org/wiki/Model_View_ViewModel). Model=GameState, View = your WPF classes, ViewModel=GameRunner, or an MVP architecture (Presenter=GameRunner). That way, your GameRunner depends only of an interface to the Presentation layer, which makes it easier to exchange the Presentation layer later.Doc Brown– Doc Brown2015年05月30日 18:03:36 +00:00Commented May 30, 2015 at 18:03
-
1@DocBrown I'll have to take a look more into MVVM and MVP to pick one but that seems like exactly the kind of structure I'm looking for. Thanks a lot!Evan Frisch– Evan Frisch2015年05月30日 18:20:19 +00:00Commented May 30, 2015 at 18:20
AddPlayer
return the gameState object to the caller?? This seems to break encapsulation.GameState
but for now I do that to remind myself that the whole state is being returned.