2

Assume we want to model a table where players can sit down to get together to play a game (card games, dice games, ...). Assume a few properties associated with each seat

class Seat
{
    public int PlayerId { get; set; }
    public double Money { get; set; }
    // ...
}

Say the Table is simply modelled as

class Table
{
    public Seat[] Seats = new Seat[6];
}

And I want to represent empty seats. Is it a better practise to have Seats[i] = null or adding a Flag / Enumeration / ... that indicates the Seat is empty and all properties are invalid? Like if (Seats[i].SeatState != Seatstate.Empty) { ... }

Christophe
  • 81,699
Benj
  • 169

1 Answers1

3

A Seat should know whether or not it is occupied. Simply because someone joins or leaves the game doesn't mean you need a new Seat(). The player has just vacated that spot at the table.

That being said, the PlayerId and Money properties are currently value types, meaning they will always have a value. In this case it appears they do not always have a value, so the first change should be to make those properties nullable (the second change I would make is to use decimal as the data type for the Money property).

Currently everything in both Table and Seat is public. In the very least you want to enforce some encapsulation, and make the setters for your properties private. My first sentence has some bold face words and phrases, which you can use to guide your design of the Seat class:

  • is occupied
  • joins or leaves the game
  • vacated

These give you some of the basic operations that can be performed on a Seat. The Seat class should have a property that returns whether or not this particular seat is currently occupied. When both PlayerId and Money are null, then seat.IsOccupied should return false.

This leads us to discover our first class invariant: when PlayerId is not null, then Money should not be null as well. The setters for both properties need to be private. Something needs to ensure when a Seat becomes occupied that both player and money have values. We need a public method on the Seat class called Occupy that takes both values: public void Occupy(int playerId, decimal money). Here we can use the Type System in C# to ensure both values are not null by virtue of the fact they are both value types.

Now when you create a new Seat() the player Id and money properties are both null and seat.IsOccupied returns false. After calling seat.Occupy(45, 1200.00m) both properties are set, and then seat.IsOccupied returns true.

Finally, when someone leaves the game, they vacate the seat. The seat.Vacate() method should set both PlayerId and Money to null, which will cause seat.IsOccupied to return false.

var seat = new Seat(); // Create vacant seat

seat.Occupy(45, 50.00m);

// Play game

seat.Vacate(); // Spouse calls. Kids are going crazy. Time to go home.

But we still have a problem. Calling seat.Vacate() makes the player (and their money) disappear. While this works out great for players that owe the House money (or maybe it doesn't...) players that still have money do not want to blink out of existence simply because they left the game. You need a Player class to encapsulate the player Id and money properties. So a Seat really only needs a Player in order to determine if it IsOccupied:

var player = new Player(45, 50.00m);
var blackJackTable = new Table();
var texasHoldemTable = new Table();

blackJackTable.Seats[0].Occupy(player);
Console.Writeline(blackJackTable.Seats[0].IsOccupied); // Prints "True"
blackJackTable.Seats[0].Vacate();

texasHoldemTable.Seats[2].Occupy(player);
texasHoldemTable.Seats[2].Vacate(); // Spouse calls. Kids are going crazy. Time to go home.

Now a Player can occupy and vacate a seat, and then transfer their winnings to another game.

But we still have a problem. You need to know which seat is available when joining a table. The Table class needs some encapsulation as well. The Table class should expose a public method allowing you to join the table. In fact, public Seat Join(Player newPlayer) would be a great method to add to the Table class. That encapsulates logic for joining the table. It returns a Seat which at a later time the player can choose to Vacate(). By exposing a public boolean property on Table called IsFull that returns true if all Seats are occupied, code outside the Table class does not need access to the Seats array. The Seats array should be completely hidden. Make Seats a private, read-only property. This is called information hiding, and is another fundamental concept of object oriented programming.