/* --- CSU213 Spring 2006 Lecture Notes --------- Copyright 2006 Viera K. Proulx Lecture 7: Amazing Maze Goals: - learn to define and use methods in classes with union and self-reference Introduction: The following class diagram represents the data for a simple maze game that consists of three different kinds of rooms: Token rooms, Gold rooms, and Pits. The player starts the game in one room. In each token room the player add to her fortune the number of tokens found in the token room and chooses to go to the next room through either the left or the right door. When the player reaches the Pit, the game ends with player loosing everything. Each Gold room contains a key in the shape of a number. When the player reaches the Gold room each token changes into as many gold nuggets as the number on the key, and the game ends. The maze is design in such way that the player cannot visit the same room twice. +------+ | Room |<---------------------+ +------+ | / \ | | | - - - - - - - - - - - - - - - - - | | | | | +-----+ +------------+ +------------+ | | Pit | | Gold | | Token | | +-----+ +------------+ +------------+ | +-----+ | int factor | | int num | | +------------+ | Room left |----+ | Room right |----+ +------------+ Here is an example of such maze: +---------+ | start | | TOKEN 5 | +---------+ / \ / \ +-----+ +---------+ | PIT | | token3 | +-----+ | TOKEN 3 | +---------+ / \ / \ +-----+ +--------+ | PIT | | gold | +-----+ | GOLD 2 | +--------+ The player starts in the 'Start' room and adds 5 tokens to it's token collection. If he chooses to go left, he ends up in a Pit and the game ends with no winnings. If the chooses to go right, he goes to the TokenA roon and adds 3 tokens to the token collection. After that, going to the left again leads to a Pit, but going to the right ends the game with 16 gold nuggets - as each of the eight tokens in the player's possession turns into tow gold nuggets. We now design the classes that represent this game and make examples of data, specifically to represent the game shown above. // to represent a room in a maze game interface Room { } // to represent a pit in a maze game class Pit implements Room { Pit() { } } // to represent a gold room in a maze game class Gold implements Room { int factor; Gold(int factor) { this.factor = factor; } } // to represent a token room in a maze game class Token implements Room { int num; Room left; Room right; Token(int num, Room left, Room right) { this.num = num; this.left = left; this.right = right; } } class Examples { Examples () {} Room pit = new Pit(); Room gold = new Gold(2); Room token3 = new Token(3, this.pit, this.gold); Room start = new Token(5, this.pit, this.token3); } We only need one instance of a Pit, because there is no way to differentiate between the instances, because the class Pit has no fields. We now want to count the number of pits in the whole game. That means, we need to add a method 'countPits' to the classes that represent different rooms in the maze. The method consumes no arguments - knowing where in the maze we are at the current time (the value represented by 'this', the object that invoked the method) is sufficient to answer the question. We add the method header to each class - and to their common interface. We omit the constructors from the text below - to save the space and to highlight the connections between the classes. // to represent a room in a maze game interface Room { // count the number of pits in the maze starting at this room int countPits(); } // to represent a pit in a maze game class Pit implements Room { // count the number of pits in the maze starting at this pit room int countPits() {...} } // to represent a gold room in a maze game class Gold implements Room { int factor; // count the number of pits in the maze starting at this gold room int countPits() {...} } // to represent a token room in a maze game class Token implements Room { int num; Room left; Room right; // count the number of pits in the maze starting at this token room int countPits() {...} } The next step in the design recipe call for examples. We use the examples of data from above. Room pit = new Pit(); Room gold = new Gold(2); Room token3 = new Token(3, this.pit, this.gold); Room start = new Token(5, this.pit, this.token3); boolean testCountPits1 = this.pit.countPits() == 1; boolean testCountPits2 = this.gold.countPits() == 0; boolean testCountPits3 = this.token3.countPits() == 1; boolean testCountPits4 = this.start.countPits() == 2; Remember, we must have examples for each of the three variants of the union --- because we are designing three different methods, one for each variant of the union. The first two methods (for the classes Pit and Gold) are trivial - they always return one and zero respectively. The method for the class Token is harder. We start with the template: ... this.num ... -- int ... this.left ... -- Room ... this.right ... -- Room ... this.left.countPits() ... -- int ... this.right.countPits() ... -- int We added for each self-reference an invocation of the method 'countPits' that we are designing. Let us read aloud the purpose statements for these two method invocations: // count the number of pits in the maze starting at this (left) room ... this.left.countPits() ... // count the number of pits in the maze starting at this (right) oom ... this.right.countPits() ... It is easy to see that the total count of pit rooms in the maze that starts in a token room is the sum of these two quantities. The three methods then become: // in the class Pit: // count the number of pits in the maze starting at this pit room int countPits() { return 1; } // in the class Gold: // count the number of pits in the maze starting at this gold room int countPits() { return 0; } // in the class Token: // count the number of pits in the maze starting at this token room int countPits() { return this.left.countPits() + this.right.countPits(); } Of course, we now have to run our examples. Next we want to figure out what is the maximum number of tokens a player can collect (before falling into a pit, or before the tokens get converted into gold nuggets). Again, knowing the room where the game starts is sufficient, and so the method 'mostTokens' needs no additional arguments. The purpose and the method header is: // find the most number of tokens a player can collect starting in this room int mostTokens(){...} Here are examples: boolean testMostTokens1 = this.pit.mostTokens() == 0; boolean testMostTokens2 = this.gold.mostTokens() == 0; boolean testMostTokens3 = this.token3.mostTokens() == 3; boolean testMostTokens4 = this.start.mostTokens() == 8; It is clear that themethods for the Pit class and for the Gold class are again trivial (always produce zero). For the class Token, the template is extended by the two self-referential method invocations: ... this.num ... -- int ... this.left.mmm() ... -- Room ... this.right.mmm() ... -- Room ... this.left.countPits() -- int ... this.right.countPits() -- int ... this.left.mostTokens() -- int ... this.right.mostTokens() -- int It is clear that the number we want is the sum of the number of tokens in this room (this.num) and the larger of the two values (this.left.mostTokens() and this.right.mostTokens). We design a helper method 'maxValue' that produces the larger of the two given intergers. The method is defined in the class Token, but it makes no use of the Token object that invokes it. We leave it to the reader to go through the design steps. Finally, the body of the method 'mostTokens' becomes: // find the most number of tokens a player can collect starting in this room int mostTokens(){ return this.num + this.maxValue(this.left.mostTokens(), this.right.mostTokens()); } Running the tests confirms that we are on the right track. Remember, test can only verify that the test cases work properly --- it is usually impossible to guaranteee through tests that the program works correctly for all possible inputs. Complete working code is shown below. Notice, how we extended the class diagram with the list of method headers for all methods defined in these classes, or required for the given interfaces. This description of the classes allows the client of the class see clearly how the instances of the class can be defined and used. +------------------+ | interface: | | Room |<----------------------------------------+ +------------------+ | | int countPits() | | | int mostTokens() | | +------------------+ | / \ | | | - - - - - - - - - - - - - - - - - - - - - - - - - - - | | | | | +------------------+ +------------------+ +------------------------+ | | Pit | | Gold | | Token | | +------------------+ +------------------+ +------------------------+ | | int countPits() | | int factor | | int num | | | int mostTokens() | +------------------+ | Room left |--+ +------------------+ | int countPits() | | Room right |--+ | int mostTokens() | +------------------------+ +------------------+ | int countPits() | | int mostTokens() | | int maxValue(int, int) | +------------------------+ */ // to represent a room in a maze game interface Room { // count the number of pits in the maze starting at this room int countPits(); // find the most number of tokens a player can collect starting in this room int mostTokens(); } // to represent a pit room in a maze game class Pit implements Room { Pit() { } /* // TEMPLATE ??? mmm() { } */ // count the number of pits in the maze starting at this pit room int countPits(){ return 1; } // find the most number of tokens a player can collect starting in this room int mostTokens(){ return 0; } } // to represent a gold room in a maze game class Gold implements Room { int factor; Gold(int factor) { this.factor = factor; } /* // TEMPLATE ??? mmm() { ... this.factor ... -- int } */ // count the number of pits in the maze starting at this gold room int countPits(){ return 0; } // find the most number of tokens a player can collect starting in this room int mostTokens(){ return 0; } } // to represent a token room in a maze game class Token implements Room { int num; Room left; Room right; Token(int num, Room left, Room right) { this.num = num; this.left = left; this.right = right; } /* // TEMPLATE ??? mmm() { ... this.num ... -- int ... this.left.mmm() ... -- Room ... this.right.mmm() ... -- Room ... this.left.countPits() -- int ... this.right.countPits() -- int ... this.left.mostTokens() -- int ... this.right.mostTokens() -- int } */ // count the number of pits in the maze starting at this token room int countPits(){ return this.left.countPits() + this.right.countPits(); } // find the most number of tokens a player can collect starting in this room int mostTokens(){ return this.num + this.maxValue(this.left.mostTokens(), this.right.mostTokens()); } // find the maximum of the two given numbers int maxValue(int a, int b){ if (a > b) return a; else return b; } } class Examples { Examples () {} Room pit = new Pit(); Room gold = new Gold(2); Room token3 = new Token(3, this.pit, this.gold); Room start = new Token(5, this.pit, this.token3); boolean testCountPits1 = this.pit.countPits() == 1; boolean testCountPits2 = this.gold.countPits() == 0; boolean testCountPits3 = this.token3.countPits() == 1; boolean testCountPits4 = this.start.countPits() == 2; boolean testMostTokens1 = this.pit.mostTokens() == 0; boolean testMostTokens2 = this.gold.mostTokens() == 0; boolean testMostTokens3 = this.token3.mostTokens() == 3; boolean testMostTokens4 = this.start.mostTokens() == 8; }