CSC 120(A) - Principles of Computer Science I Prof. Nadeem Abdul Hamid Fall 2004 Class Notes - Object-Oriented Design: A Tic-Tac-Toe Game (These are notes on my recollections of what we've covered in class. Use them to supplement yours... Hopefully they are very similar.) ********************************************************************** Section 1. The CRC Process As an example of the process of object-oriented program design, let us suppose we have the problem of writing a program that will let a human player play a game of TicTacToe (TTT) against the computer. I assume you already know what the game of TTT is, so let's start by identify all the objects that are in this problem. That is, as we model a game of TTT on the computer, what are the real-world objects that interact during the game which we have to capture in our computer implementation. Well, here is an initial list generating by brainstorming a bunch of possible objects: Objects: -------- board O's and X's (marks) boxes (locations where marks can be made) human player computer player pencil and paper score ? (not really applicable if we're just playing a single game of TTT at a time) Now, the next step is to filter and revise our initial list of objects to include only those objects which are most applicable to a computer implementation. For example, we don't need "pencil and paper" because the computer will keep track some other way of the state of the "board". Also, the "marks" and "locations where marks can be made" can be included as part of the "board" object. Let's assume that the board will be in charge of keeping track of those things. There's one other object that we didn't capture in our brainstorming above but that we will probably need to model in the computer. That is an overall controller of the game, like a referee. In a game between two human players, we usually play fairly enough so we don't need a referee to make sure that we wait our turn before making a mark, to check whether the game is a win/loss or tie, to make sure that one player doesn't erase the other's mark and put his/her own in its place, etc. (It seems kind of silly or obvious that we don't need this in "real-life," but that is because we have some social mechanisms controlling our interaction, assuming both human players know how to play the game properly. When we model this game on the computer, however, the computer does not "understand" any such built-in mechanisms of social interaction, so we have to explicitly provide a model for a "referee".) Anyway, our revised list of objects is: Objects: (revised) ------------------ board human player computer player game controller (acts as a referee, as discussed above) Now, the next step in our object-oriented design (OOD) process is to figure out the *responsibilities* and *collaborations* of each of the objects above. That is, in the course of a TTT game, what operations/duties are each of the objects responsible for, and which other objects do they depend on (collaborate with) in order to carry out those operations/duties. Let's start with the players: each player has to support an operation of deciding a move to make. The player will probably need to see the board in order to decide a move, so for this operation, the players will collaborate with the board object. (Notice that *how* the players decide a move will probably be radically different... the human player will give input on the keyboard, the computer player will have some "artificially intelligent" strategy... but the interface provided by both players is the same: they support a basic operation of deciding a move to make.) What about the game controller? The game controller will have a bunch of tasks associated with being a referee -- like setting up the initial board, getting the players ready, etc. -- but let's just group all those into a single abstract operation of "running a game". In order to run the game, obviously, the controller has to work with the players and the board, so it collaborates with all of the other objects. Here, then are a list of the responsibilities and collaborations of the various objects in our design: Human Player ------------ Responsibilities Collaborations 1. decide a move Board Computer Player --------------- Responsibilities Collaborations 1. decide a move Board Game Controller --------------- Responsibilities Collaborations 1. run a game Board, Human Player, Computer Player Board ----- Responsibilities Collaborations 1. keep track of marks at the various locations on the board 2. set a mark at a given location 3. tell whether there is a mark at a given location 4. determine whether the board is in a win/loss state or a tie 5. display the current state Screen? (e.g. System.out) of the board ********************************************************************** Section 2. Java Classes and Methods Alright, so we have developed a reasonable set of objects and their operations which will allow us to implement the TTT game in a computer program. The process we have followed above is one approach to OOD known as the CRC (Classes-Responsibilities-Collaborations) card design process. The benefit of this process is that we know now what Java classes and methods we need to write to implement a TTT program. Each of the objects we've identified above will be present in our Java implementation, and in order to create them, we will use a blueprint, which is the Java "class" definition. Then, each of the objects above has a set of associated functionality. Each operation will correspond to a method within the appropriate class. Hence, below is a skeleton of the Java classes and methods we will have to fill in. Notice that there are some ??? at places where Java types should appear. We have to work out the implementation details of how we represent some things still, so for now we'll just use the ??? placeholders. (For example, the decideMove() methods are supposed to return a move that the player wants to make. But what Java data type is used to represent a "move"? We'll have to work that out later.) public class HumanPlayer { public ??? decideMove(Board b) { } } public class ComputerPlayer { public ??? decideMove(Board b) { } } public class GameController { public void runGame() { } } public class Board { // has to store marks on the board somehow // ... /* put a mark at the given position */ public void setMark(??? pos, ??? mark) { } public ??? getMark(??? pos) { } /* tell if the board is in a winning state for the specified player */ public boolean isWin(??? player) { } public boolean isTie() { } /* return a String representation of the board, to be printed on the screen */ public String toString() { } } Notice, that as we implement more and more of this program, we will be refining the responsibilities and collaborations of the various classes of objects. Thus, we should continuously update our CRC designs above as well to keep them consistent, but for brevity, I won't keep doing that in these notes. ********************************************************************** Section 3. Functional Decomposition Another software design methodology, which is older than object-oriented design but is complementary to it, is known as functional decomposition, or also "top-down design" or "stepwise refinement". This design methodology concentrates more on the algorithms rather than objects and data structures. For example, we can use functional decomposition to design the runGame() operation of the GameController class. The idea is to decompose the runGame() operation into a series of smaller sub-operations, which may in turn be composed of yet smaller and more primitive sub-operations. As we decompose into smaller and smaller operations, we are increasing the level of detail and decreasing the level of abstraction. So, let's take a look at the runGame() functionality of the GameController. Here is some "pseudo-code" for the algorithm to manage and run a TTT game: runGame: set up an initial board get players ready while (still empty spaces or someone wins) (display the board ... for human user only?) determine the next move from the current player mark the move on the board tell the result of a game: who wins/loses or if it's a tie Let's now translate this algorithm into Java code. We'll keep track of who's the current player by keeping tracking of the number of turns in the game so far. If the turn number is odd it'll be one player's turn; if it's even it'll be the other player's turn. public void runGame() { Board b = new Board(); HumanPlayer h = new HumanPlayer(); ComputerPlayer c = new ComputerPlayer(); int turn = 1; while (!board.isTie() && !board.isWin()) { if (turn%2 == 1) { // computer goes on odd turns ??? move = c.decideMove(b); // computer's move board.setMark(move, 'X'); } else { ??? move = h.decideMove(b); // human's move board.setMark(move, 'O'); } turn++; } if (board.isWin('X')) System.out.println("Computer wins!"); else if (board.isWin('O')) System.out.println("Human wins!"); else System.out.println("Tie game!"); } Having performed the object-oriented analysis earlier means our functional decomposition is more readily accomplished, because we have already defined such sub-operations as isTie(), isWin(), decideMove(), and setMark() in the Board and Human/ComputerPlayer classes, so it's intuitively obvious how to define the process of running a game in terms of those sub-operations. Now we have to drill down into each of those operations and define their functionality, which will probably require us to start worrying about some concrete implementation details. Notice that by now, I've already introduced one implementation detail which is that the marks are being represented as characters 'X' or 'O'. The isWin() method of the Board class is now taking one of these characters as a parameter to determine of there are three of those characters in a row somewhere on the board. We've still got to decide about how moves are represented though (notice the ???'s). So, in the next section, let's discuss some remaining implementation details of the Board class. ********************************************************************** Section 4. Implementation Details As we went over in class, there are different posssible approaches to implementing the details of how locations on the board, moves, and the state of the board itself are implemented. In short, we decided to represent each of the possible locations on the TTT board by integers in the range 0 to 8. i.e.: 0 |1 |2 | | | | ----------- 3 |4 |5 | | | | ----------- 6 |7 |8 | | | | Thus, when deciding a move, a player will have to provide one of these integers to indicate where he/she/it(computer) wants to place a mark. The board class also has to keep track of the marks that have been made. Again, as we discussed in class, a good approach would be to store the mark data as a string of length 9. Each character position in the string will indicate the mark that is stored at the corresponding position in the board above. We can use the substring() and charAt() methods of the string class to manipulate and update the state of the board. I will provide the complete Java implementations of the Board, GameController, and HumanPlayer classes on the class web page. I will also include a ComputerPlayer with a really dumb playing strategy. It will be your job to figure out a better strategy for the computer to decide its moves.