CS 124, Spring 2017
Lab 7: Subroutines (with Turtle graphics)

For this lab, you will be using "turtle graphics," an old and well-known technique for making line drawings. You will work with a simple turtle graphics API, and you will write some subroutines that use that API. This is an exercise in using an API as well as writing your own subroutines.

You will probably want to use Eclipse for this lab, but that is not a requirement. You can create a new Eclipse project to hold the files for this lab, or you can add the files for this lab to an existing project. How you want to organize your work is up to you. You will need copies of three Java files, TurtleGraphics.java, TurtlePanel.java, and TextIO.java. You can find copies in /classes/cs124.

This lab is due by the start of next lab, at 2:00 PM next Thursday, October 19. You will submit just one file, named TurtleGraphics.java. Be sure to do all the exercises! Submit your program using the Submit web page, as usual. Remember that if are using Eclipse, then the file will be in a directory inside your Eclipse workspace directory.

Pair Programming Option

For this lab, you have the option of working with a partner. If you do decide to work with someone, the program that you write should do several subroutines for Exercise 5, not just one. Furthermore, you should do the type of improved error-handling described in "Better Error Handling" at the end of this web page. Also, you should make sure that both names are in the comment at the start of your program, and only one person should submit the program.

About Comments

Every subroutine that you write should have a Javadoc comment. The comment should come immediately before the subroutine definition, and is should start with /** and end with */. Comments that start with /** are "Javadoc" comments, and they should follow a certain format. You are not responsible for following the exact format for now, but you should still use /** for the comments on your subroutines. Note that in Eclipse, if you click the line before a subroutine definition, type /**, and press return, the Eclipse will add an outline of a Javadoc-style comment.

About The Turtle in Turtle Graphics

The programming assignment for this week uses Turtle Graphics. The idea of turtle graphics is simple: A simulated "turtle" moves around in a window, under control of the program. The turtle has a simulated "pen," which it can raise and lower. If the pen is down when the turtle moves, it leaves a trail: The pen draws a line from the starting point to the point to which the turtle moves. With proper programming, the turtle can draw some nice pictures.

You will only work in the file TurtleGraphics.java, but your workspace should also contain TurtlePanel.java and TextIO.java. TurtlePanel.java defines the turtle graphics methods that are used in the program. Do not make any changes to TurtlePanel.java; your program must work with the original version.

The file TurtlePanel.java defines a screen area that represents the turtle habitat. The habitat has x-coordinates that range from -10 on the left to 10 on the right, and it has y-coordinates that range from -10 on the bottom to 10 at the top. (These coordinates are real numbers, of type double, and they are not the same as pixel coordinates.) The turtle starts out in the center, at (0,0), facing to the right. In this program, the turtle is represented as a small triangle.

TurtleGraphics has a variable named turtle. You give commands to the turtle by calling methods in turtle such as turtle.forward(5), turtle.turn(45), and turtle.penUp(). To learn about all the available methods, you can read the source code file, TurtlePanel.java. But here is a list that includes all the turtle methods that you might need for this lab.

The Programming Assignment

You should be editing TurtleGraphics.java. Try running the progam. The main() routine creates the turtle window and then calls a subroutine named squareSprial(). This is done as an example. As you write your own subroutines, you can replace the call to squareSpiral() with a call to your own subroutine. This allows you to easily test each subroutine. You will write and test at least five subroutines. Then, in the last part of the lab, you will make the program more interesting by letting the user interactively give commands to the turtle.


Exercise 1: Write a subroutine that will draw a blue triangle inside a red triangle, as shown at the right. Remember that the definition of a subroutine cannot be inside another subroutine. Other than that, it can be anywhere in the class. In your subroutine, for this exercise, use only turtle.forward, turtle.back, turtle.turn, and commands for setting color and maybe line width. The subroutine has no parameters. The size and position of the big triangle are up to you.

Be sure to test the subroutine and every subroutine that you write, by calling it from the main() routine. For a subroutine that has parameters, you will want to try calling it with a variety of parameter values.


Exercise 2: Most subroutines have parameters. For this exercise, you will write a subroutine with a parameter, of type int. The subroutine should draw a picture similar to the one shown at the right. The parameter specifies the number of lines in the picture. All the lines should radiate from the current turtle position. Use a for loop to draw as many lines as the parameter specifies. Note that you can say "turtle.forward(length); turtle.back(length);" to a draw line of a given length along the direction in which the turtle is pointing and then return to the starting point of the line (ready to change the direction and draw the next line). The length should be chosen at random. You can use turtle.face to point the turtle in random directions, or use turtle.turn to turn a random amount between each line and the next.

Optionally, you can add another parameter, of type double, to specify the maximum allowed length of a line.

(Hint: Slow down the turtle to a crawl while you are first testing a subroutine. Note the call to turtle.setAutodelay(10) in main(). You can change the 10 to a larger number, such as 1000, to slow the turtle down more.)


Exercise 3: For your third exercise, you should write a subroutine that draws a set of nested squares, like the ten squares shown in the picture. You are required to use the drawRect() subroutine, which is already defined in the program, to draw each square. The squares should be centered at the current turtle position. You are not required to move the turtle back to that position at the end, but you can if you want. You can make all the squares the same color, or you can make them random colors.

The subroutine should have two parameters. One specifies the number of squares to draw, and one specifies the size of the gap between one square and the next. You will need to use a loop to draw the squares. (Hint: the size of the innermost square is twice the gap distance.)

You can use any turtle commands that you want, but you must use the drawRect() subroutine to draw the actual squares. You will need turtle.penUp() and turtle.penDown() to make it possible to move the turtle from one square to the next without drawing a line.


Exercise 4: Write a subroutine that sends the turtle on a random walk. The idea is to do the following over and over: Select one of the directions 0, 90, 180, or 290 at random; face in that direction; then move forward by a small, constant amount (say 0.25). Here is a loop that does this forever:

while (true) {
    int direction;
    direction = 90 * (int)(4*Math.random());
    turtle.face(direction);
    turtle.forward(0.25);
}

A sample random walk created by this code is shown in the picture on the right. One could just put the code into a subroutine. However, you should write a random walk subroutine that has at least one parameter. What the parameters do is up to you. A parameter could be the distance that the turtle moves forward in each step. Or a parameter could specify the number of steps that the turtle will take, instead of letting it walk forever. Another idea is to implement two different kinds of random walk, and use a boolean parameter to tell the subroutine which kind of walk to do. You can get a nice pattern by selecting the direction randomly from 0, 60, 120, 180, 240, 300 instead of from 0, 90, 180, 270. A different sort of random walk is obtained by facing in a completely random direction and moving forward a random amount each time. Another idea is to use a random color for each step in the random walk.

Note: An actual infinite loop would be a bad idea in this program, since there would be no way for the subroutine ever to return to main()! If the random walk uses a "while (true)" loop, then the turtle will eventually wander off the screen. If you want to detect when that happens and end the loop at that point, note that there are functions turtle.getTurtleX() and turtle.getTurtleY() that return the current x- and y-coordinates of the turtle. And remember that coordinates on the visible window extend from -10 to 10.)


Exercise 5: Write at least one additional drawing subroutine of your own design. The subroutine should have at least one parameter. Try to do something interesting or attractive! (If you are working on this lab with a partner, you are required to do at least two subroutines for this exercise.)


Final Exercise: Making it Interactive! In the completed version of your program, the user of the program should control the turtle by typing in commands. When you run the program, you should arrange the windows on your screen so that you can see both the Turtle window and the user input/output. The commands will be things that use built-in turtle methods and the subroutines that you wrote, such as like forward 5 or squares 20 0.25.

If you are working with a partner, you will need to do the improved version described in the section "Better Error Handling" below, but you should still read this section to learn the general requirements for the program. If you are working alone, you can do the version described here. For the basic version, you should add the following code to the end of the main() routine:

System.out.println("Type in your commands for the turtle!");
System.out.println();
while (true) {
   try {
       System.out.print(">>> ");
       doUserCommand();
   } 
   catch (Exception e) {
       System.out.println("Error in input: " + e.getMessage());
   }
   TextIO.getln();  // Discard the rest of the line of input.
}

You need to write the subroutine doUserCommand(). This subroutine should read one command from the user and implement it. A command is a single word, and it can be followed by the numbers that are needed as parameters to the command. There are no parentheses or commas in the input! You need to figure out what the command is and translate it into a call to one of the subroutines that you have written or to one of the built-in methods of the turtle.

For example, if the user enters forward 5, Your program should call turtle.forward(x) where the value of x has been set to the user's input, 5. If you named the subroutine for Exercise 3 nestedSquares, and if the user says squares 20 0.25, you might translate that into the subroutine call nestedSquares(n,x), where n and x have been set to the numbers input with the command.

This is not very complicated. You should read the user's command using the TextIO function TextIO.getWord(). This subroutine reads one word, consisting of non-blank characters, stopping at the first blank or end-of-line. Note that it does not discard the rest of the line. (TextIO.getlnWord() would do that.) You need to keep the rest of the line around so that you can read any numbers that are on the same line. Once you have the command itself, you can decipher it with tests such as if (command.equals("forward")), or you can use a switch statement to test the command. When carrying out the command, you can read in the appropriate parameter values using TextIO.getInt() and TextIO.getDouble().

Note that my main routine catches exceptions. Your subroutine should throw an IllegalArgumentException if the user enters a command that you have not implemented. This will cause the main routine to print an error message. You might also want to throw exceptions in your subroutines when a bad parameter value is provided, such as a negative value when that doesn't make sense.

Note that the code that you need to implement a user command can usually be reduced to one line. For example, you might implemented a "color" command that has three integer parameters with the subroutine call

turtle.color( new Color( TextIO.getInt(), 
                           TextIO.getInt(), TextIO.getInt() ) );

In your program, there should be a user command for each of the five subroutines that you wrote for exercises 1 through 5. You should also provide user commands for at least the basic built-in turtle subroutines: forward, turn, and reset. And you should implement the command exit, with no parameters, to end the program, by calling System.exit(0). You might also implement other built-in commands as user commands; for example, commands for setting the turtle color and line width, raising and lowering the pen, hiding and showing the turtle, and setting the delay time between turtle actions.

The exact design of your turtle command language is up to you, but be sure to document it in the comment at the beginning of the program!

Better Error Handling

This part of the lab is required if you are working with a partner, but you are certainly welcome to do it in any case! The goal is to implement better error handling when processing user input in your program. TextIO really doesn't handle bad input very well. You might want to try my solution, which you can find in /classes/cs124TurtleGraphicsComplete.jar. (Run this program on the command line with the command java  -jar  TurtleGraphicsComplete.jar.)

My program breaks a line of input from the user into individual strings, then uses Double.parseDouble and Integer.parseInt to parse the user's parameter values. It also checks that the correct number of parameters was provided. Because I want to process a complete line of input, I pass the whole line as a parameter to doUserCommand(). I replaced the while loop in the main program with

while (true) {
   try {
       System.out.print(">>> ");
       doUserCommand(TextIO.getln());
   } 
   catch (Exception e) {
       System.out.println("Error in input: " + e.getMessage());
   }
}

And the method itself starts like this:

private static void doUserCommand(String line) {
    String[] tokens = line.split("\\s+");
    if (tokens.length == 0)
        return;  // Ignore empty lines.
    switch ( tokens[0].toLowerCase() ) {
    case "forward":
        if (tokens.length != 2)
            throw new IllegalArgumentException(
                            "Wrong number of params for forward.");
        turtle.forward( toDouble(tokens[1]) );
        break;
    .
    .
    .

The first line of code in this method uses line.split("\\s+") to break the line into an array of "tokens" where a token consists of a sequence of non-blank characters. (The "\\s+" is a "regular expression", which you will not learn about in this course but might encounter in CPSC 229.) So, token[0] is the command, and the remaining array elements should be the parameters for the command, but represented as Strings instead of numeric values. Later in the code, when parsing the parameter for the forward command, I use toDouble(tokens[1]) to convert the string to a double value. I defined the function toDouble as follows:

private static double toDouble(String s) {
    try {
        return Double.parseDouble(s);
    }
    catch (NumberFormatException e) {
        throw new IllegalArgumentException(
                      "Expected a real number, but found " + s);
    }
}

This allows me to give a better error message when the parameter is not a valid real number. You would need a similar function to handle integer parameters.