Skip to main content
Engineering LibreTexts

3.6: Testing an Improved OneRowNim

  • Page ID
    15075
  • \( \newcommand{\vecs}[1]{\overset { \scriptstyle \rightharpoonup} {\mathbf{#1}} } \) \( \newcommand{\vecd}[1]{\overset{-\!-\!\rightharpoonup}{\vphantom{a}\smash {#1}}} \)\(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\) \(\newcommand{\id}{\mathrm{id}}\) \( \newcommand{\Span}{\mathrm{span}}\) \( \newcommand{\kernel}{\mathrm{null}\,}\) \( \newcommand{\range}{\mathrm{range}\,}\) \( \newcommand{\RealPart}{\mathrm{Re}}\) \( \newcommand{\ImaginaryPart}{\mathrm{Im}}\) \( \newcommand{\Argument}{\mathrm{Arg}}\) \( \newcommand{\norm}[1]{\| #1 \|}\) \( \newcommand{\inner}[2]{\langle #1, #2 \rangle}\) \( \newcommand{\Span}{\mathrm{span}}\)\(\newcommand{\AA}{\unicode[.8,0]{x212B}}\)

    Let’s use the control structures that we have discussed to improve the definition of the takeSticks() method of OneRowNim. We noted earlier that our current definition allows \(4\) or more sticks to be removed from nSticks even though the rules of One Row Nim indicate that a player must take one, two, or three sticks on a turn. We can use if-else statements to make certain that no more than \(3\) sticks get removed.

    What should happen if the method takeSticks() is called with an argument that does not represent a legal number of sticks to remove? In this case, it would probably make sense to remove no sticks at all and to keep the value of player the same so that the player whose turn it is does not change. In addition, it would be nice if the method could signal that an illegal move has been attempted. This can be accomplished if we redefine takeSticks() to return a boolean value. Let’s have a return value of true represent the case that a valid number of sticks have been removed and the player to play next has been changed. A return of false will indicate that an illegal move has been attempted. Making these changes to the takeSticks() method will yield a method definition that looks like:

    public boolean takeSticks(int num) 
    {   if (num < 1) {
           return false; // Error
        } else if ( num > 3) {
           return false; // Error
        } else {
           nSticks = nSticks - num;
           player = 3 - player;
           return true;
        } //else
    } //takeSticks

    Notice that the new definition of the takeSticks() method has a boolean return type. Also notice that the if/else multiway structure is used to handle the three cases of the parameter num being less than one, more than three, or a valid number.

    Let us add one more method to the OneRowNim class. Let’s define a method called getWinner()that will return the number of the winning player if the game is over. Recall that the player who takes the last stick loses, so after that last play, the player whose turn it is to play next is the winner. However, we should be concerned about what value to return if the game is not over when the method is called. A common strategy is to have a method return a special value to indicate that it is in a state in which it cannot return the value requested. Returning a \(0\) value is a good way to indicate that the game is not over so a winner cannot be identified. With this information, the if/else statement can be used in the definition of getWinner().

    public int getWinner()
    {   if (nSticks < 1)
            return player;
        else 
            return 0;
    } // getWinner()

    We now have the final version (for this chapter) of the OneRowNim class whose implementation is given in Figure [fig-ornclass]. We have turned a very simple class into one that contains quite a few elements. Compared to our first version (in Chapter 1), this Chapter’s version of OneRowNim presents an interface (to other objects) that is easy and convenient to use. The constructor methods with parameters provide an easy way to create a OneRowNim instance with any number of sticks. The use of private instance variables and a single, carefully designed mutator method, takeSticks(), prevents other objects from tampering with the state of a OneRowNim object’s state. The other methods provide a flexible way to find out the state of a OneRowNim object. The complete implementation of this OneRowNim is shown in Figure [fig-ornclass].

    public class OneRowNim 
    {   private int nSticks = 7;
        private int player = 1;
    
        public OneRowNim() 
        {
        } // OneRowNim() constructor
    
        public OneRowNim(int sticks) 
        {   nSticks = sticks;
        }  // OneRowNim() constructor2
    
        public OneRowNim(int sticks, int starter) 
        {   nSticks = sticks;
            player = starter;
        }  // OneRowNim() constructor3
    
        public boolean takeSticks(int num) 
        {   if (num < 1) return false;       // Error
            else if ( num > 3) return false; // Error
            else              // this is a valid move
            {   nSticks = nSticks - num;
                player = 3 - player;
                return true;
            } // else
        } // takeSticks()
    
        public int getSticks() 
        {   return nSticks;
        } // getSticks()
    
        public int getPlayer() 
        {   return player;
        } // getPlayer()
    
        public boolean gameOver()
        {   return (nSticks <= 0);
        } // gameOver()
    
        public int getWinner()
        {   if (nSticks < 1) return getPlayer();
            else return 0;  //game is not over
        } // getWinner()
        
        public void report()
        {   System.out.println("Number of sticks left: " + 
                                                 getSticks());
            System.out.println("Next turn by player " + 
                                                 getPlayer());
        }   // report()
    } // OneRowNim class

    Let’s use a while statement to test the new methods of the class. A pseudocode description of how a game is played might look like:

      Choose the initial number of sticks for the game
      while the game is not over
      {   Report the state of the game
          Process the next move
      }
      Report the state of the game
      Report who the winner is

    Translating this pseudocode into Java code in a main() method in a separate class gives us the class shown in Figure [fig-tornclass]. We will use the Scanner class

    import java.util.Scanner;  
      
    public class TestOneRowNim
    {
      public static void main(String argv[])
      { Scanner sc = Scanner.create(System.in);
        OneRowNim game = new OneRowNim(11);
        while(game.gameOver() == false) 
        {   game.report();  // Prompt the user
          System.out.print("Input 1, 2, or 3: ");
          int sticks = sc.nextInt(); // Get move
          game.takeSticks(sticks);   // Do move
          System.out.println();
        } // while
        game.report();  // The game is now over
        System.out.print("Game won by player ");
        System.out.println(game.getWinner());
     } // main()
    } // TestOneRowNim

    introduced in the previous chapter to get moves from the keyboard for both players. Before each move game.report() describes the state of the game before the user is prompted to input a move for one of the players. A reader interested in seeing the lengthy output to the console when the TestOneRowNim class is run is encouraged to actually run the program.

    Note that the return value of the takeSticks() method is ignored in this test program. We will make use of the return value in test programs in the next chapter when better user interfaces are developed for OneRowNim. Note, however, that taken together, the public methods for OneRowNim provide other objects with an interface that they can use to communicate with individual OneRowNim objects.

    To reiterate a point made at the outset, object-oriented programming is a process of constructing objects that will interact with each other. Object-oriented programs must ensure that the objects themselves are well designed in terms of their ability to carry out their designated functions. Good design in this sense requires careful selection of instance variables and careful design of methods to ensure that the object can carry out its assigned tasks. However, equal care must be taken to ensure that the interactions that take place among objects are constrained in ways that make sense for that particular program. This aspect of designing objects comes into play in designing the methods—constructor, accessor, and mutator—that make up the object’s interface.

    Special Topic: Intelligent Agents

    Wouldn’t it be nice if we had a computer program that could schedule appointments for us, remind us of meetings and commitments, find information for us on the WWW, and manage our e-mail messages for us? Wouldn’t it be nice to have a computerized personal assistant?

    Actually, such programs are called intelligent agents, which are programs that are capable of acting autonomously to carry out certain tasks. Intelligent agent technology is becoming an important research area in computer science. Most agent programs incorporate some kind of machine learning capability, so that their performance improves over time.

    As a typical agent activity, suppose I was able to tell my intelligent agent to buy me a copy of a certain book that I just heard about. Given a command like “buy me a copy of X,” the agent would perform a search of online book sellers and come up with the best deal. Once it had found the best buy, the agent would communicate with a computer-based agent representing the book seller. My agent would make the order and pay for it (assuming I gave it authority to do so), and the book seller’s agent would process the order.

    As far-fetched as the capability may now seem, this is the direction that research in this area is headed. Researchers are developing agent languages and describing protocols that agents can use to exchange information in a reliable and trustworthy environment. Obviously, you wouldn’t want your agent to give your money to a fraudulent book seller, so there are significant problems to solve in this area that go well beyond the problem of simply exchanging information between two agents.

    The best way to learn more about this research area is to do a Web search using the search string “Intelligent Agent.” There are numerous research groups and companies that provide online descriptions and demos of their products.

    From the Java Library java.lang.Object

    The most general class in Java’s class hierarchy is the java.lang.Object class. It is the superclass of all classes that occur in Java programs. By default, it is the direct superclass of any class that does not explicitly specify a pedigree in its class definition.

    All subclasses of Object inherit the public and protected methods contained in Object, so all such methods can be thought of as belonging to the subclasses. This means that all classes inherit the methods of the Object class, because every class is a subclass of it. In this section, let’s look briefly at how we can use an inherited method and also at how we can override it–that is, redefine the method–if it doesn’t exactly suit our purposes.

    One of the most useful methods in the Object class is the toString() method:

    public class Object
    {   
        public String toString() ;
    }

    The toString() method returns a String representation of its object. For example, o1.toString() will return a String that in some sense describes o1.

    Because OneRowNim is a subclass of Object, it inherits the toString() method. To illustrate the default behavior of toString(), let’s use it with a OneRowNim instance:

      OneRowNim g1 = new OneRowNim(11);
      OneRowNim g2 = new OneRowNim(13);
      System.out.println(g1.toString());
      System.out.println(g2.toString());

    This code segment creates two OneRowNim instances, one named g1 and the other named g2. The inherited toString() method is then invoked on each OneRowNim instance, which produces the following output:

      OneRowNim@1dc6077b
      OneRowNim@1dc60776

    What this experiment shows is that the default definition of toString() returns some kind of internal representation of its object. It looks as if it returns the name of the object’s class concatenated with its memory address. This may be useful for some applications. But for most objects we will want to override the default definition to make the toString() method return a string that is more appropriate for OneRowNim.

    What String should the g1.toString() method return? Let’s have it return a String that reports the OneRowNim instances’s current state, which are the values stored in the two instance variables. To override a method, you simply define a method with the same signature in the subclass. If you call toString() with an instance of the subclass, its version of the method will be used. In this way, the subclass method overrides the superclass version. Thus, OneRowNim.toString() will have the following signature:

    public String toString();

    Let us describe the state of a oneRowNim instance very briefly in the string returned by the toString() method:

    public String toString()
    { return "nSticks = " + nSticks + ", player = " + player;
    }

    If we add the toString() method to the OneRowNim class and then run the program shown in Figure [fig-orntostring], we get the following output:

      nSticks = 9, player = 2
      nSticks = 13, player = 1
    public class TestToString
    {
      public static void main(String argv[])
      { OneRowNim g1 = new OneRowNim(11);
        OneRowNim g2 = new OneRowNim(13);
        g1.takeSticks(2);
        System.out.println(g1.toString());
        System.out.println(g2.toString());
      } //main
    } //TestToString

    While this new method may not play an important role in the OneRowNim class, it does provide a very brief, understandable description of the state of the object. This is the reason that the toString() method was included in the Object class.

    Object-Oriented Design: Inheritance and Polymorphism

    This use of Object’s toString() method provides our first look at Java’s inheritance mechanism and how it promotes the generality and extensibility of the object-oriented approach. As a subclass of Object, our OneRowNim class automatically inherits toString() and any other public or protected methods defined in Object. We can simply use these methods as is, insofar as they are useful to us. As we saw in this case, the default version of toString() wasn’t very useful. In that case, we can override the method by defining a method in our class with the exact same method signature. The new version of toString() can be customized to do exactly what is most appropriate for the subclass.

    One of the great benefits of the object-oriented approach is the ability to define a task, such as toString(), at a very high level in the class hierarchy and let the inheritance mechanism spread that task throughout the rest of the hierarchy. Because toString() is defined in Object, you can invoke this method for any Java object. Moreover, if you override toString() in the classes you define, you will be contributing to its usefulness. Two important lessons from this example are

    Obviously there is much more that needs to be explained about Java’s inheritance mechanism. Therefore, we will be revisiting this topic on numerous occasions in subsequent chapters.

    Another important concept of object-oriented design is polymorphism. The toString() method is an example of a polymorphic method. The term polymorphism is from the Greek terms poly, which means “many,” and morph, which means “form.” The toString() method is polymorphic because it has different behavior when invoked on different objects.

    For example, suppose we design a class, Student, as a subclass of Object and define its toString() method to return the student ID number. Given this design, then obj.toString() will return a student ID if obj is an instance of Student, but if it is an instance of OneRowNim, it will return a the description of its state that we defined above. The following code segment illustrates this point:

    Object obj;           // obj can refer to any Object
    obj = new Student("12345");// obj refers to a Student
    System.out.println(obj.toString()); // Prints "12345"
    obj = new OneRowNim(11); // obj refers to a OneRowNim
    System.out.println(obj.toString());
    
    // Prints: nSticks = 11, player = 1

    In this case, the variable obj is used to refer to a Student and then to a OneRowNim instance. This is okay because both classes are subclasses of Object. When toString() is invoked on obj, Java will figure out what subclass of Object the instance belongs to and invoke the appropriate toString() method.


    This page titled 3.6: Testing an Improved OneRowNim is shared under a CC BY 4.0 license and was authored, remixed, and/or curated by Ralph Morelli & Ralph Wade via source content that was edited to the style and standards of the LibreTexts platform; a detailed edit history is available upon request.