It is fascinating how quickly a simple problem can escalate to a very difficult design problem.
Here are the major pieces needed to frame one of these difficult problems in Tic Tac Toe:
CellValue : X, O, EMPTY Board : state -> CellValue[9] Game : board -> Board
Everything is going well until we get to Game.getWinner(). In Tic Tac Toe, the winner is one of four things: X, O, Draw or “unknown/in progress”. The above definitions already includes “X” and “O” – so, can getWinner() return type CellValue? That is a bit awkward, since “EMPTY” makes sense for a Board, but “EMPTY” is a poor name to use for “Draw”. And what string should “EMPTY” return? The “-” to use when printing a board, or “Draw” for printing the winner?
One possible solution is this design:
Player : X, O CellValue : X(Player.X), O(Player.O), EMPTY Winner : X(Player.X), O(Player.O), DRAW, INPROGRESS Board : state -> CellValue[9] Game : board -> Board
Now, Game.getWinner() returns type Winner, and there are no “null” values floating around to spawn runtime errors. The two similar “Player” concepts are captured explicitly (i.e. “X” can be both a value on a Board, and a winner of a Game), with only a small glitch around things like “Does board.state[3] equal game.getWinner()”? i.e. the sub-problem of comparing a CellValue with a Winner, when they currently share no useful common parent type.
If your goal is to get a good grade in a software engineering class, then use the second design. If your goal is a software project that can generate Kaggle CSV dataset files, the first design will suffice (See tictactoe).