Each Noun Phrase in the problem description corresponds to a candidate (i.e., possible) class. Our goal is to capture every possible thing that could be a class… maybe.
We do not perform any pruning… yet.
Each Verb Phrase in the problem description corresponds to a candidate (i.e., possible) responsibility (i.e., behavior or member function). Our goal is to capture every possible action that could be a responsibility… maybe.
We do not perform any pruning… yet.
This is an informal step. I only remove move things I believe to be synonyms. I capture this by listing the synonym in parens next to the preferred noun phrase.
This is an informal step. I only remove move things I believe to be synonyms. I capture this by listing the synonym in parens next to the preferred verb phrase.
I have reordered the entries that I believe might be similar.
This is a simple example: A Single Tic-Tac-Toe Match. I am going to look a some pseudo-code for selected pieces of a single match. Let us look at a single move by a single player:
In pseudocode the <-
represents assignment, e.g., x <- 5
would show that x
is assigned the value 5
.
selectedCell <- player1.makeMove()
symbol <- player1.getSymbol()
board.set(selectedCell, symbol)
Let us assume–for the moment–that all players make valid moves all the time. That leaves us with two classes:
It would be really nice if we had some sort of UML Class Diagram to handle this notation–instead of inventing our own notation.
In class, and in the notes I reference Dia. As a Programmer–who likes vim and neovim–I like my keyboard, and dislike my mouse.
The provided markup is PlantUML Class Diagram syntax.
@startuml
Player : symbol
Player : makeMove()
Board : getCell(position)
Board : setCell(position, symbol)
@enduml
The resulting UML Class Diagram is a good start…
Of course, I need to add the missing move
argument to Player::makeMove
.
Notice how I added strategy? I do not believe a Player generates a move with no thought. There must be some logic behind it. Strategy is a noun. I am not sure how to fully capture the notion of Strategy:
For now I will include Strategy
as a class and use a Labeled Association to link it to Player
For the rest of this discussion, the PlantUML markup will be shown in code listings. Click the +
symbols to expand and view the markup.
The corresponding PlantUML markup can be written as:
@startuml
hide empty members
Player : name
Player : symbol
Player : makeMove(theBoard : Board, theMove)
Board : getCell(position)
Board : setCell(position, symbol)
class Strategy {
}
Player --> Strategy: employs
@enduml
Note: I added hide empty members
to collapse all empty attributes and behaviors. I do not particularly like the resulting markup. I am a stickler for consistency. Let us tweak the Player
and Board
lines.
@startuml
hide empty members
class Player {
name
symbol
makeMove(theBoard : Board, theMove)
}
class Board {
getCell(position)
setCell(position, symbol)
}
class Strategy {
}
Player --> Strategy: employs
@enduml
Let us add the Referee
and Game
classes to our diagram. I am pretty sure I will include both Game
and Referee
in my final model. If for some reason I am fundamentally mistaken… I can remove them later.
@startuml
hide empty members
class Player {
name
symbol
makeMove(theBoard : Board, theMove)
}
class Board {
getCell(position)
setCell(position, symbol)
}
class Strategy {
}
class Referee {
}
class Game {
}
Player --> Strategy: employs
@enduml
Let us revisit our lists of Noun Phrases
Note the crossed-off entries. A few of these are obvious (i.e., they are classes already included in our model). Three are not so obvious: Cell, Symbol and Move.
I claim that Symbol
is an attribute of Player
. Every Player
has a symbol
. The symbol
is merely a marker placed on the Board
. A symbol
has no operations. I will probably use a character when I start writing code (e.g., char symbol
).
I claim that Move
follows a similar argument. A move
is merely a position
on the game Board
. This is sufficiently captured with board::setCell
.
Note that I made a mistake in Player How can makeMove accept a move argument? The makeMove operation gives me the move! I guess this is why naming is important. I have corrected this in the next diagram. I have also changed makeMove
to nextMove
.
and Verb Phrases
Let us tackle Game
and Referee
concurrently. We have not tackled:
I argue that Rules
are covered by Referee
. However, I am not sure. I better make a note to ask the Domain Expert (Question Time).
I also argue that Winner
and Loser
are not appropriately captured as classes. A Game
will have two Player
s: player1 and player2. It is possible for neither to win (i.e., there is no winner or loser). Let us update Game
with this new insight.
Note that I listed that player1 and player2 are of the type Player
. This is appropriate. Player
is a domain specific type (i.e., it is a conceptual notion). It is not an implementation detail.
We are now left with
Notice how I updated Tie? I think it is a synonym for Stalemate.
Let us tackle Round
. I think this is really meant to capture a single two-player turn sequence (i.e., player1 makes a move then player2 makes a move). English is fun… This probably should have been listed as a Verb Phrase in the form: player1 makes a single move followed by player 2. This is defined as one round or turn. Context is key. This is why the candidate lists are merely a starting point.
Let us update our Game
class…
I will apply a similar set of arguments to Win, Loss, and Stalemate. These are probably meant to check the results at the and of a single Game
… once the Game
is Over. We now have four new operations: isOver
, endedWithWin
, endedWithLoss
, and endedWithStalemate
. Let us update our model…
Wait… I do not like that. How do I know when a win occurs? What about a loss? What about a stalemate? I think there is some sort of process behind these checks. I think this is handled by the Referee
.
I am much happier with this. The Referee
checks for the win, loss, or stalemate. The Game
merely records if these have occurred. I am comfortable with this logic.
Let us add the relationship between Referee
and Board
.
Our PlantUML markup is now:
@startuml
hide empty members
class Player {
name
symbol
nextMove(theBoard : Board)
}
class Board {
getCell(position)
setCell(position, symbol)
}
class Strategy {
}
class Referee {
validate(selectedMove, theBoard : Board)
checkForWin(theBoard: Board)
checkForLoss(theBoard: Board)
checkForStalemate(theBoard: Board)
}
class Game {
player1: Player
player2: Player
getWinner() : Player
getLoser() : Player
playOneRound()
isOver()
endedWithWin()
endedWithLoss()
endedWithStalemate()
}
Player --> Strategy: employs
Referee --> Board: examines
@enduml
I do not think a Game Board
can exist outside a Game
. Let us add the appropriate composition relationship.
@startuml
hide empty members
class Player {
name
symbol
nextMove(theBoard : Board)
}
class Board {
getCell(position)
setCell(position, symbol)
}
class Strategy {
}
class Referee {
validate(selectedMove, theBoard : Board)
checkForWin(theBoard: Board)
checkForLoss(theBoard: Board)
checkForStalemate(theBoard: Board)
}
class Game {
player1: Player
player2: Player
getWinner() : Player
getLoser() : Player
playOneRound()
isOver()
endedWithWin()
endedWithLoss()
endedWithStalemate()
}
Player --> Strategy: employs
Referee --> Board: examines
Game *--Board
@enduml
We can also say that a Referee
is assigned to a Game
and that Referee
keeps Player
s honest
Before we continue… we should probably add isFull
to Board
. I think the Referee
might need this information for the stalemate check.
This leaves us with a fairly some fairly long PlantUML markup.
@startuml
hide empty members
class Player {
name
symbol
nextMove(theBoard : Board)
}
class Board {
getCell(position)
setCell(position, symbol)
isFull()
}
class Strategy {
}
class Referee {
validate(selectedMove, theBoard : Board)
checkForWin(theBoard: Board)
checkForLoss(theBoard: Board)
checkForStalemate(theBoard: Board)
}
class Game {
player1: Player
player2: Player
getWinner() : Player
getLoser() : Player
playOneRound()
isOver()
endedWithWin()
endedWithLoss()
endedWithStalemate()
}
Player --> Strategy: employs
Referee --> Board: examines
Game *-- Board
Referee --> "2" Player : keeps honest
Referee --> "1" Game : assigned to
@enduml
What are we conveying in our current diagram? We are saying everything at once. We only have five (5) classes in this model. Imagine a larger model (i.e., one with at least 12 classes).
Could you make sense of such a digram? The answer is no.
Let us strip out the associations.
This captures our known Class interfaces
and attributes
. What do we want to convey? Now… Let us form coherent thoughts!
This diagram captures how players have a strategy. It is based on this strategy they select their moves. The referee ensures that not invalid moves are made (e.g., that players do not cheat).
This diagram captures the structure of a single game (i.e., one match between two players on a single board). Each board is fundamentally linked to a single game
We should probably consider Human and Computer Player
s. I will leave writing a description as an exercise to the reader.
This results in our last piece of PlantUML markup.. for now!
@startuml
hide empty members
class Player {
name
symbol
nextMove(theBoard : Board)
isHuman()
isComputer()
}
Player <|---- HumanPlayer
Player <|---- ComputerPlayer
@enduml
How would our previous C++, Java, and Python design decisions change?
Nope… this is the last piece of markup. I guess I am a victim of the standard off-by-one error.
@startuml
hide empty members
class Player {
name
symbol
nextMove(theBoard : Board)
}
class Board {
getCell(position)
setCell(position, symbol)
isFull()
}
class Strategy {
}
class Referee {
validate(selectedMove, theBoard : Board)
checkForWin(theBoard: Board)
checkForLoss(theBoard: Board)
checkForStalemate(theBoard: Board)
}
class Game {
player1: Player
player2: Player
getWinner() : Player
getLoser() : Player
playOneRound()
isOver()
endedWithWin()
endedWithLoss()
endedWithStalemate()
}
Player <|---- HumanPlayer
Player <|---- ComputerPlayer
@enduml