Welcome to Maine
CS 151: Computational Thinking
(Spring 2014)

Syllabus

Teachers
Beatrice
Caitlin
Stephen
Dan
Lanya
Victoria
Matt
Hieu
Jacob
Scott
Hieu


Assignments


Other Pages

Project 9:
Unique Trees and Shapes


Assigned: Wed Apr 16 2014
Due: 11:59:59 PM on Tue Apr 22 2014
Team Size: 1
Language: Python


Lab:

Inheritance

The purpose of this lab is to give you practice in creating a base class and many child (derived) classes that inherit the methods and fields of a base class. In addition, we'll be modifying the L-system class to enable it to use rules that include multiple possible replacement strings and choose them stochastically (randomly).

Adding the capability to incorporate different replacements will enable you to draw yet more complex L-system structures where each tree is unique. We'll continue to use material from the ABOP book.


Tasks

This week we'll modify the lsystem.py and turtle_interpreter.py files and create a new shapes.py file that implements shapes other than trees. Please follow the naming conventions for classes and methods. We'll provide some test code that expects particular classes, methods, and parameter orderings.

  1. Create a new working folder. Copy your lsystem.py and turtle_interpreter.py files from your prior assignment (version 2). This week we are writing version 3 of both files. Make sure a comment at the top of the file indicates they are version 3.
  2. The change to the Lsystem class is to enable it to use rules with multiple possible replacements. During replacement, the particular replacement string is chosen randomly. There are a number of methods requiring modification: __init__, read, replace, and addRule. In addition, you'll need to import the random package.

    An example of an L-system with multiple possible replacements is given below. Note that the rule has the symbol F followed by three separate replacement strings, separated by spaces.

    base F
    rule F F[+F]F[-F]F F[+F]F F[-F]F
    

    To make handling multiple rules easier, we're going to make use of Python dictionaries. A dictionary is an implementation of a lookup table. Each entry in a dictionary has a key (like a word) and a value (like a definition). To create an empty dictionary we use curly brackets instead of square brackets like a list.

    myDict = {}

    To add a new entry to a dictionary, we index the dictionary with the key and make an assignment. The following creates an entry in the dictionary for the string 'F'. The value of the entry is a list containing the string 'FF'.

    myDict['F'] = [ 'FF' ]

    In the __init__ method, change the initialization of the self.rules field to be an empty dictionary instead of an empty list.

  3. Remove the getRule method. It no longer makes sense because rules are not stored via indices.
  4. In the addRule method we need to redo the function. We no longer want to append a 2-element list to self.rules. Instead, we want to make an assignment into the dictionary with a new key. The value should be a list of all the possible replacments for a symbol.

    Consider the process from file to dictionary. For example, the line below is a possible rule for a tree.

    rule F F[+F] F[-F] F[+F][-F]

    After reading the line and breaking it into strings using split, the words variable in the read method would be the following.

    [ 'rule', 'F', 'F[+F]', 'F[-F]', 'F[+F][-F]' ]

    If we pass words[1:] into addRule as the rule argument, then the rule variable will be:

    [ 'F', 'F[+F]', 'F[-F]', 'F[+F][-F]' ]

    The symbol is the first item in the list (rule[0]) and the set of replacements is the remainder of the list (rule[1:]). Modify the addRule method so that it assigns the list of replacements (rule[1:]) to the self.rules dictionary with the symbol (rule[0]) as the key.

  5. In the applyRules function we need to make use of the dictionary to build up the string. Using a dictionary is much easier than using a list of rules because we don't have to search for a rule matching the key. Instead, we just ask if the key exists in the dictionary. If it does, we pick a random replacement from the list of replacements and add it to the output string. Otherwise, we add the character itself to the output string.
        def applyRules( self, inputString ):
            # assign to a local variable (e.g. resultString) the empty string
            # for each character, char, in the input
                # if char is in the self.rules dictionary
                    # add to resultString a random choice from the dictionary entry self.rules[char]
                # else
                    # add to resultString the character char
            # return resultString
    
  6. Because we are using mutators to update the information in the object, the read method should not change, so long as you are passing to self.addRule the argument words[1:], which passes everything but the first item in the words list.

    When you're done with these changes, try running one of the following L-systems with just one or two iterations. Do it twice and see if you get the same string both times.

  7. Make three changes to the TurtleInterpreter class.

    First, just after the class TurtleInterpreter: line, and inside the class, assign False to a variable called initialized. This variable is what is called a class global. Only one copy of it exists for all of the objects in the class. We're going to use it to keep track of whether the turtle window has been initialized.

    After created the class variable, add the following three lines to the beginning of the __init__ method. Note that we have to use the class name to access a class variable.

            if TurtleInterpreter.initialized:
                return
            TurtleInterpreter.initialized = True
    

    Now the init function will not recreate the turtle window if it already exists.

    Second, add two cases to draw string. For the character '{' call turtle.fill(True), and for the character '}' call turtle.fill(False).

    Third, if you don't already have it, create a method color(self, c) that sets the turtle's color to the value in c.

    When you're done, download and run the following test function. You should get something like the following, but with randomly selected colors.

  8. We're now going to start building a system that makes use of our turtle interpreter class to build shapes that are not necessarily the output of L-systems. Just as we made functions in the second project to draw shapes, now we're going to make classes that create shape objects.

    Create a new file shapes.py. In it, create a class called Shape. It is a base class, from which other classes will be derived. Then define an init method with the following definition.

        def __init__(self, distance = 100, angle = 90, color = (0, 0, 0), istring = '' ):
            
            # create a field of self called distance and assign it distance
            # create a field of self called angle and assign it angle
            # create a field of self called color and assign it color
            # create a field of self called string and assign it istring
    
  9. Create the following mutator methods.
    • setColor(self, c) - set the color field to c
    • setDistance(self, d) - set the distance field to d
    • setAngle(self, a) - set the angle field to a
    • setString(self, s) - set the string field to s
  10. Create a method draw that executes the following algorithm.
        def draw(self, xpos, ypos, scale=1.0, orientation=0):
            # create a TurtleInterpreter object
            # have the TurtleInterpreter object place the turtle at (xpos, ypos, orientation)
            # have the TurtleInterpreter object set the turtle color to self.color
            # have the TurtleInterpreter object draw the string
            #    Note: use the distance, angle, and string fields of self
            #    Note: multiply the distance by the scale parameter of the function
    
  11. In the same file, but below the Shape class definition, begin a new class Square that is derived from Shape.

    class Square(Shape):

    Have the __init__() function of the Square class take two optional arguments: distance and color. The __init__() function should do the following.
        def __init__(self, distance=100, color=(0, 0, 0) ):
            # call the parent's __init__ function with distance, 
            #      an angle of 90, color, and the string 'F-F-F-F-'
    
  12. In the same file, below the Square class, create a new class called Triangle that is the same as the Square class, but sets the string to 'F-F-F-' and sets the angle to 120.
  13. Once you have completed the Square and Triangle classes, download the second test function and run it. You should get the output below.

