CS 151: Lab #9

Lab Exercise 9: Classes

Main course page

The purpose of this lab is to give you practice in creating your own classes. In particular, we will convert both the lsystem and the transformer modules into classes. Ultimately, this will make it easier to build a more complex system.

If you have not already done so, take a look at the book 'The Algorithmic Beauty of Plants', which is the basis for this series of lab exercises. You can download the entire book from the algorithmic botany site

The other piece we'll be implementing this week is handling multi-rule L-systems. Adding this capability will enable you to draw more complex L-system structures.


Tasks

Most of the exercises in lab will involve building up an L-system class. This week it will be important for you to use the method names provided. The next several labs will expect the L-system and Transformer classes to have certain methods with particular names.

  1. On the Desktop, make a folder called lab9.
  2. Create a new file called lsystem2.py, which will hold the definition of the L-system class and its methods.
  3. A class has a simple structure. It begins with the class declaration.

    class Lsystem:

    Then we define the __init__() method (function), which is executed when a Lsystem object is created. The init method should set up the fields of a class and give them reasonable initial values. Init methods often take optional arguments. You can make an argument optional by assigning a value to it in the argument list of the function declaration. For example, the following function has two arguments, the second of which is optional with a default value of 5.

    def boo( a, b = 5 ):
        print a, " ", b
    

    The Lsystem init function should have four arguments: self, an argument for the angle, an argument for the base string, and an argument for the rules. The latter three arguments should be optional. The init function should then store this information in the object. For now, create an object field called base, an object field called angle, and an object field called rules, and assign the values from the arguments to the three fields. For the base string and angle, this is fine. We'll need to do something more complex with the rules field.

    Creating a field inside of an object is just like creating a regular python variable by assigning a value to a symbol. The only difference is that the object field must be a member of self. For example:

    self.angle = angle

    Finally, put a print statement in your init function that tells you the init function for an Lsystem is being called. Then start up python, import lsystem2 and try to create an Lsystem object.

    t = lsystem2.Lsystem( 90, 'FF', [] )

  4. Create accessor functions that let you set each field of the class. The functions should be setAngle, setBase, and setRules. Each function will have self as the first argument and the new value for the field as the second.

    The setAngle and setBase methods will each take a number or string, respectively. A simple assignment statement is all that's needed.

    The setRules function, however, will take a list of lists as its argument. For example, the following is a possible input to the setRules function.

    [ [ 'F', 'FF' ], [ 'X', 'F[+X][-X]FX' ] ]

    The algorithm for the setRules function is as follows.

        def setRules( self, irules ):
            # assign the empty list to the rules field of self
            # for each rule in irules
    	  # call the addRule method (see below)
    
  5. We need one more utility function that lets us add a single rule to our current rules list. Create a method addRule as below.
        def addRule( self, rule ):
            # assign the empty list to a new variable to hold the new rule
            # for each element of rule
                # append the element to the new variable
            # append the new rule to the rules field of self
    

    Now finish up the setRules to properly call addRule, if you haven't already.

    When the addRule and setRules functions are done, update your __init__ function to use it to initialize the rules field.

    Once you have completed your accessor functions, start python in a Terminal, import your lsystem2 module and test your accessor functions. Create a new lsystem object, then use the accessor functions to set the fields. Make sure the resulting lsystem is correct by looking at the fields of the object.

  6. Create a read method that has self and a filename as its arguments. There are two main differences between the read method and the createFromFile function we made last week. First, we need to use fields of the self variable instead of indexes into a list to store the information. Second, we want to use the addRule function we just created instead of the setRules, because there may be more than one rule in the L-system file. Otherwise, they are very similar.
        def read( self, filename ):
            # open the file 
            # read all of the lines of the file
            # close the file
            
            # for each line in the file
                # split the line into words
    
                # if the first string in the list is initial
                    # set the base string to the second element in the words list
                # if the first string in the list is angle
                    # set the angle to the second element in the words list
                # if the first string in the list is rule
                    # use the addRule method to add the rule 
                    # the rule is from the second element to the end of the words list
    
  7. To have L-systems with multiple rules we have to write our own replace method. The algorithm is as follows.
        def replace( self, istring ):
    
            # initialize the new string (newstring) to the empty string
            # for each symbol in istring
                # initialize a found variable to False
                # for each rule in the self.rules field
                    # if the symbol matches the first element of the rule
                        # concatenate the second element of the rule with the new string
                        # set found to be True
                # if found is not True
                    # concatenate symbol with the new string
            # return the new string
    
  8. Define a method buildString that takes in self and a number of iterations as its arguments. The function should return the string built by the L-system after the number of iterations specified. The algorithm is as follows.
    def buildString( self, iterations ):
      # assign the base string to a local variable (e.g. curstring)
      # for the number of iterations
        # assign to curstring the output of the new replace method called on curstring
      # return curstring
    
  9. Define a method test() that asks the user for a filename, reads in the L-system from the file and prints out the string that results after 0, 1, and 2 iterations (0 iterations should be the base string).
    def test(self):
      # get an input filename from the user with the raw_input() function
      # read in the L-system from the file
      # for iterations in 0 to 2
         # use buildString to generate the string wiith the proper iterations
         # print out the string 
    

    Once you finish the test function, put the following lines at the bottom of the lsystem2.py file.

    if __name__ == "__main__":
      g = Lsystem()
      g.test()
    

    Now if you run the file using the command python lsystem2.py the test() function will execute. If you import the lsystem2.py file into another python file, however, the test() method will not execute.

    Run the file on the following example L-systems.

  10. Create a new file called transformer2.py. You will want to import the turtle package and the turtleUtils module into this file.
  11. Begin the definition of a class called Transformer. We're going to start the class with something a little different. We're going to make a class variable turtleInitialized and set it to false.
    class Transformer:
    
        turtleInitialized =  False
    

    Inside of methods, we can access this variable using the notation Transformer.turtleInitialized. There is only one copy of this variable, but any Transformer object can access it. We're using it to keep track of whether the turtle window has been created.

  12. Create the __init__ method that takes two optional arguments that describe a window size. If the Transformer.turtleInitialized variable is False, then the method should call turtleUtils.setup(), then call turtle.reset(), then set Transformer.turtleInitialized to True. You may also want to set turtle.Tracer(False). That's it.
  13. Define a method drawString( self, distance, angle, string ) that takes in a distance, an angle, and a string and interprets the string into a set of turtle actions. You should be able to just copy and paste your existing drawString function into this method.
  14. Write a test(self) method that calls drawString with some kind of string. You can just hard-code the string if you wish, for now.
  15. Use the following if statement at the bottom of the file to call the test method when the program is run on the command line. Run your transformer test program.

    if __name__ == "__main__":
        xform = Transformer()
        xform.test()
    

When you are done with the lab exercises, go ahead and get started on the assignment.