4

Context: I'm going to use the board game Sorry as an example, but this question could apply to other board games. In Sorry, each player has four pawns. Based on drawn cards, a player moves their pawns around the board. These moves can impact other players' pawns, like bumping another player's pawn back to Start.

For an (oversimplified) possible implementation of the game, consider this architecture: 1. Game class - Responsibility is handling turns. Has a collection of Player objects 2. Player class - Responsibility is enabling a player (AI or human) to take actions. Has a collection of that player's Pawns. 3. Pawn class - Responsibility is knowing its state; each instance represents a single entity.

Example: For some Player actions, like performing a Sorry, the player needs to know where all pawns are to make an informed decision. Other actions are similar, such as choosing which pawn to move; I might move a pawn so it ends on an opponent's pawn.

A simple approach would be for the Player class to have functions like: Pawn* ChooseSorryPawn(vector oppPlayers). The function would then query each Player for its Pawns, build the whole list of opponent Pawns, and then make a decision.

Concern: For one function, the above might be ok. But this same pattern would be needed for all Player functions involved with making a decision, since a player needs to know the whole state of the board.

Question: Is this really the best way to go, or is there a better design pattern for letting a Player make decisions without passing in direct references to opposing Players and their Pawns? Might a Mediator be the right approach here?

As a related question, would it better to store Pawn position state in a separate class, like a GameBoard class, instead of with the Pawns themselves? That way, each Player would take a GameBoard reference in its constructor.

4 Answers 4

8

The best design decision to make here is the data structure to use to represent the state of the game.

You want one that makes it easy to evaluate how good a game position is for each player and make moves.

I don't see that happening if you have to ask players where their pieces are.

Rather a player should make a move by saying "here's the board" by passing to the next player the new data structure that represents the board.

each player would take a GameBoard reference in its constructor.

You don't have to mutate the board. You can make completely new ones each time and just pass them along.

I don't see a lot of functionality in the pawns to make them worth turning into an object. The cards however are very interesting.

4
  • 1
    "You don't have to mutate the board. You can make completely new ones each time and just pass them along." Obviously this depends on how complex the game state is. Sure, you can do this for chess or go or similar small sized games, but it might get a bit hairy for a game like, say, Civilization. That said, immutable boards make AI implementation much easier.
    – Jules
    May 15, 2017 at 19:25
  • 1
    @Jules it depends on the systems memory capacity. But we're talking about sorry. May 15, 2017 at 19:29
  • @CandiedOrange Where do you see the cards fitting in? Echoing Joel below, perhaps the drawn card uses the board to determine which pawns can legally move and what the legal moves are?
    – Craig
    May 18, 2017 at 3:19
  • @Craig sounds right to me May 18, 2017 at 3:32
2

One way to do this would be through the observer pattern. As each pawn moves, it notifies other players about its position. In this case, if you have a user interface, it can also be an observer and be notified to display pawn positions to the user.

1
  • While I'm leaning toward the GameBoard design proposed by others, I appreciate you bringing up the observer approach.
    – Craig
    May 18, 2017 at 3:24
2

This context (modeling a game of Sorry) lends itself very well to modeling the class interfaces/interactions consistently with the real world interactions.

When you're playing Sorry, do you ask other players for the position of their pawns? No. Then why would you model your classes such that the Player requests a Pawn vector from another Player?

How do you actually interact with the game/board? You "ask" the board for the current board state. You don't ask for Player 1's Pawns, then Player 2's Pawns, etc. You simply make a request of the board (a visual request for the sighted among us).

The Player doesn't "ChooseSorryPawn", the player chooses a Pawn to apply the Card to and the result of that Pawn landing on or sliding past an opposing Pawn is the opposing Pawn being moved back Home. (Sorry if my recollection of the rules is incorrect).

The interactions are between Player and Board and within the Board. The Board may have Card and Pawn, but I would probably keep the interactions between Player and Board (Player chooses Pawn), Board and Card (Board has a deck of Cards, Board reveals a Card), and Board and Pawn (Board moves Pawn).


Additionally, I think it's helpful to think about how a human player would interact with your electronic Sorry game as opposed to how a player interacts with a physical Sorry game. A player shuffles the deck because the cardboard Board/Cards cannot do it themselves; the player picks a card because the card can't reveal itself; the player moves the pawn because the Board/Pawns cannot move themselves; the player says, "Sorry!" in a super disingenuous way because the player is a jerk and the Board is just inanimate cardboard.

The only aspects of gameplay that are fully the responsibility of the Player are:

  1. Deciding which Pawn to move
  2. Being a jerk
1
  • Chris G - Liked the "being a jerk" comment.
    – Craig
    May 18, 2017 at 3:23
1

For Sorry! in particular, I would personally design it such that the game state and board classes are the same. There isn't a player's private hand or a bank or anything of the sort. The cards belong to the board, the pieces belong to the board, and all players do is draw and then interpret the state of the board to make decisions.

All you need is some kind of identity for pieces, some kind of identity for players, and a mapping between pieces and players. All the board needs to expose is an interface to view the entire board, an interface to draw a card which will return a card/a status (i.e. skipped turn because you can't act on the card, skip player input because there was only one possible thing that could happen with the card, waiting for player input), and an interface to execute a move for the current player that takes an input of a specific piece ID. You could arrange the move API several different ways.

You could have a method per card type. You could have a more general function that accepts a specific move type and have an exception for Sorry/leaving Start. You could also just use one function with various optional parameters. Depends on what you think is the most clear.

You could also return a set of valid moves from the card drawing function, which would simplify the choice of move function (since you just pass back the one that the player chooses) if there is more than one move. If there's one move, you can return no moves and just perform that move. Of course, there would need to be a way to query the card so you can display it for users, but that could be a part of the board query as well.

Either way, these are really the only two functions of the game. All of the logic of the game can be handled by the board.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.