Assignment:

The project this week is to continue to make shape classes, making use of inheritance, starting with a tree class.


Tasks

  1. The first task is to make a Tree shape class, similar to the Square and Triangle classes. The difference between a Tree and a Square, though, is that the Tree generates its string dynamically using an L-system. The string for a Square is always the same, so it can set the string field once and then use the parent Shape class' draw method. However, every time we draw a tree, we first must build a string using an L-system. Then it can use the Shape draw method.

    Because we use an L-system to generate the string to draw, a Tree object must contain an L-system, which means it must have a field that holds an L-system object.

    Because a Tree is a Shape, it must be a child of the Shape class. That lets it use the parent functions for setting color, distance, and angle, among other things.

    To make our Tree class, start by creating a file called tree.py. Import your lsystem and shape modules. The Tree class should be derived from the Shape class, but you'll need to override some of the methods because of the special nature of a Tree: it needs more fields than a simple Shape, and it has to dynamically create the string it will draw using an L-system.

    The methods you'll need to override or create for the Tree class include:

    • def __init__(self, distance=5, angle=22.5, color=(0.5, 0.4, 0.3), iterations=3, filename=None): - The init function should call the parent init with distance, angle, and color, store the iterations number in an iterations field, then create an Lsystem object and store it in a field.
    • Create a setIterations modifier for the iterations field of the Tree object. Then create a read(self, filename) method that calls the lsystem's read method with the specified filename.
    • Override the draw method--but keep the same parameter list--so it uses the Lsystem to build the string, assigns the string to the string field of self, and then calls the parent draw function. You may want to change the default orientation to 90 so the trees grow up.

    Once you've written the tree class, make a test function for the class and try it out. This function should take in an L-System filename, create a Tree object, and then use the Tree object's draw method to draw at least 3 trees. Use an L-system with multiple replacements for at least one rule and show the three trees are different.

    The output of your tree.py test function is required image 1.

  2. In shapes.py, create at least three classes--other than Square and Triangle--that are derived from the Shape class and define different shapes using strings. One of them should make a filled shape using curly brackets { and } to turn on and off the fill. Make a test function for your shapes.py file that generates an image that incorporates all of the shapes you created. The function should test all of the capabilities of the different shape classes.

    The output of your shapes.py test method is required image 2.

  3. In a file named indoorscene.py, Create a new indoor scene where part of your scene is a set of shapes and at least one tree in something that looks like a painting. You may use only the Tree and various shape classes from this assignment to create the scene. You may not use your turtle code from first three assignments. Only the TurtleInterpreter class can execute turtle commands.

    The indoor scene with a painting is required image 3.

  4. In a file named mosaic.py, create a function tile(x, y, scale) that draws a set of shapes inside a square that is scale by scale in size with the lower left corner of the tile at location (x, y). If scale is 10, then the tile would be 10x10.

    Then make a function mosaic(x, y, scale, Nx, Ny) that draws a 2D array of tiles Nx by Ny, where each tile is of size scale by scale, and the lower left corner of the mosaic is at (x, y). So if scale is 10, Nx is 3 and Ny is 4, the function should draw twelve 10x10 tiles three across and four down.

    An image of at least 20 tiles (5 x 4) is required image 4.


