Object-oriented – Designing a card game

cdesignobject-oriented

I want to expand my personal projects portfolio, so I decided to make a card game. To be more precise, it's called Macau. I've read this answer on StackOverflow and tried to follow the steps from the first answer. I made a narrative in which I describe the general rules of the game and its particularities.

Macao game. The minimum number of players is 2. The deck of cards
initially contains 52 cards. Each card has the following attributes:

  • Suit (diamonds, clubs, hearts and spades)
  • Rank (2, 3, 4, 5, 6, 7, 8, 9, A, J, Q, K, Joker)

Starting the game, the deck is shuffled and every player receives 5
cards from it. The first player that remains out of cards is the
winner. Afterwards, a card is taken from the deck and put on the table
with the face up.

The current player looks at the card that's been put on the table and
decides whether he can put a card from his packet on the table or not.
A player can put a card on the table if their card is compatible with
the other one.

Two cards are compatible if they accomplish one of the following
conditions: they have the same rank or the same suit. Expanding upon
the cards domain, there exist several special cards. These have to
obey the specified rules too, but they have an extra attribute: the
special ability. They may have any suit, but these ranks qualify a
card to be special: 2, 3, 4, 7, A, and Joker. The Joker is a wildcard
as it doesn’t need to obey to the earlier specified rule. That is, the
player can put it on the table at any time.

The next section gives a thorough description of the mentioned cards’
special abilities:

  • Rank: 7. When put on the table, this card requires the current player to specify a suit. The next player is obliged to put any card
    that has the earlier specified suit, otherwise he will receive a new
    card from the deck.

  • Rank: 2 and 3. This card obliges the next player to receive 2/3 (2 cards if the rank is 2, same rule for 3) cards from the deck. Now, if
    the next player also has a 2 or a 3 with the same suit, or a Joker, then he can
    put it on the table and the next player has to receive the total
    amount of cards. The same rules apply in a circular manner. If the
    next player has a 4 with the same suit, then he can stop the
    obligation injected by the previous player.

  • Rank: 4. Special ability: it can stop an obligation injected by the previous player. If it is put over a regular card (i.e. a card
    that has no special ability), then it acts like one.

  • Rank: A (Ace). Special ability: it skips the next player's turn.
  • Rank: Joker. It is a wildcard so it can be put on the table at almost all instances. The exception arises when the card that is on
    the top of the table is an A (Ace), then the current player's turn is
    skipped. Special ability: the same as the 2/3 card, except that the
    next player will have to receive 5 cards (for the Black Joker) or 10
    (in case of the Red one).

Having the rules clarified, the game continues. If the current player
owns such a compatible card (or more), then it's his choice whether to
put one on the table or receive a new card from the deck. Also, while
in game, there can appear the situation when the deck would be
emptied. In that case, all the cards from the table that are located
under the top one are taken, shuffled, and used to refill the deck.
The card on the top is untouched.

Now it's the next player's turn, and the earlier described steps
repeat until one of the players remains out of cards. That player is
the winner.

From this narrative I extracted the following classes:

  • Card
  • Deck
  • Player (maybe Hand?)
  • Table (that contains the Deck and the stack of already played cards)
  • Game

Here come my problems.

  1. I'm not sure if I made the right abstractions.
  2. I wouldn't know how to design the special cards. Should there exist a separate (inherited from Card) class for every special card? Or should I use composition? Also, how should I implement the Joker cards? They don't really have a Suit, they just have the Rank.
  3. Should the Game class manage players' turns and the exceptional cases (those cases when the top card would be a special one)?
  4. Should Player be just an interface? This would leave space for additional features like an AI player (that implements Player).

This is my first medium-sized object-oriented project and I want to be sure that I'm on the right path.

Best Answer

I'm not sure if I made the right abstractions.

Well, it is a start, but you will find only out if you stop overthinking it and start to write some code using these abstractions. This code could be tests, real game logic, or both.

I wouldn't know how to design the special cards. Should there exist a separate (inherited from Card) class for every special card? Or should I use composition?

Neither. If I got this right, being "special" is just a property which can be derived from the rank. So when a Card class has an attribute Rank (as well as Suit), it carries all the information the game logic requires to make a decision about the "special abilities".

Also, how should I implement the Joker cards? They don't really have a Suit, they just have the Rank.

Simple: introduce a fifth Suit "None" for Jokers. Your card's constructor can throw an exception if someone tries to initialize a card with a suit "None" for non-Jokers or a card with a rank "Joker" and a different suit.

Should the Game class manage players' turns and the exceptional cases (those cases when the top card would be a special one)?

Somewhere you need to implement the game's logic. Putting it initially into the Game class is a sensible start. If that class evolves, and it becomes too large / gets too many responsibilities, try to identify such responsibilities, give them a name and refactor them to separate classes. There is no need to overanalyse this beforehand.

Should Player be just an interface? This would leave space for additional features like an AI player (that implements Player).

Again, there is no need to make this decision now. Start with one Player class, and when you come to the point where you need a HumanPlayer and an AIPlayer, it is early enough to refactor.

Related Topic