Extensions

Each assignment will have a set of suggested extensions. The required tasks constitute about 85% of the assignment, and if you do only the required tasks and do them well you will earn a B+. To earn a higher grade, you need to undertake one or more extensions. The difficulty and quality of the extension or extensions will determine your final grade for the assignment. One complex extension, done well, or 2-3 simple extensions are typical.

  • Make non-square tiles. Rectangles are easy, hexagons or triangles are a real extension.
  • Make new L-systems and add characters to the vocabulary that do interesting things.
  • Modify drawString so that when drawing a tree the branches droop down like gravity is pulling at them.
  • Create a sequence of images to build an animation.
  • Make more tile functions and mix them around in the mosaic function.
  • Make more shape classes that do interesting things. Making a fixed sequence of characters is easy. Make a shape class where the strings are the result of executing a function. L-systems are one example of a dynamically created string, but there are many other ways to do that.
  • Use a Python language feature new to you (not just a new library feature or function)

Writeup and Hand-In

Before handing in your code, double check that it is well-styled:

  • All variable names and function names use either camelCase or snake_case.
  • All files and functions have docstrings.
  • Comments are added to explain complicated code blocks.
  • All variable and function names are appropriately descriptive.
  • Functions are defined before any other code is added at the top level of each file.
  • In a file with any functions defined, top level code is wrapped so that it won't execute if that file is imported by another.

Make a new wiki page for your assignment. Put the label cs151s14project9 on the page. Each of you needs to make your own writeup.

In addition to making the wiki page writeup, put the python files you wrote on the Academics server in your private handin directory.

Colby Wiki

In general, your writeup should follow the outline below.

  • A brief summary of the task, in your own words. This should be no more than a few sentences. Give the reader context and identify the key purpose of the assignment.
  • A description of your solution to the tasks, including any images you created. (Make sure your images are appropriately sized to fit onto the wiki page.) This should be a description of the form and functionality of your final code. You may want to incorporate code snippets in your description to point out relevant features. Note any unique computational solutions you developed.
  • A description of any extensions you undertook, including images demonstrating those extensions. If you added any modules, functions, or other design components, note their structure and the algorithms you used.
  • A brief description (1-3 sentences) of what you learned.
  • A list of people you worked with, including students who took the course in previous semesters, TAs, and professors. Include in this list anyone whose code you may have seen. If you didn't work with anyone, please say so.
  • Don't forget to label your writeup so that it is easy for others to find. For this lab, use cs151s14project9